PageRenderTime 34ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/~enabled/smd_user_manager.php

https://bitbucket.org/mrdale/txp-plugins
PHP | 2136 lines | 1610 code | 314 blank | 212 comment | 311 complexity | ccada1338aa7ebb75141582e43475df6 MD5 | raw file
  1. <?php
  2. // This is a PLUGIN TEMPLATE for Textpattern CMS.
  3. // Copy this file to a new name like abc_myplugin.php. Edit the code, then
  4. // run this file at the command line to produce a plugin for distribution:
  5. // $ php abc_myplugin.php > abc_myplugin-0.1.txt
  6. // Plugin name is optional. If unset, it will be extracted from the current
  7. // file name. Plugin names should start with a three letter prefix which is
  8. // unique and reserved for each plugin author ("abc" is just an example).
  9. // Uncomment and edit this line to override:
  10. $plugin['name'] = 'smd_user_manager';
  11. // Allow raw HTML help, as opposed to Textile.
  12. // 0 = Plugin help is in Textile format, no raw HTML allowed (default).
  13. // 1 = Plugin help is in raw HTML. Not recommended.
  14. # $plugin['allow_html_help'] = 1;
  15. $plugin['version'] = '0.20';
  16. $plugin['author'] = 'Stef Dawson';
  17. $plugin['author_uri'] = 'http://stefdawson.com/';
  18. $plugin['description'] = 'Manage user accounts, groups and privileges';
  19. // Plugin load order:
  20. // The default value of 5 would fit most plugins, while for instance comment
  21. // spam evaluators or URL redirectors would probably want to run earlier
  22. // (1...4) to prepare the environment for everything else that follows.
  23. // Values 6...9 should be considered for plugins which would work late.
  24. // This order is user-overrideable.
  25. $plugin['order'] = '8';
  26. // Plugin 'type' defines where the plugin is loaded
  27. // 0 = public : only on the public side of the website (default)
  28. // 1 = public+admin : on both the public and admin side
  29. // 2 = library : only when include_plugin() or require_plugin() is called
  30. // 3 = admin : only on the admin side (no AJAX)
  31. // 4 = admin+ajax : only on the admin side (AJAX supported)
  32. // 5 = public+admin+ajax : on both the public and admin side (AJAX supported)
  33. $plugin['type'] = '5';
  34. // Plugin "flags" signal the presence of optional capabilities to the core plugin loader.
  35. // Use an appropriately OR-ed combination of these flags.
  36. // The four high-order bits 0xf000 are available for this plugin's private use
  37. if (!defined('PLUGIN_HAS_PREFS')) define('PLUGIN_HAS_PREFS', 0x0001); // This plugin wants to receive "plugin_prefs.{$plugin['name']}" events
  38. if (!defined('PLUGIN_LIFECYCLE_NOTIFY')) define('PLUGIN_LIFECYCLE_NOTIFY', 0x0002); // This plugin wants to receive "plugin_lifecycle.{$plugin['name']}" events
  39. $plugin['flags'] = '3';
  40. // Plugin 'textpack' is optional. It provides i18n strings to be used in conjunction with gTxt().
  41. // Syntax:
  42. // ## arbitrary comment
  43. // #@event
  44. // #@language ISO-LANGUAGE-CODE
  45. // abc_string_name => Localized String
  46. $plugin['textpack'] = <<<EOT
  47. #@smd_um
  48. smd_um_active => Users currently active:
  49. smd_um_active_timeout => Activity timeout (seconds)
  50. smd_um_admin_group => Protected administrator group
  51. smd_um_article_count => Articles
  52. smd_um_based_on => based on
  53. smd_um_chp_lbl => Change pass
  54. smd_um_file_count => Files
  55. smd_um_grp_affected => . Users affected: {num}
  56. smd_um_grp_created => Group "{name}" created
  57. smd_um_grp_deleted => Group deleted
  58. smd_um_grp_exists => Group already exists as priv ID {id}
  59. smd_um_grp_lbl => Groups
  60. smd_um_grp_new => New group title
  61. smd_um_grp_new_name => name
  62. smd_um_grp_saved => Group info updated
  63. smd_um_heading_grp => User groups
  64. smd_um_heading_prf => User manager settings
  65. smd_um_heading_prv => User privileges
  66. smd_um_heading_usr => User management
  67. smd_um_hierarchical_groups => Assume hierarchical groups (levels)
  68. smd_um_image_count => Images
  69. smd_um_link_count => Links
  70. smd_um_max_search_limit => Maximum user search result limit
  71. smd_um_name_required => A name is required
  72. smd_um_new_user => New user
  73. smd_um_pass_change_error => Password NOT saved
  74. smd_um_pass_length => Password length (characters)
  75. smd_um_prf_lbl => Prefs
  76. smd_um_prv_created => Priv area "{area}" created
  77. smd_um_prv_exists => Priv area already exist
  78. smd_um_prv_lbl => Privs
  79. smd_um_prv_new => New priv area
  80. smd_um_prv_saved => Privs updated
  81. smd_um_prv_smd_um => Cannot create privs for smd_user_manager
  82. smd_um_reset => [R]
  83. smd_um_self_alter => Allow smd_um privs to be altered
  84. smd_um_sel_all => Select the entire area then (c)heck, (u)ncheck or (t)oggle highlighted checkboxes
  85. smd_um_sel_grp => Select this group then (c)heck, (u)ncheck or (t)oggle highlighted checkboxes
  86. smd_um_sel_prv => Select this area set then (c)heck, (u)ncheck or (t)oggle highlighted checkboxes
  87. smd_um_sel_reset => Reset: any checked area sets will revert to their defaults after Save
  88. smd_um_settings => Settings
  89. smd_um_tab_name => User manager
  90. smd_um_tbl_installed => Tables installed
  91. smd_um_tbl_not_installed => Tables not installed
  92. smd_um_tbl_not_removed => Tables not removed
  93. smd_um_tbl_removed => Tables removed
  94. smd_um_user_count => Users in this group:
  95. smd_um_usr_lbl => Users
  96. EOT;
  97. if (!defined('txpinterface'))
  98. @include_once('zem_tpl.php');
  99. # --- BEGIN PLUGIN CODE ---
  100. /**
  101. * smd_user_manager
  102. *
  103. * A Textpattern CMS plugin for complete user administration:
  104. * -> Search / filter / alter info on users (with asset counts)
  105. * -> Create / alter groups (roles)
  106. * -> Create / customise privs (areas)
  107. * -> Online user list
  108. *
  109. * @author Stef Dawson
  110. * @link http://stefdawson.com/
  111. */
  112. // TODO:
  113. // -> Why does multi-edit fire twice? Is it still attached to the Admin->Users table?
  114. if (!defined('SMD_UM_PRIVS')) {
  115. define("SMD_UM_PRIVS", 'smd_um_privs');
  116. }
  117. if (!defined('SMD_UM_GROUPS')) {
  118. define("SMD_UM_GROUPS", 'smd_um_groups');
  119. }
  120. if(@txpinterface == 'admin') {
  121. global $smd_um_event, $txp_permissions, $txp_groups, $txp_user, $step;
  122. $smd_um_event = 'smd_um';
  123. add_privs($smd_um_event.'.usr.list', '1, 2, 3');
  124. add_privs($smd_um_event.'.usr.edit', '1, 2');
  125. add_privs($smd_um_event.'.usr.create', '1');
  126. add_privs($smd_um_event.'.grp', '1');
  127. add_privs($smd_um_event.'.prv', '1');
  128. add_privs($smd_um_event.'.prf', '1');
  129. add_privs('plugin_prefs.smd_user_manager', '1');
  130. register_tab('admin', $smd_um_event, gTxt('smd_um_tab_name'));
  131. register_callback('smd_um_silence', 'admin');
  132. register_callback('smd_um_dispatcher', $smd_um_event);
  133. register_callback('smd_um_dispatcher', $smd_um_event.'.usr.edit.own');
  134. register_callback('smd_um_users_tab', 'admin_side', 'head_end');
  135. register_callback('smd_um_prefs', 'plugin_prefs.smd_user_manager');
  136. register_callback('smd_um_welcome', 'plugin_lifecycle.smd_user_manager');
  137. register_callback('smd_um_inject_css', 'admin_side', 'head_end');
  138. // Log the time of this access attempt
  139. $curr_users = unserialize(get_pref('smd_um_current_users', ''));
  140. $curr_users[$txp_user] = time();
  141. set_pref('smd_um_current_users', serialize($curr_users), 'smd_um', PREF_HIDDEN, '', 0);
  142. // Merge in the groups only for now
  143. smd_um_priv_merge(1, 0);
  144. include_once txpath.'/lib/txplib_admin.php';
  145. // Permit user self-editing
  146. $smd_um_grps = array_keys(smd_um_get_groups(0));
  147. unset($smd_um_grps[0]); // Remove None user
  148. $allprivs = join(',',$smd_um_grps);
  149. add_privs($smd_um_event, $allprivs); // Required to display anything at all on the User Manager tab
  150. add_privs($smd_um_event.'.usr.edit.own', $allprivs);
  151. // Now the privs are established for all admin steps so we can go ahead
  152. // and merge in the changes. One caveat: if we're saving the privs we
  153. // need to delay the database merge until after the resets have been applied,
  154. // otherwise we won't know what the defaults (in admin_config.php) are
  155. $do_privs = (($step == 'smd_um_privs') && ps('smd_um_priv_save')) ? 0 : 1;
  156. smd_um_priv_merge(0, $do_privs);
  157. }
  158. // ********************
  159. // ADMIN SIDE INTERFACE
  160. // ********************
  161. // -------------------------------------------------------------
  162. // CSS definitions: hopefully kind to themers
  163. // Includes a hack for Remora (#nav li ul) to prevent menu from disappearing under the
  164. // privs content. This only occurs because the .smd_um_privgroup class uses position:relative.
  165. // Needs workaround as it affects other plugins.
  166. function smd_um_get_style_rules() {
  167. $smd_um_styles = array(
  168. 'control-panel' => '
  169. .smd_um_privgroup { margin:10px auto; width:50%; position:relative; }
  170. .smd_um_privgroup h3 { text-align:left; font-weight:bold; }
  171. .smd_um_privsave { position:absolute; left:-55px; top:0; }
  172. .smd_um_active_users { margin:15px auto; width:80%; text-align:center; }
  173. .smd_um_selected { background-color:#e2dfce; }
  174. .smd_um_grp_name, .smd_um_prv_name, .smd_um_reset_col { cursor:pointer; }
  175. .smd_um_checkbox, .smd_um_prv_hdr { text-align:center!important; }
  176. #nav li ul { z-index:10000; }
  177. ',
  178. );
  179. return $smd_um_styles;
  180. }
  181. // -------------------------------------------------------------
  182. function smd_um_inject_css($evt, $stp) {
  183. global $smd_um_event, $event;
  184. if ($event == $smd_um_event || $event == 'admin') {
  185. $smd_um_styles = smd_um_get_style_rules();
  186. echo '<style type="text/css">', $smd_um_styles['control-panel'], '</style>';
  187. }
  188. return;
  189. }
  190. // -------------------------------------------------------------
  191. // Destroy the existing Admin->Users tab if we come from any smd_um-initiated step
  192. function smd_um_silence($evt, $stp) {
  193. global $event, $smd_um_event;
  194. $ignore = array(
  195. 'author_list',
  196. 'author_edit',
  197. 'author_save',
  198. 'author_save_new',
  199. 'change_pass',
  200. );
  201. $no_dispatch = array(
  202. 'change_pass',
  203. );
  204. if (!in_array($stp, $ignore)) {
  205. ob_end_clean(); // Kill any existing Admin tab panel...
  206. ob_start(); // ... and start again
  207. $event = $smd_um_event;
  208. if (!in_array($stp, $no_dispatch)) {
  209. smd_um_dispatcher($event, $stp);
  210. }
  211. }
  212. }
  213. // -------------------------------------------------------------
  214. // Plugin jump off point
  215. function smd_um_dispatcher($evt, $stp, $msg='') {
  216. global $smd_um_event, $txp_permissions, $txp_user;
  217. $available_steps = array(
  218. 'smd_um' => false,
  219. 'smd_um_edit' => false,
  220. 'smd_um_save' => true,
  221. 'smd_um_save_new' => true,
  222. 'smd_um_groups' => false,
  223. 'smd_um_privs' => false,
  224. 'smd_um_prefs' => false,
  225. 'smd_um_multi_edit' => true,
  226. 'smd_um_change_pass' => true,
  227. 'smd_um_change_pass_form' => false,
  228. 'smd_um_change_pageby' => true,
  229. 'smd_um_table_install' => true,
  230. 'smd_um_table_remove' => true,
  231. 'save_pane_state' => true,
  232. );
  233. if (!has_privs($smd_um_event.'.usr.list')) {
  234. $stp = ($stp) ? $stp : 'smd_um_edit';
  235. $uid = safe_field('user_id', 'txp_users', "name='" . doSlash($txp_user) . "'");
  236. if ($uid) {
  237. // Inject this value so the edit step picks it up and edits only the current user.
  238. // The edit/save steps will verify if the current user is the one trying to be edited
  239. // to prevent people adding &user_id=N to the URL
  240. $_POST['user_id'] = $uid;
  241. }
  242. }
  243. if ($stp == 'save_pane_state') {
  244. smd_um_save_pane_state();
  245. } else if ($stp && bouncer($stp, $available_steps)) {
  246. if ($msg) {
  247. $stp($msg);
  248. } else {
  249. $stp();
  250. }
  251. } else {
  252. $smd_um_event($msg);
  253. }
  254. }
  255. // ------------------------
  256. // Try to hide the Admin->Users tab in the secondary nav via jQuery.
  257. // May fail with inventive DOM structures / themes
  258. // (note to self: campaign for improvement in this area because jQuery smells hackish)
  259. function smd_um_users_tab() {
  260. global $event;
  261. $userStr = gTxt('tab_site_admin');
  262. echo script_js(<<<EOJS
  263. jQuery(function() {
  264. jQuery("a[href='?event=admin']").each(function() {
  265. me = jQuery(this);
  266. if (me.text() == "{$userStr}") {
  267. me.parent().hide();
  268. }
  269. });
  270. if ('{$event}' == 'admin') {
  271. jQuery("a[href='?event=smd_um']").each(function() {
  272. me = jQuery(this);
  273. me.parent().removeClass('inactive').addClass('active'); // Hive, etc
  274. me.removeClass('tabdown').addClass('tabup'); // Classic, etc
  275. });
  276. }
  277. });
  278. EOJS
  279. );
  280. }
  281. // ------------------------
  282. function smd_um_welcome($evt, $stp) {
  283. $msg = '';
  284. switch ($stp) {
  285. case 'installed':
  286. smd_um_table_install(0);
  287. $msg = 'Super duper users!';
  288. break;
  289. case 'deleted':
  290. smd_um_table_remove(0);
  291. break;
  292. }
  293. return $msg;
  294. }
  295. // ------------------------
  296. // Main user display list
  297. function smd_um($msg='') {
  298. global $step, $smd_um_event, $txp_user, $smd_um_list_pageby;
  299. require_privs($smd_um_event.'.usr.list');
  300. $smd_um_prefs = smd_um_get_prefs();
  301. if (!smd_um_table_exist(1)) {
  302. smd_um_table_install(0);
  303. }
  304. pagetop(gTxt('smd_um_tab_name').' &raquo; '.gTxt('smd_um_usr_lbl'), $msg);
  305. extract(gpsa(array('page', 'sort', 'dir', 'crit', 'search_method')));
  306. if ($sort === '') $sort = get_pref('smd_um_sort_column', 'login');
  307. if ($dir === '') $dir = get_pref('smd_um_sort_dir', 'desc');
  308. $dir = ($dir == 'asc') ? 'asc' : 'desc';
  309. switch ($sort) {
  310. case 'real_name':
  311. $sort_sql = 'RealName '.$dir.', last_access desc';
  312. break;
  313. case 'email':
  314. $sort_sql = 'email '.$dir.', last_access desc';
  315. break;
  316. case 'privs':
  317. $sort_sql = 'privs '.$dir.', last_access desc';
  318. break;
  319. case 'article_count':
  320. $sort_sql = 'article_count '.$dir.', last_access desc';
  321. break;
  322. case 'image_count':
  323. $sort_sql = 'image_count '.$dir.', last_access desc';
  324. break;
  325. case 'file_count':
  326. $sort_sql = 'file_count '.$dir.', last_access desc';
  327. break;
  328. case 'link_count':
  329. $sort_sql = 'link_count '.$dir.', last_access desc';
  330. break;
  331. case 'last_login':
  332. $sort_sql = 'last_access '.$dir;
  333. break;
  334. default:
  335. $sort = 'name';
  336. $sort_sql = 'name '.$dir.', last_access desc';
  337. break;
  338. }
  339. set_pref('smd_um_sort_column', $sort, 'smd_um', PREF_HIDDEN, '', 0, PREF_PRIVATE);
  340. set_pref('smd_um_sort_dir', $dir, 'smd_um', PREF_HIDDEN, '', 0, PREF_PRIVATE);
  341. $switch_dir = ($dir == 'desc') ? 'asc' : 'desc';
  342. $criteria = 1;
  343. $count_columns = array('article_count', 'image_count', 'file_count', 'link_count');
  344. if ($search_method and $crit != '') {
  345. $crit_escaped = doSlash(str_replace(array('\\','%','_','\''), array('\\\\','\\%','\\_', '\\\''), $crit));
  346. // Permit searching by privilege name (sort of)
  347. if ($search_method == 'privileges' && !is_numeric($crit_escaped)) {
  348. $levels = get_groups();
  349. foreach ($levels as $idx => $group) {
  350. if (strpos(strtolower($group), strtolower($crit_escaped)) !== false) {
  351. $crit_escaped = $idx;
  352. break;
  353. }
  354. }
  355. }
  356. // Permit <, =, and > operators in count searches.
  357. // nullcheck is not required for privs searches because the value is a true 0 (whereas in the computed
  358. // columns it's empty/null)
  359. $operator = '=';
  360. $nullcheck = '';
  361. if (in_array($search_method, $count_columns) || $search_method == 'privileges') {
  362. preg_match('/([<=>]+)?([0-9]+)/', $crit_escaped, $matches);
  363. $operator = (isset($matches[1]) && $matches[1] != '') ? $matches[1] : '=';
  364. $crit_escaped = (isset($matches[2]) && $matches[2] != '') ? $matches[2] : $crit_escaped;
  365. $char_one = substr($operator, 0, 1);
  366. $char_two = substr($operator, 1, 1);
  367. $nullcheck = ($char_one == '<' || ($char_one == '>' && $char_two == '=' && $crit_escaped == '0')) ? ' OR ISNULL('.$search_method.')' : '';
  368. }
  369. $critsql = array(
  370. 'login_name' => "name like '%$crit_escaped%'",
  371. 'real_name' => "RealName like '%$crit_escaped%'",
  372. 'email' => "email like '%$crit_escaped%'",
  373. 'privileges' => "privs $operator '$crit_escaped'",
  374. 'article_count' => "article_count $operator '$crit_escaped'$nullcheck",
  375. 'image_count' => "image_count $operator '$crit_escaped'$nullcheck",
  376. 'file_count' => "file_count $operator '$crit_escaped'$nullcheck",
  377. 'link_count' => "link_count $operator '$crit_escaped'$nullcheck",
  378. );
  379. if (array_key_exists($search_method, $critsql)) {
  380. $criteria = $critsql[$search_method];
  381. $limit = get_pref('smd_um_max_search_limit', $smd_um_prefs['smd_um_max_search_limit']['default']);
  382. } else {
  383. $search_method = '';
  384. $crit = '';
  385. }
  386. } else {
  387. $search_method = '';
  388. $crit = '';
  389. }
  390. // Since the *_count columns are computed we need to some jiggery pokery here.
  391. // Thus, if we're looking for counts of 'zero' the actual search value should be isnull()
  392. if (in_array($search_method, $count_columns) && $operator == '=' && $crit_escaped == '0') {
  393. $criteria = "ISNULL($search_method)";
  394. }
  395. // The fields, joins and sub-queries that make up the real and computed columns
  396. $fields = 'txu.user_id, txu.name, txu.RealName, txu.email, txu.privs, unix_timestamp(txu.last_access) as last_login, txp.total AS article_count, txi.total AS image_count, txf.total AS file_count, txl.total AS link_count';
  397. $clause = ' FROM '.PFX.'txp_users as txu
  398. LEFT JOIN (SELECT AuthorID, count(ID) AS total FROM '.PFX.'textpattern GROUP BY AuthorID) AS txp ON txp.AuthorID = txu.name
  399. LEFT JOIN (SELECT author, count(id) AS total FROM '.PFX.'txp_image GROUP BY author) AS txi ON txi.author = txu.name
  400. LEFT JOIN (SELECT author, count(id) AS total FROM '.PFX.'txp_file GROUP BY author) AS txf ON txf.author = txu.name
  401. LEFT JOIN (SELECT author, count(id) AS total FROM '.PFX.'txp_link GROUP BY author) AS txl ON txl.author = txu.name';
  402. // Perform a count on the relevant search item. Doing a count(*) is awkward due to the computed columns
  403. // so a straight query is performed with a loop to increment the total. getThing() or getRows for some reason
  404. // failed under certain conditions
  405. $total = 0;
  406. // Call this so plugins that hook into the step can play
  407. $criteria .= callback_event('admin_criteria', 'author_list', 0, $criteria);
  408. $totrs = safe_query('SELECT '.$fields.$clause.' HAVING '.$criteria);
  409. while ($row = nextRow($totrs)) {
  410. $total++;
  411. }
  412. $btnbar = smd_um_buttons('usr');
  413. echo '<h1 class="txp-heading">', gTxt('smd_um_heading_usr'), '</h1>',
  414. '<div id="', $smd_um_event, '_control" class="txp-control-panel">',
  415. $btnbar;
  416. if ($total < 1) {
  417. if ($criteria != 1) {
  418. echo n, smd_um_search_form($crit, $search_method),
  419. n, graf(gTxt('no_results_found'), ' class="indicator"'),
  420. n, '</div>';
  421. }
  422. return;
  423. }
  424. $limit = max($smd_um_list_pageby, 15);
  425. list($page, $offset, $numPages) = pager($total, $limit, $page);
  426. $use_multi_edit = ( has_privs($smd_um_event.'.usr.edit') && (safe_count('txp_users', '1=1') > 1) );
  427. echo n, smd_um_search_form($crit, $search_method), '</div>';
  428. // Retrieve the user info and related counts
  429. $rs = safe_query('SELECT '.$fields.$clause.' HAVING '.$criteria.' ORDER BY '.$sort_sql.' LIMIT '.$offset.', '.$limit);
  430. if ($rs) {
  431. echo n, '<div class="txp-container">',
  432. n, '<form action="index.php" id="smd_um_form" method="post" name="longform" class="multi_edit_form" onsubmit="return verify(\''.gTxt('are_you_sure').'\')">',
  433. n, '<div class="txp-listtables">',
  434. n, startTable('', '', 'txp-list'),
  435. n, '<thead>',
  436. n, tr(
  437. n. (($use_multi_edit)
  438. ? hCell(fInput('checkbox', 'select_all', 0, '', '', '', '', '', 'select_all'), '', ' title="'.gTxt('toggle_all_selected').'" class="multi-edit"')
  439. : hCell('', '', ' class="multi-edit"')
  440. ).
  441. n. column_head('login_name', 'name', 'smd_um', true, $switch_dir, $crit, $search_method, (('name' == $sort) ? "$dir " : '').'name login-name').
  442. n. column_head('real_name', 'real_name', 'smd_um', true, $switch_dir, $crit, $search_method, (('real_name' == $sort) ? "$dir " : '').'name real-name').
  443. n. column_head('email', 'email', 'smd_um', true, $switch_dir, $crit, $search_method, (('email' == $sort) ? "$dir " : '').'email').
  444. n. column_head('privileges', 'privs', 'smd_um', true, $switch_dir, $crit, $search_method, (('privs' == $sort) ? "$dir " : '').'privs').
  445. n. column_head('last_login', 'last_login', 'smd_um', true, $switch_dir, $crit, $search_method, (('last_login' == $sort) ? "$dir " : '').'date last-login modified').
  446. n. column_head(gTxt('smd_um_article_count'), 'article_count', 'smd_um', true, $switch_dir, $crit, $search_method, (('article_count' == $sort) ? "$dir " : '')).
  447. n. column_head(gTxt('smd_um_image_count'), 'image_count', 'smd_um', true, $switch_dir, $crit, $search_method, (('image_count' == $sort) ? "$dir " : '')).
  448. n. column_head(gTxt('smd_um_file_count'), 'file_count', 'smd_um', true, $switch_dir, $crit, $search_method, (('file_count' == $sort) ? "$dir " : '')).
  449. n. column_head(gTxt('smd_um_link_count'), 'link_count', 'smd_um', true, $switch_dir, $crit, $search_method, (('link_count' == $sort) ? "$dir " : ''))
  450. ),
  451. n, '</thead>',
  452. n, '<tbody>';
  453. $curr_priv = safe_field('privs', 'txp_users', "name = '" .doSlash($txp_user). "'");
  454. while ($row = nextRow($rs)) {
  455. extract(doSpecial($row));
  456. $permitted = smd_um_can_edit($curr_priv, $name, $privs);
  457. echo tr(
  458. td(((has_privs($smd_um_event.'.usr.edit') && $txp_user != $row['name']) ? fInput('checkbox', 'selected[]', $row['name'], 'checkbox') : ''), '', 'multi-edit').
  459. td( (($permitted) ? eLink($smd_um_event, 'smd_um_edit', 'user_id', $user_id, $name) : $name), '', 'name login-name actions').
  460. td($RealName, '', 'name real-name').
  461. td('<a href="mailto:'.$email.'">'.$email.'</a>', '', 'email').
  462. td(smd_um_get_priv_level($privs), '', 'privs').
  463. td(($last_login ? safe_strftime('%b&#160;%Y', $last_login) : ''), '', 'date last-login modified').
  464. td(($article_count) ? eLink('list', 'list', 'search_method', 'author', $article_count, 'crit', $name) : '0').
  465. td(($image_count) ? eLink('image', 'image_list', 'search_method', 'author', $image_count, 'crit', $name) : '0').
  466. td(($file_count) ? eLink('file', 'file_list', 'search_method', 'author', $file_count, 'crit', $name) : '0').
  467. td(($link_count) ? eLink('link', 'link_edit', 'search_method', 'author', $link_count, 'crit', $name) : '0')
  468. );
  469. }
  470. echo n, '</tbody>',
  471. n, endTable(),
  472. n, '</div>',
  473. n, (($use_multi_edit) ? smd_um_multiedit_form($page, $sort, $dir, $crit, $search_method) : ''),
  474. n, tInput(),
  475. n, '</form>',
  476. n, '<div id="users_navigation" class="txp-navigation">',
  477. n, nav_form('smd_um', $page, $numPages, $sort, $dir, $crit, $search_method, $total, $limit),
  478. n, pageby_form('smd_um', $smd_um_list_pageby),
  479. n, '</div>',
  480. n, smd_um_active_users(),
  481. n, '</div>';
  482. }
  483. // Call the Admin side's author_list routine so other plugins with a vested interest can join the party
  484. callback_event('admin', 'author_list');
  485. }
  486. // Can this logged-in user edit the given user account?
  487. function smd_um_can_edit($curr_priv, $name, $privs) {
  488. global $smd_um_event, $txp_user;
  489. $smd_um_prefs = smd_um_get_prefs();
  490. $permitted = false; // Assume no editing rights unlesss otherwise stated
  491. $tiered = get_pref('smd_um_hierarchical_groups', $smd_um_prefs['smd_um_hierarchical_groups']['default']);
  492. $protected = get_pref('smd_um_admin_group', $smd_um_prefs['smd_um_admin_group']['default']);
  493. // For some reason checking ($privs != '') doesn't work *shrug*
  494. if ( ($name != '') && ($privs == 0 || $privs) ) {
  495. $permitted = has_privs($smd_um_event.'.usr.edit.own') && ($name == $txp_user);
  496. $can_edit = has_privs($smd_um_event.'.usr.edit');
  497. if ($can_edit) {
  498. $permitted |= $can_edit;
  499. if ($tiered) {
  500. $permitted &= (($privs >= $curr_priv) || ($privs == 0));
  501. }
  502. if ($protected) {
  503. $permitted &= (($privs != $protected) || ($curr_priv == $protected));
  504. }
  505. }
  506. }
  507. return $permitted;
  508. }
  509. // ------------------------
  510. // Edit a single User
  511. function smd_um_edit($msg='') {
  512. global $step, $txp_user, $smd_um_event;
  513. $vars = array('user_id', 'name', 'RealName', 'email', 'privs');
  514. extract(gpsa($vars));
  515. $rs = array();
  516. $curr_priv = safe_field('privs', 'txp_users', "name = '" .doSlash($txp_user). "'");
  517. if ($user_id) {
  518. $user_id = assert_int($user_id);
  519. $rs = safe_row('*', 'txp_users', "user_id = $user_id");
  520. extract($rs);
  521. } else if (!has_privs($smd_um_event.'.usr.create')) {
  522. // If the current user doesn't have sufficient rights to create new users
  523. // then this Edit request without user_id is a self-edit
  524. $rs = safe_row('*', 'txp_users', "name = '" .doSlash($txp_user). "'");
  525. extract($rs);
  526. }
  527. // Check for edit / creation rights
  528. $permitted = ($user_id) ? smd_um_can_edit($curr_priv, $name, $privs) : has_privs($smd_um_event.'.usr.create');
  529. if (!$permitted)
  530. exit(pageTop('Restricted').'<p style="margin-top:3em;text-align:center">'.
  531. gTxt('restricted_area').'</p>');
  532. $caption = gTxt(($user_id) ? 'edit_author' : 'add_new_author');
  533. pagetop(gTxt('smd_um_tab_name').' &raquo; '.gTxt('smd_um_usr_lbl'), $msg);
  534. $btnbar = smd_um_buttons('usr');
  535. echo '<h1 class="txp-heading">', $caption, '</h1>',
  536. n, '<div id="', $smd_um_event, '_control" class="txp-control-panel">',
  537. n, $btnbar,
  538. n, '</div>',
  539. n, '<div id="', $smd_um_event.'_container" class="txp-edit">',
  540. n, form(
  541. '<div class="txp-edit">'.n.
  542. inputLabel('login_name', ($user_id ? strong($name) : fInput('text', 'name', $name, '', '', '', INPUT_REGULAR, '', 'login_name')), ($user_id ? '' : 'login_name'), ($user_id ? '' : 'add_new_author')).n.
  543. inputLabel('real_name', fInput('text', 'RealName', $RealName, '', '', '', INPUT_REGULAR, '', 'real_name'), 'real_name').n.
  544. inputLabel('login_email', fInput('text', 'email', $email, '', '', '', INPUT_REGULAR, '', 'login_email'), 'email').n.
  545. inputLabel('privileges', (($txp_user != $name) ? selectInput('privs', smd_um_get_groups(1), $privs) : hInput('privs', $privs).strong(smd_um_get_priv_level($privs))), ($user_id ? '' : 'privileges'), 'about_privileges').n.
  546. pluggable_ui('author_ui', 'extend_detail_form', '', $rs).n.
  547. graf(fInput('submit', '', gTxt('save'), 'publish')).
  548. eInput($smd_um_event).
  549. ($user_id ? hInput('user_id', $user_id).sInput('smd_um_save') : sInput('smd_um_save_new')).
  550. '</div>'
  551. , '', '', 'post', 'edit-form', '', 'user_edit'),
  552. '</div>';
  553. // Call the Admin side's author_edit routine so other plugins with a vested interest can join the party
  554. callback_event('admin', 'author_edit');
  555. }
  556. // ------------------------
  557. // Virtually cloned from Admin->Users
  558. function smd_um_save() {
  559. global $event, $smd_um_event, $txp_user;
  560. // Call the Admin side's author_save routine so other plugins can join the party
  561. callback_event('admin', 'author_save');
  562. extract(doSlash(psa(array('privs', 'user_id', 'RealName', 'email'))));
  563. $privs = assert_int($privs);
  564. $user_id = assert_int($user_id);
  565. $name = safe_field('name', 'txp_users', "user_id = $user_id");
  566. $curr_priv = safe_field('privs', 'txp_users', "name = '" .doSlash($txp_user). "'");
  567. // Check for hacking attempts
  568. $permitted = smd_um_can_edit($curr_priv, $name, $privs);
  569. if (!$permitted)
  570. exit(pageTop('Restricted').'<p style="margin-top:3em;text-align:center">'.
  571. gTxt('restricted_area').'</p>');
  572. if (!is_valid_email($email)) {
  573. smd_um_edit(array(gTxt('email_required'), E_ERROR));
  574. return;
  575. }
  576. $rs = safe_update('txp_users', "
  577. privs = $privs,
  578. RealName = '$RealName',
  579. email = '$email'",
  580. "user_id = $user_id"
  581. );
  582. if ($rs) {
  583. $msg = gTxt('author_updated', array('{name}' => $RealName));
  584. } else {
  585. $msg = '';
  586. }
  587. smd_um_dispatcher($event, '', $msg);
  588. }
  589. // ------------------------
  590. // Virtually cloned from Admin->Users
  591. function smd_um_save_new() {
  592. global $smd_um_event;
  593. require_privs($smd_um_event.'.usr.create');
  594. // Call the Admin side's author_save_new routine so other plugins with a vested interest can join the party
  595. callback_event('admin', 'author_save_new');
  596. extract(doSlash(psa(array('privs', 'name', 'email', 'RealName'))));
  597. $smd_um_prefs = smd_um_get_prefs();
  598. $privs = assert_int($privs);
  599. $length = function_exists('mb_strlen') ? mb_strlen($name, '8bit') : strlen($name);
  600. if ($name and $length <= 64 and is_valid_email($email)) {
  601. $exists = safe_field('name', 'txp_users', "name = '" .$name. "'");
  602. if ($exists) {
  603. smd_um_edit(array(gTxt('author_already_exists', array('{name}' => $name)), E_ERROR));
  604. return;
  605. }
  606. $pass_len = get_pref('smd_um_pass_length', $smd_um_prefs['smd_um_pass_length']['default']);
  607. $password = generate_password($pass_len);
  608. $hash = doSlash(txp_hash_password($password));
  609. $nonce = doSlash(md5(uniqid(mt_rand(), TRUE)));
  610. $rs = safe_insert('txp_users', "
  611. privs = $privs,
  612. name = '$name',
  613. email = '$email',
  614. RealName = '$RealName',
  615. nonce = '$nonce',
  616. pass = '$hash'
  617. ");
  618. if ($rs) {
  619. // TODO: consider cloning send_password() because only people with admin.edit can run it (i.e. Publishers)
  620. send_password($RealName, $name, $email, $password);
  621. $msg = gTxt('password_sent_to').sp.$email;
  622. $smd_um_event($msg);
  623. } else {
  624. $msg = array(gTxt('error_adding_new_author'), E_ERROR);
  625. smd_um_edit($msg);
  626. }
  627. } else {
  628. $msg = array(gTxt('error_adding_new_author'), E_ERROR);
  629. smd_um_edit($msg);
  630. }
  631. }
  632. // ------------------------
  633. // Group management panel
  634. function smd_um_groups($msg='') {
  635. global $smd_um_event, $txp_user, $txp_groups, $txp_permissions;
  636. require_privs($smd_um_event.'.grp');
  637. if (!smd_um_table_exist()) {
  638. smd_um_table_install(0);
  639. }
  640. // Handle any form actions
  641. if (ps('smd_um_group_save')) {
  642. $excluded = smd_um_get_groups(0);
  643. $ids = ps('smd_um_group_id');
  644. $names = ps('smd_um_group_name');
  645. $titles = ps('smd_um_group_title');
  646. foreach($ids as $idx => $id) {
  647. $title = $titles[$idx];
  648. $name = strtolower(sanitizeForUrl($names[$idx]));
  649. // Can't create duplicate types
  650. if (!in_array($name, $excluded)) {
  651. safe_update(SMD_UM_GROUPS, "name='" . doSlash($name) . "'", "id='" . doSlash($id) . "'");
  652. }
  653. smd_um_upsert_lang($title, $name);
  654. }
  655. $msg = gTxt('smd_um_grp_saved');
  656. } else if (ps('smd_um_group_add')) {
  657. $title = ps('smd_um_new_grp');
  658. $name = ps('smd_um_new_grp_name');
  659. $name = ($name == '') ? strtolower(sanitizeForUrl($title)) : $name;
  660. if ($name) {
  661. $exists = safe_field('id', SMD_UM_GROUPS, "name='".doSlash($name)."'");
  662. if ($exists) {
  663. $msg = array(gTxt('smd_um_grp_exists', array('{id}' => $exists)), E_USER_WARNING);
  664. } else {
  665. // It's not atomic but it'll do, given that:
  666. // a) normally only one person administers this plugin
  667. // b) groups are added one at a time
  668. $curr_max = safe_field("MAX(id)", SMD_UM_GROUPS, '1=1');
  669. $new_priv = ($curr_max + 1);
  670. safe_insert(SMD_UM_GROUPS, "id='" . $new_priv . "', name='" . doSlash($name) . "'");
  671. smd_um_upsert_lang($title, $name);
  672. $based_on = ps('smd_um_new_grp_based_on');
  673. if ($based_on != '') {
  674. assert_int($based_on);
  675. // Can't rely on the privs being in the database so resort to the (merged) array
  676. foreach ($txp_permissions as $area => $privs) {
  677. $privs = do_list($privs);
  678. if (in_array($based_on, $privs)) {
  679. safe_insert(SMD_UM_PRIVS, "area='" . doSlash($area) . "', priv='" . doSlash($new_priv) . "'");
  680. }
  681. }
  682. }
  683. $msg = gTxt('smd_um_grp_created', array('{name}' => $name));
  684. }
  685. } else {
  686. $msg = array(gTxt('smd_um_name_required'), E_ERROR);
  687. }
  688. } else if (ps('smd_um_group_del')) {
  689. $id = str_replace('smd_um_del_', '', ps('smd_um_grp_del'));
  690. assert_int($id);
  691. $affected_users = safe_column('user_id', 'txp_users', "privs = '".doSlash($id)."'");
  692. if ($affected_users) {
  693. // Set all orphaned users to no privs -- can always assign them a new group from the main screen later
  694. $ret = safe_update('txp_users', "privs=0", "user_id IN ('". join("','", doSlash($affected_users)) ."')");
  695. }
  696. // TODO: double check this isn't a core group?
  697. $red = safe_delete(SMD_UM_GROUPS, "id='".doSlash($id)."'");
  698. if ($red) {
  699. $ret = safe_delete(SMD_UM_PRIVS, "priv='".doSlash($id)."'");
  700. $msg = gTxt('smd_um_grp_deleted') . ($affected_users ? gTxt('smd_um_grp_affected', array('{num}' => count($affected_users))) : '');
  701. }
  702. }
  703. // Render the page
  704. pagetop(gTxt('smd_um_tab_name').' &raquo; '.gTxt('smd_um_grp_lbl'), $msg);
  705. $btnbar = smd_um_buttons('grp');
  706. $grouplist = smd_um_get_groups(1);
  707. unset($grouplist[0]); // Don't want None privs
  708. $grouplist = selectInput('smd_um_new_grp_based_on', $grouplist, '', true, '', 'smd_um_new_grp_based_on');
  709. // New group
  710. echo '<h1 class="txp-heading">', gTxt('smd_um_heading_grp'), '</h1>',
  711. n, '<div id="'.$smd_um_event.'_control" class="txp-control-panel">',
  712. n, $btnbar,
  713. n, form(
  714. graf(
  715. '<label for="smd_um_new_grp">' . gTxt('smd_um_grp_new') . '</label>'
  716. .n.fInput('text', 'smd_um_new_grp', '', '', '', '', '', '', 'smd_um_new_grp')
  717. .n.'<label for="smd_um_new_grp_name">' . gTxt('smd_um_grp_new_name') . '</label>'
  718. .n.fInput('text', 'smd_um_new_grp_name', '', '', '', '', '', '', 'smd_um_new_grp_name')
  719. .n.'<label for="smd_um_new_grp_based_on">' . gTxt('smd_um_based_on') . '</label>'
  720. .n.$grouplist
  721. .n.fInput('submit', 'smd_um_group_add', gTxt('create'))
  722. .n.eInput($smd_um_event)
  723. .n.sInput('smd_um_groups')
  724. )
  725. , '','','post','search-form'
  726. ),
  727. n, '</div>';
  728. // Retrieve the group info and user counts per privilege level
  729. $fields = 'smdg.id, smdg.name, smdg.core, txu.total AS user_count';
  730. $clause = ' FROM '.PFX.'smd_um_groups AS smdg
  731. LEFT JOIN (SELECT privs, count(privs) AS total FROM '.PFX.'txp_users GROUP BY privs) AS txu ON smdg.id = txu.privs';
  732. $rs = getRows('SELECT ' . $fields.$clause . ' ORDER BY id');
  733. if ($rs) {
  734. echo n, '<div class="plugin-column">',
  735. n, '<form action="index.php" id="smd_um_grp_form" method="post" name="longform" onsubmit="return verify(\''.gTxt('are_you_sure').'\')">',
  736. n.'<div class="txp-listtables">'.
  737. n, startTable('', '', 'txp-list'),
  738. n, '<thead>',
  739. n, tr(
  740. hCell('ID', '', ' class="id"').
  741. hCell('name', '', ' class="name"').
  742. hCell('title', '', ' class="name"').
  743. hCell('', '', '')
  744. ),
  745. n, '</thead>',
  746. n, '<tbody>';
  747. foreach ($rs as $row) {
  748. extract(doSpecial($row));
  749. $user_count = empty($user_count) ? 0 : $user_count;
  750. $dLink = ($core) ? '&nbsp;' : fInput('submit', 'smd_um_group_del', "×", '', '', 'smd_um_presub(this)', '', '', 'smd_um_del_'.$id)
  751. .eInput($smd_um_event)
  752. .sInput('smd_um_groups')
  753. .tInput();
  754. echo tr(
  755. tda(
  756. hInput('smd_um_group_id[]', $id)
  757. .(($user_count) ? eLink($smd_um_event, '', 'search_method', 'privileges', $id, 'crit', $id) : $id)
  758. , ' class="id"'
  759. .(($user_count) ? ' title="' . gTxt('smd_um_user_count') . $user_count . '"': '')
  760. )
  761. .td(fInput('text', 'smd_um_group_name[]', $name), '', 'name')
  762. .td(fInput('text', 'smd_um_group_title[]', gTxt($name)), '', 'name')
  763. .td($dLink)
  764. );
  765. }
  766. echo n, '</tbody>',
  767. n, endTable(),
  768. n, '</div>',
  769. n, graf(fInput('submit', 'smd_um_group_save', gTxt('save'), 'publish')),
  770. n, fInput('hidden', 'smd_um_grp_del', '', '', '', '', '', '', 'smd_um_grp_del'),
  771. n, eInput($smd_um_event),
  772. n, sInput('smd_um_groups'),
  773. n, tInput(),
  774. n, '</form>',
  775. n, smd_um_active_users(),
  776. n, '</div>',
  777. script_js(<<<EOJS
  778. function smd_um_presub(obj) {
  779. jQuery('#smd_um_grp_del').val(jQuery(obj).attr('id'));
  780. }
  781. EOJS
  782. );
  783. }
  784. }
  785. // ------------------------
  786. // Privs management panel
  787. function smd_um_privs($msg='') {
  788. global $smd_um_event, $txp_user, $txp_permissions;
  789. require_privs($smd_um_event.'.prv');
  790. if (!smd_um_table_exist()) {
  791. smd_um_table_install(0);
  792. }
  793. if (ps('smd_um_priv_save')) {
  794. $areas = ps('smd_um_areas');
  795. foreach ($areas as $area) {
  796. $ar_fakename = str_replace('.', '---', $area);
  797. $privs = ps($ar_fakename);
  798. $area = strtolower(sanitizeForPage($area));
  799. // Delete the old area privs if they exist
  800. safe_delete(SMD_UM_PRIVS, "area='".doSlash($area)."'");
  801. if (is_array($privs)) {
  802. foreach ($privs as $priv) {
  803. // Reset should always be first in the list since it's the first checkbox col
  804. // If reset, don't add the privs again (thus they will be read from admin_config.php)
  805. if ($priv == 'smd_um_reset') {
  806. break;
  807. } else {
  808. assert_int($priv);
  809. safe_insert(SMD_UM_PRIVS, "area='" . doSlash($area) . "', priv='" . doSlash($priv) . "'");
  810. }
  811. }
  812. }
  813. }
  814. // Merge the changes into the priv table
  815. smd_um_priv_merge(0,1);
  816. $msg = gTxt('smd_um_prv_saved');
  817. } else if (ps('smd_um_priv_add')) {
  818. $name = ps('smd_um_new_prv');
  819. $name = strtolower(sanitizeForPage($name));
  820. if ($name) {
  821. if (strpos($name, 'smd_um') === 0) {
  822. // Can't create privs for this plugin
  823. $msg = array(gTxt('smd_um_prv_smd_um'), E_USER_WARNING);
  824. } else {
  825. $exists = array_key_exists($name, $txp_permissions);
  826. if ($exists) {
  827. $msg = array(gTxt('smd_um_prv_exists'), E_USER_WARNING);
  828. } else {
  829. safe_insert(SMD_UM_PRIVS, "area='" . doSlash($name) . "'");
  830. smd_um_priv_merge(0,1);
  831. $msg = gTxt('smd_um_prv_created', array('{area}' => $name));
  832. }
  833. }
  834. } else {
  835. $msg = array(gTxt('smd_um_name_required'), E_ERROR);
  836. }
  837. }
  838. pagetop(gTxt('smd_um_tab_name').' &raquo; '.gTxt('smd_um_prv_lbl'), $msg);
  839. $btnbar = smd_um_buttons('prv');
  840. echo '<h1 class="txp-heading">', gTxt('smd_um_heading_prv'), '</h1>',
  841. n, '<div id="'.$smd_um_event.'_control" class="txp-control-panel">',
  842. n, $btnbar,
  843. n, form(
  844. graf(
  845. '<label for="smd_um_new_prv">' . gTxt('smd_um_prv_new') . '</label>'
  846. .n.fInput('text', 'smd_um_new_prv', '', '', '', '', '', '', 'smd_um_new_prv')
  847. .n.fInput('submit', 'smd_um_priv_add', gTxt('create'))
  848. .n.eInput($smd_um_event)
  849. .n.sInput('smd_um_privs')
  850. )
  851. , '','','post','search-form'
  852. ),
  853. n, '</div>';
  854. $grouplist_name = smd_um_get_groups(0);
  855. $grouplist_title = smd_um_get_groups(1);
  856. unset($grouplist_name[0]); // Don't want None privs
  857. unset($grouplist_title[0]); // Ditto
  858. $curr_area = '';
  859. $area_count = 0;
  860. $thatts = ' class="smd_um_grp_name" title="' . gTxt('smd_um_sel_grp') . '"';
  861. $headers = '<thead>'.tr(
  862. hCell('', '', ' class="smd_um_sel_area" title="' . gTxt('smd_um_sel_all') . '"')
  863. .hCell(gTxt('smd_um_reset'), '', ' class="smd_um_reset_col" title="' . gTxt('smd_um_sel_reset') . '"')
  864. .hCell(join('</th><th'.$thatts.'>', $grouplist_title), '', $thatts)
  865. , ' class="smd_um_prv_hdr"'). '</thead>';
  866. $viz = do_list(get_pref('pane_smd_um_priv_visible'));
  867. echo script_js(<<<EOJS
  868. function smd_um_presub(obj) {
  869. jQuery('#smd_um_prv_del').val(jQuery(obj).attr('id'));
  870. }
  871. jQuery.fn.smd_um_rowsel = function(idx) {
  872. return jQuery('tr:nth-child('+(idx+1)+') td.smd_um_checkbox', this);
  873. }
  874. jQuery.fn.smd_um_colsel = function(idx) {
  875. return jQuery('tr td:nth-child('+(idx+1)+')', this);
  876. }
  877. // Affect all highlighted checkboxes on keypress
  878. function smd_um_toggleCheckbox(ev) {
  879. key = ev.keyCode;
  880. obj = jQuery('.smd_um_selected :checkbox');
  881. switch(key) {
  882. case 67:
  883. // (c)heck selected boxes
  884. obj.prop('checked', true);
  885. break;
  886. case 68:
  887. // (d)eselect all selected rows/cols
  888. jQuery('.smd_um_selected, .smd_um_rsel, .smd_um_csel').removeClass('smd_um_selected smd_um_rsel smd_um_csel');
  889. break;
  890. case 84:
  891. // (t)oggle selected boxes
  892. obj.each(function() {
  893. cb = jQuery(this);
  894. if (cb.prop('checked') == true) {
  895. cb.prop('checked', false);
  896. } else {
  897. cb.prop('checked', true);
  898. }
  899. });
  900. break;
  901. case 85:
  902. // (u)ncheck selected boxes
  903. obj.prop('checked', false);
  904. break;
  905. }
  906. }
  907. jQuery(function() {
  908. jQuery(document).bind('keyup', smd_um_toggleCheckbox);
  909. // Row selector
  910. jQuery('.smd_um_prv_name').click(function() {
  911. tr = jQuery(this).closest('tr');
  912. rownum = tr.index();
  913. obj = jQuery(tr).parent().smd_um_rowsel(rownum);
  914. // Can't use toggleClass because it would untoggle any cols that were already selected
  915. if (jQuery(this).hasClass('smd_um_rsel')) {
  916. obj.removeClass('smd_um_selected');
  917. } else {
  918. obj.addClass('smd_um_selected');
  919. }
  920. jQuery(this).toggleClass('smd_um_rsel');
  921. });
  922. // Column selector
  923. jQuery('.smd_um_grp_name, .smd_um_reset_col').click(function() {
  924. colnum = jQuery(this).index();
  925. tbody = jQuery(this).parent().parent().next('tbody');
  926. obj = jQuery(tbody).smd_um_colsel(colnum);
  927. // Can't use toggleClass because it would untoggle any rows that were already selected
  928. if (colnum > 0) {
  929. if (jQuery(this).hasClass('smd_um_csel')) {
  930. obj.removeClass('smd_um_selected');
  931. } else {
  932. obj.addClass('smd_um_selected');
  933. }
  934. }
  935. jQuery(this).toggleClass('smd_um_csel');
  936. });
  937. // Whole table selector
  938. jQuery('.smd_um_sel_area').click(function() {
  939. me = jQuery(this);
  940. thead = me.parent().parent();
  941. tbody = thead.next('tbody');
  942. tbody.toggleClass('smd_um_allsel');
  943. if (tbody.hasClass('smd_um_allsel')) {
  944. tbody.find('.smd_um_prv_name').removeClass('smd_um_rsel').click();
  945. me.nextAll('.smd_um_grp_name').addClass('smd_um_csel');
  946. } else {
  947. tbody.find('.smd_um_prv_name').addClass('smd_um_rsel').click();
  948. me.nextAll('.smd_um_grp_name').removeClass('smd_um_csel');
  949. }
  950. });
  951. });
  952. EOJS
  953. );
  954. echo n, '<div class="txp-list">',
  955. n, '<form action="index.php" id="smd_um_privilege_form" method="post" name="longform" onsubmit="return verify(\''.gTxt('are_you_sure').'\')">',
  956. n, eInput($smd_um_event).sInput('smd_um_privs').tInput();
  957. foreach ($txp_permissions as $area => $privs) {
  958. $priv_list = do_list($privs);
  959. $area_parts = do_list($area, '.');
  960. if (preg_match('/^([A-Za-z0-9]{3,3})\_/', $area_parts[0], $matches)) {
  961. // Plugin
  962. $area_parts[0] = $matches[1];
  963. }
  964. // Start of a new area so close the previous one and start a new block
  965. if ($curr_area != $area_parts[0]) {
  966. if ($area_count > 0) {
  967. echo '</tbody>' . endTable() . '</div></div>';
  968. }
  969. $area_head = gTxt($area_parts[0]);
  970. $is_viz = in_array($area_parts[0], $viz);
  971. $ref = 'smd_um_priv_'.$area_parts[0];
  972. echo n, '<div class="smd_um_privgroup"><h3 class="txp-summary lever', ($is_viz ? ' expanded' : ''), '"><a href="#', $ref, '">', $area_parts[0], (($area_parts[0] != $area_head) ? ' ('. gTxt($area_parts[0]). ')' : ''), '</a></h3>',
  973. n, '<div id="', $ref, '" class="toggle" style="display:', ($is_viz ? 'block' : 'none'), '">',
  974. n, fInput('submit', 'smd_um_priv_save', gTxt('save'), 'smd_um_privsave'),
  975. n, startTable('', '', 'txp-list'),
  976. n, $headers,
  977. n, '<tbody>';
  978. }
  979. $privboxes = array();
  980. $safe_area = str_replace('.', '---', $area).'[]';
  981. foreach ($grouplist_name as $id => $priv) {
  982. /// Dots aren't valid characters for a name so replace them now and swap them back upon Save
  983. $privboxes[] = td(checkbox($safe_area, $id, (in_array($id, $priv_list))), '', 'smd_um_checkbox');
  984. }
  985. echo tr(
  986. tda($area.hInput('smd_um_areas[]', $area), ' class="smd_um_prv_name" title="' . gTxt('smd_um_sel_prv') . '"')
  987. .td(checkbox($safe_area, 'smd_um_reset', 0), '', 'smd_um_resetbox')
  988. .join(n, $privboxes)
  989. );
  990. $curr_area = $area_parts[0];
  991. $area_count++;
  992. }
  993. echo n, endTable(),
  994. n, '</div></div>',
  995. n, fInput('hidden', 'smd_um_prv_del', '', '', '', '', '', '', 'smd_um_prv_del'),
  996. n, '</form>',
  997. n, smd_um_active_users(),
  998. n, '</div>';
  999. }
  1000. // -------------------------------------------------------------
  1001. function smd_um_wrap_widget($widget) {
  1002. return '<span class="edit-value">'.$widget.'</span>';
  1003. }
  1004. // ------------------------
  1005. // Prefs management panel
  1006. function smd_um_prefs($msg='') {
  1007. global $smd_um_event, $txp_user;
  1008. require_privs($smd_um_event.'.prf');
  1009. if (!smd_um_table_exist()) {
  1010. smd_um_table_install(0);
  1011. }
  1012. $smd_um_prefs = smd_um_get_prefs();
  1013. if (ps('smd_um_pref_save')) {
  1014. foreach ($smd_um_prefs as $idx => $prefobj) {
  1015. $val = ps($idx);
  1016. $val = (is_array($val)) ? join(', ', $val) : $val;
  1017. set_pref($idx, doSlash($val), $smd_um_event, $prefobj['type'], $prefobj['html'], $prefobj['position']);
  1018. }
  1019. $msg = gTxt('preferences_saved');
  1020. }
  1021. pagetop(gTxt('smd_um_tab_name').' &raquo; '.gTxt('smd_um_prf_lbl'), $msg);
  1022. $btnbar = smd_um_buttons('prf');
  1023. echo '<h1 class="txp-heading">', gTxt('smd_um_heading_prf'), '</h1>',
  1024. n, '<div id="', $smd_um_event, '_control" class="txp-control-panel">', $btnbar, '</div>';
  1025. $out = array();
  1026. $out[] = n.'<div class="plugin-column">';
  1027. $out[] = '<form name="smd_um_prefs" id="smd_um_prefs" action="index.php" method="post">';
  1028. $out[] = eInput($smd_um_event).sInput('smd_um_prefs');
  1029. $last_grp = '';
  1030. foreach ($smd_um_prefs as $idx => $prefobj) {
  1031. if ($last_grp != $prefobj['group']) {
  1032. $out[] = hed(gTxt($prefobj['group']), 2);
  1033. }
  1034. $last_grp = $prefobj['group'];
  1035. $subout = array();
  1036. $label = '<span class="edit-label">'
  1037. .'<label>'.gTxt($idx).'</label>'
  1038. .'</span>';
  1039. $val = get_pref($idx, $prefobj['default'], 1);
  1040. $vis = (isset($prefobj['visible']) && !$prefobj['visible']) ? 'empty' : '';
  1041. switch ($prefobj['html']) {
  1042. case 'text_input':
  1043. $subout[] = smd_um_wrap_widget(fInput('text', $idx, $val, '', '', '', INPUT_REGULAR, '', $idx));
  1044. break;
  1045. case 'textarea':
  1046. $subout[] = text_area($idx, '', '', $val, $idx);
  1047. break;
  1048. case 'yesnoradio':
  1049. $subout[] = smd_um_wrap_widget(yesnoRadio($idx, $val));
  1050. break;
  1051. case 'radioset':
  1052. $subout[] = smd_um_wrap_widget(radioSet($prefobj['content'], $idx, $val));
  1053. break;
  1054. case 'checkboxset':
  1055. $vals = do_list($val);
  1056. $lclout = array();
  1057. foreach ($prefobj['content'] as $cb => $val) {
  1058. $checked = in_array($cb, $vals);
  1059. $lclout[] = checkbox($idx.'[]', $cb, $checked). '<label>' . gTxt($val) . '</label>';
  1060. }
  1061. $subout[] = smd_um_wrap_widget(join(n, $lclout));
  1062. break;
  1063. case 'selectlist':
  1064. $subout[] = smd_um_wrap_widget(selectInput($idx, $prefobj['content'][0], $val, $prefobj['content'][1]));
  1065. break;
  1066. default:
  1067. if ( strpos($prefobj['html'], 'smd_um_') !== false && is_callable($prefobj['html']) ) {
  1068. $subout[] = smd_um_wrap_widget($prefobj['html']($idx, $val));
  1069. }
  1070. break;
  1071. }
  1072. $out[] = graf($label . n.join(n ,$subout), ($vis ? ' class="'.$vis.'"' : ''));
  1073. }
  1074. $out[] = graf(fInput('submit', 'smd_um_pref_save', gTxt('save'), 'publish'));
  1075. $out[] = tInput();
  1076. $out[] = '</form>'.smd_um_active_users().'</div>';
  1077. echo join(n, $out);
  1078. }
  1079. // ------------------------
  1080. // Common buttons for the interface
  1081. function smd_um_buttons($curr='usr') {
  1082. global $step, $smd_um_event;
  1083. $grp = has_privs($smd_um_event.'.grp');
  1084. $prf = has_privs($smd_um_event.'.prf');
  1085. $prv = has_privs($smd_um_event.'.prv');
  1086. $new = has_privs($smd_um_event.'.usr.create');
  1087. $usr = ($grp || $prf || $prv); // Don't show usr button if it's the only tab available
  1088. $btns = array (
  1089. 'new' => ( ($new) ? sLink($smd_um_event, 'smd_um_edit', gTxt('smd_um_new_user'), 'navlink') : ''),
  1090. 'grp' => ( ($grp) ? sLink($smd_um_event, 'smd_um_groups', gTxt('smd_um_grp_lbl'), 'navlink') : ''),
  1091. 'prf' => ( ($prf) ? sLink($smd_um_event, 'smd_um_prefs', gTxt('smd_um_prf_lbl'), 'navlink') : ''),
  1092. 'prv' => ( ($prv) ? sLink($smd_um_event, 'smd_um_privs', gTxt('smd_um_prv_lbl'), 'navlink') : ''),
  1093. 'usr' => ( ($usr) ? sLink($smd_um_event, 'smd_um_list', gTxt('smd_um_usr_lbl'), 'navlink') : ''),
  1094. 'chp' => ( sLink($smd_um_event, 'smd_um_change_pass_form', gTxt('change_password'), 'navlink')),
  1095. );
  1096. return graf(
  1097. (($curr == 'usr') ? n. ($step === 'smd_um_edit' ? '' : $btns['chp']) .n. $btns['new'] .n. strong($btns['usr']) : n.$btns['usr'])
  1098. .n.(($curr == 'grp') ? strong($btns['grp']) : $btns['grp'])
  1099. .n.(($curr == 'prv') ? strong($btns['prv']) : $btns['prv'])
  1100. .n.(($curr == 'prf') ? strong($btns['prf']) : $btns['prf'])
  1101. , ' class="txp-buttons"');
  1102. }
  1103. // ------------------------
  1104. // Alter the pageby quantity
  1105. function smd_um_change_pageby() {
  1106. global $smd_um_event;
  1107. event_change_pageby('smd_um');
  1108. $smd_um_event();
  1109. }
  1110. // ------------------------
  1111. // The user panel search dropdown list
  1112. function smd_um_search_form($crit, $method) {
  1113. global $smd_um_event;
  1114. $methods = array(
  1115. 'login_name' => gTxt('login_name'),
  1116. 'real_name' => gTxt('real_name'),
  1117. 'email' => gTxt('email'),
  1118. 'privileges' => gTxt('privileges'),
  1119. 'article_count' => gTxt('smd_um_article_count'),
  1120. 'image_count' => gTxt('smd_um_image_count'),
  1121. 'file_count' => gTxt('smd_um_file_count'),
  1122. 'link_count' => gTxt('smd_um_link_count'),
  1123. );
  1124. return search_form($smd_um_event, '', $crit, $methods, $method, 'login');
  1125. }
  1126. // ------------------------
  1127. function smd_um_get_priv_level($priv) {
  1128. $levels = get_groups();
  1129. return $levels[$priv];
  1130. }
  1131. // ------------------------
  1132. // Merge/edit the groups & privs into the existing global arrays
  1133. function smd_um_priv_merge($do_grp=1, $do_priv=1) {
  1134. global $txp_groups, $txp_permissions;
  1135. $smd_um_prefs = smd_um_get_prefs();
  1136. if ($do_grp && smd_um_table_exist(SMD_UM_GROUPS)) {
  1137. $new_groups = safe_rows('id, name', SMD_UM_GROUPS, '1=1');
  1138. foreach ($new_groups as $row) {
  1139. $txp_groups[$row['id']] = $row['name'];
  1140. }
  1141. ksort($txp_groups);
  1142. }
  1143. if ($do_priv && smd_um_table_exist(SMD_UM_PRIVS)) {
  1144. $new_privs = safe_rows('area, GROUP_CONCAT(priv) AS privs', SMD_UM_PRIVS, '1=1 GROUP BY area ORDER BY area');
  1145. // Allow this plugin's strings to be skipped if we don't want people upsetting the plugin's behaviour
  1146. $self_edit = get_pref('smd_um_self_alter', $smd_um_prefs['smd_um_self_alter']['default']);
  1147. foreach ($new_privs as $row) {
  1148. if (strpos($row['area'], 'smd_um') === false || $self_edit) {
  1149. $txp_permissions[$row['area']] = $row['privs'];
  1150. }
  1151. }
  1152. ksort($txp_permissions);
  1153. }
  1154. }
  1155. // ------------------------
  1156. // Show who's currently online
  1157. function smd_um_active_users() {
  1158. global $smd_um_event;
  1159. $smd_um_prefs = smd_um_get_prefs();
  1160. $curr_users = unserialize(get_pref('smd_um_current_users', '', 1));
  1161. $timeout = get_pref('smd_um_active_timeout', $smd_um_prefs['smd_um_active_timeout']['default']);
  1162. $online = array();
  1163. if (is_array($curr_users)) {
  1164. foreach ($curr_users as $user => $last_access) {
  1165. $still_active = strtotime("+$timeout seconds", $last_access);
  1166. if ( ($still_active !== false) && ($still_active > time()) ) {
  1167. $online[] = eLink($smd_um_event, '', 'search_method', 'login_name', $user, 'crit', $user);
  1168. }
  1169. }
  1170. }
  1171. return ($online) ? '<div class="smd_um_active_users">' . gTxt('smd_um_active') . join(', ', $online) . '</div>' : '';
  1172. }
  1173. // ------------------------
  1174. // Update any language string. Note this may leave orphan strings if the name is changed
  1175. function smd_um_upsert_lang($title, $name='') {
  1176. global $textarray;
  1177. $lang = get_pref('language', 'en-gb');
  1178. $name = (isset($name) && $name != '') ? $name : strtolower(sanitizeForUrl($title));
  1179. $table = 'txp_lang';
  1180. $where = "name='" . doSlash($name) . "' AND lang='" . doSlash($lang) . "'";
  1181. // Try to update first
  1182. $ret = safe_update($table, "data='" . doSlash($title) . "'", $where);
  1183. if ($ret && (mysql_affected_rows() || safe_count($table, $where))) {
  1184. // Do nothing -- record has been updated
  1185. } else {
  1186. safe_insert($table, "event='admin', name='" . doSlash($name) . "', lang='" . doSlash($lang) . "', data='" . doSlash($title) . "'");
  1187. }
  1188. // Update the global array on the page so gtxt() can get it immediately
  1189. $textarray[$name] = $title;
  1190. }
  1191. // ------------------------
  1192. function smd_um_get_groups($type=0) {
  1193. global $txp_groups, $txp_user;
  1194. static $permitted_users = array();
  1195. if (isset($permitted_users[$type])) {
  1196. return $permitted_users[$type];
  1197. }
  1198. $smd_um_prefs = smd_um_get_prefs();
  1199. $levels = ($type) ? get_groups() : $txp_groups;
  1200. $tiered = get_pref('smd_um_hierarchical_groups', $smd_um_prefs['smd_um_hierarchical_groups']['default']);
  1201. $curr_priv = safe_field('privs', 'txp_users', "name = '" .doSlash($txp_user). "'");
  1202. if (smd_um_table_exist(SMD_UM_GROUPS)) {
  1203. $protected = get_pref('smd_um_admin_group', $smd_um_prefs['smd_um_admin_group']['default']);
  1204. $grp = safe_rows('id, name', SMD_UM_GROUPS, '1=1');
  1205. foreach ($grp as $row) {
  1206. if ($protected && ($row['id'] == $protected) && ($curr_priv != $protected) ) {
  1207. unset($levels[$row['id']]);
  1208. } else {
  1209. $levels[$row['id']] = (($type) ? gTxt($row['name']) : $row['name']);
  1210. }
  1211. }
  1212. }
  1213. if ($tiered) {
  1214. // Remove any privs higher than the current logged in user
  1215. foreach ($levels as $priv => $name) {
  1216. if ( ($priv == 0) || ($priv >= $curr_priv) ) {
  1217. $permitted_users[$type][$priv] = $name;
  1218. }
  1219. }
  1220. } else {
  1221. $permitted_users[$type] = $levels;
  1222. }
  1223. ksort($permitted_users[$type]);
  1224. return $permitted_users[$type];
  1225. }
  1226. // ------------------------
  1227. function smd_um_multiedit_form($page, $sort, $dir, $crit, $search_method) {
  1228. $levels = smd_um_get_groups(1);
  1229. $privileges = selectInput('privs', $levels, '', '', '', 'privileges');
  1230. $rs = safe_column('name', 'txp_users', '1=1');
  1231. $assign_assets = $rs ? '<label for="assign_assets">'.gTxt('assign_assets_to').'</label>'.n.selectInput('assign_assets', $rs, '', true, '', 'assign_assets') : '';
  1232. $methods = array(
  1233. 'changeprivilege' => array('label' => gTxt('changeprivilege'), 'html' => $privileges),
  1234. 'resetpassword' => gTxt('resetpassword'),
  1235. 'delete' => array('label' => gTxt('delete'), 'html' => $assign_assets),
  1236. );
  1237. if (safe_count('txp_users', '1=1') <= 1) unset($methods['delete']);
  1238. return multi_edit($methods, 'smd_um', 'smd_um_multi_edit', $page, $sort, $dir, $crit, $search_method);
  1239. }
  1240. // ------------------------
  1241. // Cloned and tweaked from Admin tab :-(
  1242. function smd_um_multi_edit() {
  1243. global $smd_um_event, $txp_user;
  1244. require_privs($smd_um_event.'.usr.edit');
  1245. $smd_um_prefs = smd_um_get_prefs();
  1246. $selected = ps('selected');
  1247. $method = ps('edit_method');
  1248. $changed = array();
  1249. if (!$selected or !is_array($selected))
  1250. {
  1251. return $smd_um_event();
  1252. }
  1253. $names = safe_column('name', 'txp_users', "name IN ('".join("','", doSlash($selected))."') AND name != '".doSlash($txp_user)."'");
  1254. if (!$names) return $smd_um_event();
  1255. switch ($method) {
  1256. case 'delete':
  1257. $assign_assets = ps('assign_assets');
  1258. if ($assign_assets === '') {
  1259. $msg = array('must_reassign_assets', E_ERROR);
  1260. } elseif (in_array($assign_assets, $names)) {
  1261. $msg = array('cannot_assign_assets_to_deletee', E_ERROR);
  1262. } elseif (safe_delete('txp_users', "name IN ('".join("','", doSlash($names))."')")) {
  1263. $changed = $names;
  1264. $assign_assets = doSlash($assign_assets);
  1265. $names = join("','", doSlash($names));
  1266. // delete private prefs
  1267. safe_delete('txp_prefs', "user_name IN ('$names')");
  1268. // assign dangling assets to their new owner
  1269. $reassign = array(
  1270. 'textpattern' => 'AuthorID',
  1271. 'txp_file' => 'author',
  1272. 'txp_image' => 'author',
  1273. 'txp_link' => 'author',
  1274. );
  1275. foreach ($reassign as $table => $col) {
  1276. safe_update($table, "$col='$assign_assets'", "$col IN ('$names')");
  1277. }
  1278. callback_event('authors_deleted', '', 0, $changed);
  1279. $msg = 'author_deleted';
  1280. }
  1281. break;
  1282. case 'changeprivilege':
  1283. $levels = smd_um_get_groups(1);
  1284. $privilege = ps('privs');
  1285. if (!isset($levels[$privilege])) return $smd_um_event();
  1286. if (safe_update('txp_users', 'privs = '.intval($privilege), "name IN ('".join("','", doSlash($names))."')")) {
  1287. $changed = $names;
  1288. $msg = 'author_updated';
  1289. }
  1290. break;
  1291. case 'resetpassword':
  1292. $failed = array();
  1293. $pass_len = get_pref('smd_um_pass_length', $smd_um_prefs['smd_um_pass_length']['default']);
  1294. foreach ($names as $name) {
  1295. $passwd = generate_password($pass_len);
  1296. $hash = doSlash(txp_hash_password($passwd));
  1297. if (safe_update('txp_users', "pass = '$hash'", "name = '".doSlash($name)."'")) {
  1298. $email = safe_field('email', 'txp_users', "name = '".doSlash($name)."'");
  1299. if (send_new_password($passwd, $email, $name)) {
  1300. $changed[] = $name;
  1301. $msg = 'author_updated';
  1302. } else {
  1303. $msg = (array(gTxt('could_not_mail').' '.txpspecialchars($name), E_ERROR));
  1304. }
  1305. }
  1306. }
  1307. break;
  1308. }
  1309. if ($changed) {
  1310. return $smd_um_event(gTxt($msg, array('{name}' => txpspecialchars(join(', ', $changed)))));
  1311. }
  1312. $smd_um_event($msg);
  1313. }
  1314. // ------------------------
  1315. // Mostly cloned from Admin->Users tab
  1316. function smd_um_change_pass_form() {
  1317. global $smd_um_event;
  1318. pagetop(gTxt('smd_um_tab_name').' &raquo; '.gTxt('smd_um_chp_lbl'), '');
  1319. echo form(
  1320. '<div class="txp-edit">'.
  1321. hed(gTxt('change_password'), 2).n.
  1322. inputLabel('new_pass', fInput('password', 'new_pass', '', '', '', '', INPUT_REGULAR, '', 'new_pass'), 'new_password').n.
  1323. graf(checkbox('mail_password', '1', true, '', 'mail_password') .n. '<label for="mail_password">'.gTxt('mail_it').'</label>', ' class="edit-mail-password"').n.
  1324. graf(fInput('submit', 'change_pass', gTxt('submit'), 'publish')).
  1325. eInput($smd_um_event).
  1326. sInput('smd_um_change_pass').
  1327. '</div>'
  1328. , '', '', 'post', '', '', 'change_password');
  1329. }
  1330. // ------------------------
  1331. // Mostly cloned from Admin->Users tab
  1332. function smd_um_change_pass() {
  1333. global $event, $step, $txp_user;
  1334. // Call the Admin side's change_pass routine so other plugins with a vested interest can join the party
  1335. callback_event('admin', 'change_pass');
  1336. extract(psa(array('new_pass', 'mail_password')));
  1337. if (empty($new_pass)) {
  1338. smd_um_dispatcher($event, '', array(gTxt('password_required'), E_ERROR));
  1339. return;
  1340. }
  1341. $hash = doSlash(txp_hash_password($new_pass));
  1342. $rs = safe_update('txp_users', "pass = '$hash'", "name = '".doSlash($txp_user)."'");
  1343. if ($rs) {
  1344. $msg = gTxt('password_changed');
  1345. if ($mail_password) {
  1346. $email = fetch('email', 'txp_users', 'name', $txp_user);
  1347. send_new_password($new_pass, $email, $txp_user);
  1348. $msg .= sp.gTxt('and_mailed_to').sp.$email;
  1349. } else {
  1350. echo comment(mysql_error());
  1351. }
  1352. $msg .= '.';
  1353. } else {
  1354. $msg = array(gTxt('smd_um_pass_change_error'), E_ERROR);
  1355. }
  1356. smd_um_dispatcher($event, '', $msg);
  1357. }
  1358. // ------------------------
  1359. function smd_um_save_pane_state() {
  1360. global $smd_um_event;
  1361. $pane = str_replace('smd_um_priv_', '', gps('pane'));
  1362. $curr = do_list(get_pref('pane_smd_um_priv_visible'));
  1363. $visible = gps('visible');
  1364. if ($visible == 'true') {
  1365. $curr[] = $pane;
  1366. } else {
  1367. $pos = array_search($pane, $curr);
  1368. if ($pos !== false) {
  1369. unset($curr[$pos]);
  1370. }
  1371. }
  1372. $curr = array_unique($curr);
  1373. set_pref("pane_smd_um_priv_visible", (join(',', $curr)), $smd_um_event, PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
  1374. send_xml_response();
  1375. }
  1376. // ****************
  1377. // TABLE MANAGEMENT
  1378. // ****************
  1379. // Add group/priv tables if not already installed
  1380. function smd_um_table_install($showpane='1') {
  1381. global $smd_um_event, $txp_groups, $txp_permissions;
  1382. $GLOBALS['txp_err_count'] = 0;
  1383. $ret = '';
  1384. $sql = array();
  1385. // In truth, this table should be normalised further, but for the sake
  1386. // of one row per priv level per area, it's quicker than a join, and
  1387. // using GROUP_CONCAT() gets the priv table as in admin.config.php
  1388. $sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_UM_PRIVS."` (
  1389. `area` varchar(127) NOT NULL default '',
  1390. `priv` smallint NOT NULL default 0,
  1391. PRIMARY KEY (`area`,`priv`)
  1392. ) ENGINE=MyISAM";
  1393. // id is NOT an auto_increment colummn because autoinc doesn't allow a 0 entry, which we need for 'None'
  1394. $sql[] = "CREATE TABLE IF NOT EXISTS `".PFX.SMD_UM_GROUPS."` (
  1395. `id` smallint(4) NOT NULL default 0,
  1396. `name` varchar(64) NOT NULL default '',
  1397. `core` bool NOT NULL default 0,
  1398. PRIMARY KEY (`id`)
  1399. ) ENGINE=MyISAM PACK_KEYS=1";
  1400. // Handle upgrades: be kind to beta testers
  1401. if (smd_um_table_exist(SMD_UM_PRIVS)) {
  1402. $flds = getThings('SHOW COLUMNS FROM `'.PFX.SMD_UM_PRIVS.'`');
  1403. if (in_array('core',$flds)) {
  1404. $sql[] = "ALTER TABLE `".PFX.SMD_UM_PRIVS."` DROP `core`";
  1405. }
  1406. }
  1407. // Append initial value population to query stack if this is a new install
  1408. if (!smd_um_table_exist(SMD_UM_GROUPS)) {
  1409. foreach ($txp_groups as $id => $name) {
  1410. $sql[] = "INSERT INTO `".PFX.SMD_UM_GROUPS."` VALUES ('$id', '$name', 1)";
  1411. }
  1412. }
  1413. if (!smd_um_table_exist(SMD_UM_PRIVS)) {
  1414. foreach ($txp_permissions as $area => $privs) {
  1415. $priv_list = do_list($privs);
  1416. foreach ($priv_list as $priv) {
  1417. $sql[] = "INSERT INTO `".PFX.SMD_UM_PRIVS."` VALUES ('$area', '$priv')";
  1418. }
  1419. }
  1420. }
  1421. if(gps('debug')) {
  1422. dmp($sql);
  1423. }
  1424. foreach ($sql as $qry) {
  1425. $ret = safe_query($qry);
  1426. if ($ret===false) {
  1427. $GLOBALS['txp_err_count']++;
  1428. echo "<b>".$GLOBALS['txp_err_count'].".</b> ".mysql_error()."<br />\n";
  1429. echo "<!--\n $qry \n-->\n";
  1430. }
  1431. }
  1432. // Spit out results
  1433. if ($GLOBALS['txp_err_count'] == 0) {
  1434. if ($showpane) {
  1435. $msg = gTxt('smd_um_tbl_installed');
  1436. $smd_um_event($msg);
  1437. }
  1438. } else {
  1439. if ($showpane) {
  1440. $msg = gTxt('smd_um_tbl_not_installed');
  1441. $smd_um_event($msg);
  1442. }
  1443. }
  1444. }
  1445. // ------------------------
  1446. // Drop table if in database
  1447. function smd_um_table_remove() {
  1448. global $smd_um_event;
  1449. $ret = '';
  1450. $sql = array();
  1451. $GLOBALS['txp_err_count'] = 0;
  1452. if (smd_um_table_exist()) {
  1453. $sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_UM_PRIVS. "; ";
  1454. $sql[] = "DROP TABLE IF EXISTS " .PFX.SMD_UM_GROUPS. "; ";
  1455. if(gps('debug')) {
  1456. dmp($sql);
  1457. }
  1458. foreach ($sql as $qry) {
  1459. $ret = safe_query($qry);
  1460. if ($ret===false) {
  1461. $GLOBALS['txp_err_count']++;
  1462. echo "<b>".$GLOBALS['txp_err_count'].".</b> ".mysql_error()."<br />\n";
  1463. echo "<!--\n $qry \n-->\n";
  1464. }
  1465. }
  1466. }
  1467. if ($GLOBALS['txp_err_count'] == 0) {
  1468. $msg = gTxt('smd_um_tbl_removed');
  1469. } else {
  1470. $msg = gTxt('smd_um_tbl_not_removed');
  1471. $smd_um_event($msg);
  1472. }
  1473. }
  1474. // ------------------------
  1475. function smd_um_table_exist($which='') {
  1476. static $smd_um_installed = array();
  1477. // The number of expected cols in each table
  1478. $tbls = array(
  1479. SMD_UM_GROUPS => 3,
  1480. SMD_UM_PRIVS => 2,
  1481. );
  1482. if ($which && array_key_exists($which, $tbls) && isset($smd_um_installed[$which])) {
  1483. return ($smd_um_installed[$which] == $tbls[$which]);
  1484. }
  1485. if ($which == '1') {
  1486. $out = count($tbls);
  1487. foreach ($tbls as $tbl => $cols) {
  1488. $num = count(@safe_show('columns', $tbl));
  1489. $smd_um_installed[$tbl] = $num;
  1490. $out -= ($tbls[$tbl] == $num) ? 1 : 0;
  1491. }
  1492. return ($out===0) ? 1 : 0;
  1493. } else if (array_key_exists($which, $tbls)) {
  1494. $num = count(@safe_show('columns', $which));
  1495. $smd_um_installed[$which] = $num;
  1496. return ($smd_um_installed[$which] == $tbls[$which]);
  1497. }
  1498. return false;
  1499. }
  1500. //*****************
  1501. // PUBLIC SIDE TAGS
  1502. // Though we could load all $txp_permissions / $txp_groups to the public side for speed,
  1503. // exposing permissions to the world is not such a hot idea. Therefore the privs are
  1504. // fetched ad-hoc and cached
  1505. function smd_um_has_privs($atts, $thing=NULL) {
  1506. global $txp_user;
  1507. static $smd_um_permissions;
  1508. static $smd_um_groups;
  1509. static $smd_ili = 0;
  1510. extract(lAtts(array(
  1511. 'name' => '',
  1512. 'group' => '',
  1513. 'area' => '',
  1514. 'debug' => 0,
  1515. ),$atts));
  1516. $ret = false;
  1517. $smd_ili = ($smd_ili === 0) ? is_logged_in() : $smd_ili;
  1518. if ($smd_ili) {
  1519. $names = do_list($name);
  1520. $groups = do_list($group);
  1521. $areas = do_list($area);
  1522. // Handle > and < groups
  1523. $grplist = array();
  1524. foreach ($groups as $grp) {
  1525. if ( (strpos($grp, '>') === 0) || (strpos($grp, '<') === 0) ) {
  1526. if (!isset($smd_um_groups)) {
  1527. $smd_um_groups = safe_column('id', SMD_UM_GROUPS, '1=1 ORDER BY id');
  1528. }
  1529. $val = substr($grp, 1);
  1530. // Pull out all groups higher than this one
  1531. if (substr($grp, 0, 1) == '>') {
  1532. foreach($smd_um_groups as $ug) {
  1533. if ($ug > $val) $grplist[] = $ug;
  1534. }
  1535. }
  1536. // Pull out all groups lower than this one
  1537. if (substr($grp, 0, 1) == '<') {
  1538. foreach($smd_um_groups as $ug) {
  1539. if (($ug < $val) && ($ug != '0')) $grplist[] = $ug;
  1540. }
  1541. }
  1542. } else {
  1543. $grplist[] = $grp;
  1544. }
  1545. }
  1546. $groups = array_unique($grplist);
  1547. if ($debug) {
  1548. echo '++ LOGGED IN CREDENTIALS / PERMISSION AREAS / ALL GROUP IDs / NAME ATTR / GROUP ATTR / AREA ATTR ++';
  1549. dmp($smd_ili, $smd_um_permissions, $smd_um_groups, $names, $groups, $areas);
  1550. }
  1551. $isname = ($name && in_array($smd_ili['name'], $names));
  1552. $isgroup = (($group != '') && in_array($smd_ili['privs'], $groups));
  1553. $isarea = false;
  1554. // Build up a cached array of privs by area
  1555. if ($areas) {
  1556. // TODO: would be nice to do this in one query somehow
  1557. foreach ($areas as $place) {
  1558. if (!isset($smd_um_permissions[$place])) {
  1559. $prv = safe_field('GROUP_CONCAT(priv) AS privs', SMD_UM_PRIVS, "area = '" . doSlash($place) . "'");
  1560. $smd_um_permissions[$place] = $prv;
  1561. }
  1562. $isarea = ( $isarea || (in_array($smd_ili['privs'], do_list($smd_um_permissions[$place]))) );
  1563. }
  1564. }
  1565. if ($debug) {
  1566. echo '++ TEST AGAINST NAME / GROUP / AREA ++';
  1567. dmp($isname, $isgroup, $isarea);
  1568. }
  1569. // Compare the current logged in credentials against the relevant passed-in attribute combinations
  1570. if ($name) {
  1571. if ($group) {
  1572. if ($area) {
  1573. if ($debug) dmp('CHECK NAME AND GROUP AND AREA');
  1574. $ret = ($isname && $isgroup && $isarea);
  1575. } else {
  1576. if ($debug) dmp('CHECK NAME AND GROUP');
  1577. $ret = ($isname && $isgroup);
  1578. }
  1579. } else if ($area) {
  1580. if ($debug) dmp('CHECK NAME AND AREA');
  1581. $ret = ($isname && $isarea);
  1582. } else {
  1583. if ($debug) dmp('CHECK NAME');
  1584. $ret = $isname;
  1585. }
  1586. } elseif($group) {
  1587. if ($area) {
  1588. if ($debug) dmp('CHECK GROUP AND AREA');
  1589. $ret = ($isgroup && $isarea);
  1590. } else {
  1591. if ($debug) dmp('CHECK GROUP');
  1592. $ret = $isgroup;
  1593. }
  1594. } elseif($area) {
  1595. if ($debug) dmp('CHECK AREA');
  1596. $ret = $isarea;
  1597. } else {
  1598. if ($debug) dmp('NO CHECKS (ANY USER)');
  1599. $ret = true;
  1600. }
  1601. }
  1602. return parse(EvalElse($thing, $ret));
  1603. }
  1604. // ------------------------
  1605. // Settings for the plugin
  1606. function smd_um_get_prefs() {
  1607. global $prefs;
  1608. $smd_um_prefs = array(
  1609. 'smd_um_hierarchical_groups' => array(
  1610. 'html' => 'yesnoradio',
  1611. 'type' => PREF_HIDDEN,
  1612. 'position' => 10,
  1613. 'default' => '0',
  1614. 'group' => 'smd_um_settings',
  1615. ),
  1616. 'smd_um_admin_group' => array(
  1617. 'html' => 'selectlist',
  1618. 'type' => PREF_HIDDEN,
  1619. 'position' => 20,
  1620. 'content' => array(get_groups(), false),
  1621. 'default' => '1',
  1622. 'group' => 'smd_um_settings',
  1623. ),
  1624. 'smd_um_max_search_limit' => array(
  1625. 'html' => 'text_input',
  1626. 'type' => PREF_HIDDEN,
  1627. 'position' => 30,
  1628. 'default' => '500',
  1629. 'group' => 'smd_um_settings',
  1630. ),
  1631. 'smd_um_pass_length' => array(
  1632. 'html' => 'text_input',
  1633. 'type' => PREF_HIDDEN,
  1634. 'position' => 40,
  1635. 'default' => '12',
  1636. 'group' => 'smd_um_settings',
  1637. ),
  1638. 'smd_um_active_timeout' => array(
  1639. 'html' => 'text_input',
  1640. 'type' => PREF_HIDDEN,
  1641. 'position' => 50,
  1642. 'default' => '60',
  1643. 'group' => 'smd_um_settings',
  1644. ),
  1645. 'smd_um_self_alter' => array(
  1646. 'html' => 'yesnoradio',
  1647. 'type' => PREF_HIDDEN,
  1648. 'position' => 60,
  1649. 'default' => '0',
  1650. 'group' => 'smd_um_settings',
  1651. ),
  1652. );
  1653. return $smd_um_prefs;
  1654. }
  1655. # --- END PLUGIN CODE ---
  1656. if (0) {
  1657. ?>
  1658. <!--
  1659. # --- BEGIN PLUGIN HELP ---
  1660. h1. smd_user_manager
  1661. Complete user / group / privs management. Features:
  1662. * Replaces _Admin->Users_ tab
  1663. * Add / edit / list users, with content counts alongside each user
  1664. * Search, sort, or filter the users (standard Txp pagination result depths apply)
  1665. * Quickly find accounts with certain characteristics (e.g. self-registered spam accounts with 0 articles)
  1666. * Perform multi-edits: change privilege / reset pass / delete
  1667. * All users can edit their own details and change their password
  1668. * Create new user groups (a.k.a. roles) if the default six aren't enough
  1669. * Rename existing groups to more suitable names (you cannot delete them)
  1670. * Modify Txp's standard priv areas to alter what each user group can see/do
  1671. * Add new priv areas (useful for custom code to save doing it in a plugin)
  1672. * A "who's online" indicator
  1673. * Integrates with smd_bio (v0.40+) and smd_prognostics (v0.20+)
  1674. h2. Installation / uninstallation
  1675. p(important). Requires Txp 4.5.0+
  1676. Download the plugin from either "textpattern.org":http://textpattern.org/plugins/1229/smd_user_manager, or the "software page":http://stefdawson.com/sw, paste the code into the Txp _Admin->Plugins_ pane, install and enable the plugin. The tables will be installed and populated automatically unless you use the plugin from the cache directory; in either case, visiting the _Admin->User manager_ tab will install and populate them.
  1677. To uninstall the plugin, first assign all your users to groups in Txp's first six, then delete the plugin from the _Admin->Plugins_ page. The tables will be deleted automatically. If you do not reassign users to those default groups, you may have users with 'dangling' (i.e. no) privs. The outcome of what happens when such users log in is thus undefined: at the very least you'll get admin-side errors thrown.
  1678. Visit the "forum thread":http://forum.textpattern.com/viewtopic.php?id=36558 for more info or to report on the success or otherwise of the plugin.
  1679. h2. Admin side overview
  1680. Visit the _Admin->User manager_ tab. The default view (for admins) is a list of users. There are buttons at the top of the screen: __Change password__, "Users":#smd_um_users, "Groups":#smd_um_groups, "Privs":#smd_um_privs, and "Prefs":#smd_um_prefs. Each of those displays an area for the management of that component.
  1681. Please note: The plugin tries to remove the _Admin->Users_ menu, but depending on your installed theme this may not be possible. If the menu item remains, it will perform the same function as clicking the _User manager_ tab.
  1682. h2(#smd_um_users). User management (_Users_ button)
  1683. View / search the installed user base. Change the number of users you wish to see per page by using the 'per page' dropdown below the list. The columns on display are:
  1684. * Login -- user name
  1685. * Real Name -- full name
  1686. * E-mail -- e-mail address
  1687. * Privileges -- user group
  1688. * Last Login -- month & year of last access to the Textpattern admin side
  1689. * Articles -- number of articles authored by this user
  1690. * Images -- number of images uploaded by this user
  1691. * Files -- number of files uploaded by this user
  1692. * Links -- number of links created by this user
  1693. Click _New user_ (admins only) to create a new user account and automatically send a randomly-generated password to the nominated e-mail address.
  1694. Click a login name (privileges required) to edit the details about that user. If you have any smd_bio fields set up, they can also be edited (and hovering over the login name will retrieve some extended biographical info).
  1695. Click an e-mail address to launch your e-mail software to send a message to the user.
  1696. Click an article / image / file / link value to see the content owned by that user.
  1697. You may sort the columns by clicking the headings. Click once to sort in ascending order, click again to sort in descending order. An arrow shows the sort direction. Your sort column and direction are remembered next time you visit the panel.
  1698. You may also search users by login name, real name, e-mail address, privilege level (number or name), or content count. A few notes about searching:
  1699. * When searching privileges by name, only the _first matching_ group will be returned -- the privs are searched in order of their ID.
  1700. * You may search privileges and content counts by specifying an exact value, for example Article count: 0 shows all accounts with no articles (perhaps a self-registered spammer or malicious user?)
  1701. * You may also search these fields using greater-than or less-than symbols to find users with the matching number of assets. For example, Image count: @>=50@ shows all users with 50 images or more.
  1702. You can also perform multi-edits by selecting the rows to alter via the checkboxes and using the dropdown immediately below the list to make mass changes. The options are:
  1703. * Change privileges: set all selected users to the given privilege level. When you choose this option, a further dropdown appears to allow you to choose which level to apply.
  1704. * Reset password: send a new password to all selected users.
  1705. * Delete: remove all selected user accounts. A further dropdown appears with a list of user accounts in it: choose one to transfer over any content that belonged to the deleted user(s).
  1706. A common use is to find any users with 0 articles, and then use the multi-edit tool to delete them all.
  1707. h2(#smd_um_groups). Group management (_Groups_ button)
  1708. This panel allows you to alter the names of Txp's built-in 6 groups to suit your application. It changes the Titles in the currently installed language only.
  1709. You may also add a new group using the input controls and accompanying _Create_ button at the top of the panel. If you choose to only enter a Title, a name will be automatically generated (lower case, with most non-ASCII characters replaced by safe characters). Alternatively, you can type your own name, though please stick to 'simple' letters and numbers to make things easy on yourself later. If you choose an existing group from the _based on_ dropdown, the privs for that group will be copied across to your new group.
  1710. Custom groups can be deleted using the 'x' button alongside each. Any users that were assigned to that group will have their privileges reduced to 'None'. Either visit the _Users_ panel and alter their priv level to something suitable, or set their level to something else prior to deleting the group.
  1711. If any group contains at least one user, the group ID values are hyperlinked back to the Users panel to show only those users in that group. This is handy for reassigning priv levels before deleting a group. If you hover over the priv ID, a tooltip will display the number of users assigned to that privilege group.
  1712. h2(#smd_um_privs). Priv management (_Privs_ button)
  1713. From this panel you may alter which user groups can access which parts of the admin side interface or perform certain types of action. You will see a list of 'area' headings. Click one to expand it and see the privileges it contains; click it again to collapse it. The open/closed state of the areas is remembered next time you visit the panel.
  1714. Each area has a row of checkboxes alongside it. If a checkbox is ticked in a column corresponding to a privilege group, that group has access to that feature of the interface. You may alter who sees what by changing the tick boxes and hitting one of the _Save_ buttons that appear to the left of each area heading; all the buttons do the same thing: they save the entire list of privileges. You must confirm the action, because the change is immediate.
  1715. You may change checkboxes in batches by selecting rows/columns and then using a keyboard shortcut to make changes to all highlighted checkboxes. Firstly, choose which checkboxes to apply your changes:
  1716. # click a column heading to highlight that entire Group
  1717. # click a row heading to highlight that entire Priv set
  1718. # click the top left-hand corner of the collapsible table to select all checkboxes
  1719. You can select multiple columns/rows from multiple areas if you wish. Clicking a row/column/corner heading again will toggle its status.
  1720. When you are ready to make changes to the selected checkboxes, you can use the following keys:
  1721. * @c@ to check all selected boxes
  1722. * @u@ to uncheck all selected boxes
  1723. * @t@ to toggle the status of all selected boxes (checked become unchecked, and vice versa)
  1724. * @d@ deselects all rows / columns you have highlighted
  1725. The area headings merely group the areas for convenience and make the page look less scary! Any plugins you have installed will be shown under an area heading of the author's three-letter prefix. Note that altering privs here overrides the privs as set by the plugin so you can customise who sees what, _as long as the plugin in question is loaded before smd_user_manager_. Check the plugin load order values if things aren't working as you expect.
  1726. The 'tab' area heading governs which user groups have access to the primary navigation areas (content, presentation, admin, extensions and any custom tabs such as those created by smd_tabber). Note that giving access to these tabs does not automatically give access to the secondary navigation elements; you must turn those on independently. By contrast, giving a user group access to a secondary navigation item _does_ allow them to use that feature but they won't be able to navigate to it using the primary navigation button unless they have also been given access to that tab.
  1727. For example, if you grant the 'page' privilege to Freelancers but don't give them access to the Presentation tab, they could type @?event=page@ in the URL to get to the Pages panel but they would have no means of navigating there by clicking the mouse. Since the primary 'Presentation' tab is missing, the secondary tabs are all missing too, even though some of them may be permitted for that group of users.
  1728. If you wish to add your own priv area, type a new one in the box at the top of the panel and hit _Create_. Your area will be created under the appropriate heading, e.g. if you specified @smd_test@ it would appear under the @smd@ heading, or if you created a @file.trusted@ privilege area, it would appear under the @file@ heading. Core areas are normally specified by a dotted notation but you are free to choose any notation that makes sense to your application.
  1729. Note that:
  1730. # some plugins may not show up in the list due to factors such as plugin load order or the mechanism by which the plugin is written.
  1731. # the @[R]@ column is a special Reset indicator. Checking any row in this column will ignore any privilege checkboxes you may have set or altered and will reset the corresponding privs to their defaults after you click Save.
  1732. # by default you may not add @smd_um@ privs to override the functionality if this plugin, although you can adjust who can edit what so be careful not to open the permissions too widely. A preference setting governs whether smd_um can have its own privs altered.
  1733. # you can create priv areas that may be tested from the public side using the "smd_um_has_privs":#smd_um_has_privs tag.
  1734. h2(#smd_um_prefs). Preference management (_Prefs_ button)
  1735. Administrators can set global preferences that govern the behaviour of the plugin:
  1736. ; *Assume hierarchical groups*
  1737. : Textpattern does not normally have the notion of escalating privilege levels. A Copy Editor is not _greater than_ a Staff Writer, for example, they just have different permissions to fulfil the relevant role.
  1738. : You may set this preference to _yes_ to force Textpattern to treat your levels as a hierarchy, i.e. lower numbers are "more powerful" than higher numbers (with the exception of 0: None which is always 'no privs').
  1739. : Once set, logged-in users may not create or edit any other users that are 'above' their assigned privs, e.g. Copy Editors cannot modify Publisher or Managing Editor accounts. Further, it is not possible to alter your own privileges, nor can you create a user with a greater priv level than you possess.
  1740. : Default: no
  1741. ; *Protected administrator group*
  1742. : Nominate a group that you wish only administrators to be able to alter/create.
  1743. : Once set, the nominated group is off-limits to all users apart from those already in the selected group. In other words, non-admin users cannot create, modify, alter (or mass-alter) any privilege setting using this group.
  1744. : Can be used in tandem with or independently of the _Assume hierarchical groups_ setting.
  1745. : Default: 1 (a.k.a. Publishers)
  1746. ; *Maximum user search result limit*
  1747. : Maximum number of search results to return when filtering the User list.
  1748. : Default: 500
  1749. ; *Password length (characters)*
  1750. : The number of characters in newly-generated passwords. The higher the better (up to a point).
  1751. : Default: 12
  1752. ; *Activity timeout (seconds)*
  1753. : Number of seconds within which someone has to perform an admin-side action to remain visible on the Active Users list. For high activity sites, you can lower this value and receive a more responsive (accurate) list, at the possible expense of more perceived fluctuations in user activity when the timeout is exceeded. Lengthening the value will keep online users in the list longer, even though they may not actually still be active.
  1754. : Default: 60
  1755. ; *Allow smd_um privs to be altered*
  1756. : Set this to _yes_ to allow smd_um privs to be changed from the Privs panel.
  1757. : Default: no
  1758. h2(#smd_um_non_admin). Non-admin users
  1759. User accounts without privileges to list users will automatically be shown the Edit screen for their own user account. This is the only account that can be edited. If smd_bio is installed, additional biographical information may also be edited.
  1760. Users may also change their password using the button at the top of the screen.
  1761. h2(#smd_um_active). Active users
  1762. At the bottom of each smd_user_manager panel is a list of currently active ("online") users. Each user is hyperlinked to the "Users":#smd_um_users panel to show you the info about just that person, which is a convenient shortcut to find out about a logged-in author.
  1763. An active user is one who has performed some admin-side action in the past N seconds, where N is defined in the "Activity Timeout plugin preference":#smd_um_prefs.
  1764. h2. Public side tags
  1765. h3(#smd_um_has_privs). Tag: @<txp:smd_um_has_privs>@
  1766. Use this conditional tag to check if the currently logged-in user has permissions to perform some action. If they have, the contained content is executed. Supports @<txp:else />@.
  1767. Note that the plugin does not have a public-side logging in/out facility, it merely allows you to test whether someone who has logged in via the admin side (or via a third party login plugin) has necessary privileges.
  1768. Attributes:
  1769. ; %name%
  1770. : Check the current logged-in user's name against this list of names.
  1771. : IMPORTANT: Case sensitive.
  1772. ; %group%
  1773. : Check the current logged-in user's group (privilege) level is in this list of numbers.
  1774. : You can specify @>@ and @<@ symbols before any value to indicate you wish to check if the user has privileges higher or lower than a given number, respectively.
  1775. : If checking priv values less than a number, 0 (a.k.a None) is _never_ included: you must add it to the list explicitly if you wish to test for it.
  1776. : Example: @group="11, <4"@ means "does the user have privs of 11, 1, 2, or 3?"
  1777. ; %area%
  1778. : Check the current logged-in user has the ability to access one of the given areas in this list.
  1779. : Example: @area="sports_hall, chemistry_lab"@ would only perform the contained content if the logged-in user's priv level was present in either the sports_hall or chemistry_lab priv areas.
  1780. Notes:
  1781. * You can combine the attributes in any way: the contained content will only be executed if the logged-in user matches *all* the criteria.
  1782. * Without any attributes, the contained content will be executed if anybody is logged in, regardless of their name, privs or assigned areas.
  1783. * Again: login @name@s are case sensitive
  1784. h2. Author / credits
  1785. Written by "Stef Dawson":http://stefdawson.com/contact. Thanks to the beta test team jakob, mrdale, alanfluff, maverick, Destry, redbot, and rsilletti for their willingness to let my code loose on their servers.
  1786. h2. Changelog
  1787. * 27 Jul 2011 | v0.10 | Initial public release
  1788. * 03 Nov 2011 | v0.11 | Added smd_um_has_privs tag to check privs/areas ; added preference to allow editing of smd_um privs; fixed handling of users with privs=None
  1789. * 25 Jan 2012 | v0.12 | Fixed smd_um_has_privs multiple area check ; fixed prefs options from Plugins pane
  1790. * 25 Apr 2013 | v0.20 | Updated for Txp 4.5.0+
  1791. # --- END PLUGIN HELP ---
  1792. -->
  1793. <?php
  1794. }
  1795. ?>