users.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /**
  2. * Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
  3. * This file is licensed under the Affero General Public License version 3 or later.
  4. * See the COPYING-README file.
  5. */
  6. function setQuota (uid, quota, ready) {
  7. $.post(
  8. OC.filePath('settings', 'ajax', 'setquota.php'),
  9. {username: uid, quota: quota},
  10. function (result) {
  11. if (ready) {
  12. ready(result.data.quota);
  13. }
  14. }
  15. );
  16. }
  17. var UserList = {
  18. useUndo: true,
  19. availableGroups: [],
  20. offset: 30, //The first 30 users are there. No prob, if less in total.
  21. //hardcoded in settings/users.php
  22. usersToLoad: 10, //So many users will be loaded when user scrolls down
  23. /**
  24. * @brief Initiate user deletion process in UI
  25. * @param string uid the user ID to be deleted
  26. *
  27. * Does not actually delete the user; it sets them for
  28. * deletion when the current page is unloaded, at which point
  29. * finishDelete() completes the process. This allows for 'undo'.
  30. */
  31. do_delete: function (uid) {
  32. if (typeof UserList.deleteUid !== 'undefined') {
  33. //Already a user in the undo queue
  34. UserList.finishDelete(null);
  35. }
  36. UserList.deleteUid = uid;
  37. // Set undo flag
  38. UserList.deleteCanceled = false;
  39. // Provide user with option to undo
  40. $('#notification').data('deleteuser', true);
  41. OC.Notification.showHtml(t('settings', 'deleted') + ' ' + escapeHTML(uid) + '<span class="undo">' + t('settings', 'undo') + '</span>');
  42. },
  43. /**
  44. * @brief Delete a user via ajax
  45. * @param bool ready whether to use ready() upon completion
  46. *
  47. * Executes deletion via ajax of user identified by property deleteUid
  48. * if 'undo' has not been used. Completes the user deletion procedure
  49. * and reflects success in UI.
  50. */
  51. finishDelete: function (ready) {
  52. // Check deletion has not been undone
  53. if (!UserList.deleteCanceled && UserList.deleteUid) {
  54. // Delete user via ajax
  55. $.ajax({
  56. type: 'POST',
  57. url: OC.filePath('settings', 'ajax', 'removeuser.php'),
  58. async: false,
  59. data: { username: UserList.deleteUid },
  60. success: function (result) {
  61. if (result.status === 'success') {
  62. // Remove undo option, & remove user from table
  63. OC.Notification.hide();
  64. $('tr').filterAttr('data-uid', UserList.deleteUid).remove();
  65. UserList.deleteCanceled = true;
  66. if (ready) {
  67. ready();
  68. }
  69. } else {
  70. OC.dialogs.alert(result.data.message, t('settings', 'Unable to remove user'));
  71. }
  72. }
  73. });
  74. }
  75. },
  76. add: function (username, displayname, groups, subadmin, quota, sort) {
  77. var tr = $('tbody tr').first().clone();
  78. var subadminsEl;
  79. var subadminSelect;
  80. var groupsSelect;
  81. if (tr.find('div.avatardiv').length){
  82. $('div.avatardiv', tr).avatar(username, 32);
  83. }
  84. tr.attr('data-uid', username);
  85. tr.attr('data-displayName', displayname);
  86. tr.find('td.name').text(username);
  87. tr.find('td.displayName > span').text(displayname);
  88. // make them look like the multiselect buttons
  89. // until they get time to really get initialized
  90. groupsSelect = $('<select multiple="multiple" class="groupsselect multiselect button" data-placehoder="Groups" title="' + t('settings', 'Groups') + '"></select>')
  91. .attr('data-username', username)
  92. .data('user-groups', groups);
  93. if (tr.find('td.subadmins').length > 0) {
  94. subadminSelect = $('<select multiple="multiple" class="subadminsselect multiselect button" data-placehoder="subadmins" title="' + t('settings', 'Group Admin') + '">')
  95. .attr('data-username', username)
  96. .data('user-groups', groups)
  97. .data('subadmin', subadmin);
  98. tr.find('td.subadmins').empty();
  99. }
  100. $.each(this.availableGroups, function (i, group) {
  101. groupsSelect.append($('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>'));
  102. if (typeof subadminSelect !== 'undefined' && group !== 'admin') {
  103. subadminSelect.append($('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>'));
  104. }
  105. });
  106. tr.find('td.groups').empty().append(groupsSelect);
  107. subadminsEl = tr.find('td.subadmins');
  108. if (subadminsEl.length > 0) {
  109. subadminsEl.append(subadminSelect);
  110. }
  111. if (tr.find('td.remove img').length === 0 && OC.currentUser !== username) {
  112. var rm_img = $('<img class="svg action">').attr({
  113. src: OC.imagePath('core', 'actions/delete')
  114. });
  115. var rm_link = $('<a class="action delete">')
  116. .attr({ href: '#', 'original-title': t('settings', 'Delete')})
  117. .append(rm_img);
  118. tr.find('td.remove').append(rm_link);
  119. } else if (OC.currentUser === username) {
  120. tr.find('td.remove a').remove();
  121. }
  122. var quotaSelect = tr.find('select.quota-user');
  123. if (quota === 'default') {
  124. quotaSelect.find('option').attr('selected', null);
  125. quotaSelect.find('option').first().attr('selected', 'selected');
  126. quotaSelect.data('previous', 'default');
  127. } else {
  128. if (quotaSelect.find('option[value="' + quota + '"]').length > 0) {
  129. quotaSelect.find('option[value="' + quota + '"]').attr('selected', 'selected');
  130. } else {
  131. quotaSelect.append('<option value="' + escapeHTML(quota) + '" selected="selected">' + escapeHTML(quota) + '</option>');
  132. }
  133. }
  134. $(tr).appendTo('tbody');
  135. if (sort) {
  136. UserList.doSort();
  137. }
  138. quotaSelect.on('change', function () {
  139. var uid = $(this).parent().parent().attr('data-uid');
  140. var quota = $(this).val();
  141. setQuota(uid, quota, function(returnedQuota){
  142. if (quota !== returnedQuota) {
  143. $(quotaSelect).find(':selected').text(returnedQuota);
  144. }
  145. });
  146. });
  147. // defer init so the user first sees the list appear more quickly
  148. window.setTimeout(function(){
  149. quotaSelect.singleSelect();
  150. UserList.applyMultiplySelect(groupsSelect);
  151. if (subadminSelect) {
  152. UserList.applyMultiplySelect(subadminSelect);
  153. }
  154. }, 0);
  155. return tr;
  156. },
  157. // From http://my.opera.com/GreyWyvern/blog/show.dml/1671288
  158. alphanum: function(a, b) {
  159. function chunkify(t) {
  160. var tz = [], x = 0, y = -1, n = 0, i, j;
  161. while (i = (j = t.charAt(x++)).charCodeAt(0)) {
  162. var m = (i === 46 || (i >=48 && i <= 57));
  163. if (m !== n) {
  164. tz[++y] = "";
  165. n = m;
  166. }
  167. tz[y] += j;
  168. }
  169. return tz;
  170. }
  171. var aa = chunkify(a.toLowerCase());
  172. var bb = chunkify(b.toLowerCase());
  173. for (x = 0; aa[x] && bb[x]; x++) {
  174. if (aa[x] !== bb[x]) {
  175. var c = Number(aa[x]), d = Number(bb[x]);
  176. if (c === aa[x] && d === bb[x]) {
  177. return c - d;
  178. } else {
  179. return (aa[x] > bb[x]) ? 1 : -1;
  180. }
  181. }
  182. }
  183. return aa.length - bb.length;
  184. },
  185. doSort: function() {
  186. var self = this;
  187. var rows = $('tbody tr').get();
  188. rows.sort(function(a, b) {
  189. return UserList.alphanum($(a).find('td.name').text(), $(b).find('td.name').text());
  190. });
  191. var items = [];
  192. $.each(rows, function(index, row) {
  193. items.push(row);
  194. if(items.length === 100) {
  195. $('tbody').append(items);
  196. items = [];
  197. }
  198. });
  199. if(items.length > 0) {
  200. $('tbody').append(items);
  201. }
  202. },
  203. update: function () {
  204. if (UserList.updating) {
  205. return;
  206. }
  207. $('table+.loading').css('visibility', 'visible');
  208. UserList.updating = true;
  209. var query = $.param({ offset: UserList.offset, limit: UserList.usersToLoad });
  210. $.get(OC.generateUrl('/settings/ajax/userlist') + '?' + query, function (result) {
  211. var loadedUsers = 0;
  212. var trs = [];
  213. if (result.status === 'success') {
  214. //The offset does not mirror the amount of users available,
  215. //because it is backend-dependent. For correct retrieval,
  216. //always the limit(requested amount of users) needs to be added.
  217. $.each(result.data, function (index, user) {
  218. if($('tr[data-uid="' + user.name + '"]').length > 0) {
  219. return true;
  220. }
  221. var tr = UserList.add(user.name, user.displayname, user.groups, user.subadmin, user.quota, false);
  222. tr.addClass('appear transparent');
  223. trs.push(tr);
  224. loadedUsers++;
  225. });
  226. if (result.data.length > 0) {
  227. UserList.doSort();
  228. $('table+.loading').css('visibility', 'hidden');
  229. }
  230. else {
  231. UserList.noMoreEntries = true;
  232. $('table+.loading').remove();
  233. }
  234. UserList.offset += loadedUsers;
  235. // animate
  236. setTimeout(function() {
  237. for (var i = 0; i < trs.length; i++) {
  238. trs[i].removeClass('transparent');
  239. }
  240. }, 0);
  241. }
  242. UserList.updating = false;
  243. });
  244. },
  245. applyMultiplySelect: function (element) {
  246. var checked = [];
  247. var user = element.attr('data-username');
  248. if ($(element).hasClass('groupsselect')) {
  249. if (element.data('userGroups')) {
  250. checked = element.data('userGroups');
  251. }
  252. if (user) {
  253. var checkHandeler = function (group) {
  254. if (user === OC.currentUser && group === 'admin') {
  255. return false;
  256. }
  257. if (!oc_isadmin && checked.length === 1 && checked[0] === group) {
  258. return false;
  259. }
  260. $.post(
  261. OC.filePath('settings', 'ajax', 'togglegroups.php'),
  262. {
  263. username: user,
  264. group: group
  265. },
  266. function (response) {
  267. if(response.status === 'success'
  268. && UserList.availableGroups.indexOf(response.data.groupname) === -1
  269. && response.data.action === 'add') {
  270. UserList.availableGroups.push(response.data.groupname);
  271. }
  272. if(response.data.message) {
  273. OC.Notification.show(response.data.message);
  274. }
  275. }
  276. );
  277. };
  278. } else {
  279. checkHandeler = false;
  280. }
  281. var addGroup = function (select, group) {
  282. $('select[multiple]').each(function (index, element) {
  283. if ($(element).find('option[value="' + group + '"]').length === 0 && select.data('msid') !== $(element).data('msid')) {
  284. $(element).append('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>');
  285. }
  286. });
  287. };
  288. var label;
  289. if (oc_isadmin) {
  290. label = t('settings', 'add group');
  291. } else {
  292. label = null;
  293. }
  294. element.multiSelect({
  295. createCallback: addGroup,
  296. createText: label,
  297. selectedFirst: true,
  298. checked: checked,
  299. oncheck: checkHandeler,
  300. onuncheck: checkHandeler,
  301. minWidth: 100
  302. });
  303. }
  304. if ($(element).hasClass('subadminsselect')) {
  305. if (element.data('subadmin')) {
  306. checked = element.data('subadmin');
  307. }
  308. var checkHandeler = function (group) {
  309. if (group === 'admin') {
  310. return false;
  311. }
  312. $.post(
  313. OC.filePath('settings', 'ajax', 'togglesubadmins.php'),
  314. {
  315. username: user,
  316. group: group
  317. },
  318. function () {
  319. }
  320. );
  321. };
  322. var addSubAdmin = function (group) {
  323. $('select[multiple]').each(function (index, element) {
  324. if ($(element).find('option[value="' + group + '"]').length === 0) {
  325. $(element).append('<option value="' + escapeHTML(group) + '">' + escapeHTML(group) + '</option>');
  326. }
  327. });
  328. };
  329. element.multiSelect({
  330. createCallback: addSubAdmin,
  331. createText: null,
  332. checked: checked,
  333. oncheck: checkHandeler,
  334. onuncheck: checkHandeler,
  335. minWidth: 100
  336. });
  337. }
  338. },
  339. _onScroll: function(e) {
  340. if (!!UserList.noMoreEntries) {
  341. return;
  342. }
  343. if ($(window).scrollTop() + $(window).height() > $(document).height() - 500) {
  344. UserList.update(true);
  345. }
  346. },
  347. };
  348. $(document).ready(function () {
  349. UserList.doSort();
  350. UserList.availableGroups = $('#content table').data('groups');
  351. $(window).scroll(function(e) {UserList._onScroll(e);});
  352. $('table').after($('<div class="loading" style="height: 200px; visibility: hidden;"></div>'));
  353. $('select[multiple]').each(function (index, element) {
  354. UserList.applyMultiplySelect($(element));
  355. });
  356. $('table').on('click', 'td.remove>a', function (event) {
  357. var row = $(this).parent().parent();
  358. var uid = $(row).attr('data-uid');
  359. $(row).hide();
  360. // Call function for handling delete/undo
  361. UserList.do_delete(uid);
  362. });
  363. $('table').on('click', 'td.password>img', function (event) {
  364. event.stopPropagation();
  365. var img = $(this);
  366. var uid = img.parent().parent().attr('data-uid');
  367. var input = $('<input type="password">');
  368. img.css('display', 'none');
  369. img.parent().children('span').replaceWith(input);
  370. input.focus();
  371. input.keypress(function (event) {
  372. if (event.keyCode === 13) {
  373. if ($(this).val().length > 0) {
  374. var recoveryPasswordVal = $('input:password[id="recoveryPassword"]').val();
  375. $.post(
  376. OC.generateUrl('/settings/users/changepassword'),
  377. {username: uid, password: $(this).val(), recoveryPassword: recoveryPasswordVal},
  378. function (result) {
  379. if (result.status != 'success') {
  380. OC.Notification.show(t('admin', result.data.message));
  381. }
  382. }
  383. );
  384. input.blur();
  385. } else {
  386. input.blur();
  387. }
  388. }
  389. });
  390. input.blur(function () {
  391. $(this).replaceWith($('<span>●●●●●●●</span>'));
  392. img.css('display', '');
  393. });
  394. });
  395. $('input:password[id="recoveryPassword"]').keyup(function(event) {
  396. OC.Notification.hide();
  397. });
  398. $('table').on('click', 'td.password', function (event) {
  399. $(this).children('img').click();
  400. });
  401. $('table').on('click', 'td.displayName>img', function (event) {
  402. event.stopPropagation();
  403. var img = $(this);
  404. var uid = img.parent().parent().attr('data-uid');
  405. var displayName = escapeHTML(img.parent().parent().attr('data-displayName'));
  406. var input = $('<input type="text" value="' + displayName + '">');
  407. img.css('display', 'none');
  408. img.parent().children('span').replaceWith(input);
  409. input.focus();
  410. input.keypress(function (event) {
  411. if (event.keyCode === 13) {
  412. if ($(this).val().length > 0) {
  413. $.post(
  414. OC.filePath('settings', 'ajax', 'changedisplayname.php'),
  415. {username: uid, displayName: $(this).val()},
  416. function (result) {
  417. if (result && result.status==='success'){
  418. img.parent().parent().find('div.avatardiv').avatar(result.data.username, 32);
  419. }
  420. }
  421. );
  422. input.blur();
  423. } else {
  424. input.blur();
  425. }
  426. }
  427. });
  428. input.blur(function () {
  429. var input = $(this),
  430. displayName = input.val();
  431. input.closest('tr').attr('data-displayName', displayName);
  432. input.replaceWith('<span>' + escapeHTML(displayName) + '</span>');
  433. img.css('display', '');
  434. });
  435. });
  436. $('table').on('click', 'td.displayName', function (event) {
  437. $(this).children('img').click();
  438. });
  439. $('select.quota, select.quota-user').singleSelect().on('change', function () {
  440. var select = $(this);
  441. var uid = $(this).parent().parent().attr('data-uid');
  442. var quota = $(this).val();
  443. setQuota(uid, quota, function(returnedQuota){
  444. if (quota !== returnedQuota) {
  445. select.find(':selected').text(returnedQuota);
  446. }
  447. });
  448. });
  449. $('#newuser').submit(function (event) {
  450. event.preventDefault();
  451. var username = $('#newusername').val();
  452. var password = $('#newuserpassword').val();
  453. if ($.trim(username) === '') {
  454. OC.dialogs.alert(
  455. t('settings', 'A valid username must be provided'),
  456. t('settings', 'Error creating user'));
  457. return false;
  458. }
  459. if ($.trim(password) === '') {
  460. OC.dialogs.alert(
  461. t('settings', 'A valid password must be provided'),
  462. t('settings', 'Error creating user'));
  463. return false;
  464. }
  465. var groups = $('#newusergroups').prev().children('div').data('settings').checked;
  466. $('#newuser').get(0).reset();
  467. $.post(
  468. OC.filePath('settings', 'ajax', 'createuser.php'),
  469. {
  470. username: username,
  471. password: password,
  472. groups: groups
  473. },
  474. function (result) {
  475. if (result.status !== 'success') {
  476. OC.dialogs.alert(result.data.message,
  477. t('settings', 'Error creating user'));
  478. } else {
  479. if (result.data.groups) {
  480. var addedGroups = result.data.groups;
  481. UserList.availableGroups = $.unique($.merge(UserList.availableGroups, addedGroups));
  482. }
  483. if (result.data.homeExists){
  484. OC.Notification.hide();
  485. OC.Notification.show(t('settings', 'Warning: Home directory for user "{user}" already exists', {user: result.data.username}));
  486. if (UserList.notificationTimeout){
  487. window.clearTimeout(UserList.notificationTimeout);
  488. }
  489. UserList.notificationTimeout = window.setTimeout(
  490. function(){
  491. OC.Notification.hide();
  492. UserList.notificationTimeout = null;
  493. }, 10000);
  494. }
  495. if($('tr[data-uid="' + username + '"]').length === 0) {
  496. UserList.add(username, username, result.data.groups, null, 'default', true);
  497. }
  498. }
  499. }
  500. );
  501. });
  502. // Handle undo notifications
  503. OC.Notification.hide();
  504. $('#notification').on('click', '.undo', function () {
  505. if ($('#notification').data('deleteuser')) {
  506. $('tbody tr').filterAttr('data-uid', UserList.deleteUid).show();
  507. UserList.deleteCanceled = true;
  508. }
  509. OC.Notification.hide();
  510. });
  511. UserList.useUndo = ('onbeforeunload' in window);
  512. $(window).bind('beforeunload', function () {
  513. UserList.finishDelete(null);
  514. });
  515. });