PageRenderTime 89ms CodeModel.GetById 45ms RepoModel.GetById 1ms app.codeStats 1ms

/~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

Large files files are truncated, but you can click here to view the full 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='" .

Large files files are truncated, but you can click here to view the full file