001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/contrib/ufp/usermembership/trunk/tool/src/java/org/sakaiproject/umem/tool/ui/SiteListBean.java $
003: * $Id: SiteListBean.java 4381 2007-03-21 11:25:54Z nuno@ufp.pt $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006, 2007 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.umem.tool.ui;
021:
022: import java.io.Serializable;
023: import java.sql.Connection;
024: import java.sql.PreparedStatement;
025: import java.sql.ResultSet;
026: import java.sql.SQLException;
027: import java.text.Collator;
028: import java.util.ArrayList;
029: import java.util.Collections;
030: import java.util.Comparator;
031: import java.util.Date;
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.Map;
035:
036: import javax.faces.application.FacesMessage;
037: import javax.faces.context.ExternalContext;
038: import javax.faces.context.FacesContext;
039: import javax.faces.event.ActionEvent;
040:
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043: import org.sakaiproject.authz.api.Role;
044: import org.sakaiproject.component.cover.ComponentManager;
045: import org.sakaiproject.component.cover.ServerConfigurationService;
046: import org.sakaiproject.db.api.SqlService;
047: import org.sakaiproject.exception.IdUnusedException;
048: import org.sakaiproject.site.api.Group;
049: import org.sakaiproject.site.api.Site;
050: import org.sakaiproject.site.api.SiteService;
051: import org.sakaiproject.site.api.SiteService.SelectionType;
052: import org.sakaiproject.site.api.SiteService.SortType;
053: import org.sakaiproject.tool.api.Session;
054: import org.sakaiproject.tool.api.SessionManager;
055: import org.sakaiproject.tool.api.ToolManager;
056: import org.sakaiproject.umem.api.Authz;
057: import org.sakaiproject.user.api.UserNotDefinedException;
058: import org.sakaiproject.user.cover.UserDirectoryService;
059: import org.sakaiproject.util.ResourceLoader;
060:
061: /**
062: * @author <a href="mailto:nuno@ufp.pt">Nuno Fernandes</a>
063: */
064: public class SiteListBean {
065: private static final long serialVersionUID = 2L;
066: private static final String SORT_SITE_NAME = "siteName";
067: private static final String SORT_GROUPS_TYPE = "groups";
068: private static final String SORT_SITE_TYPE = "siteType";
069: private static final String SORT_SITE_RID = "roleId";
070: private static final String SORT_SITE_PV = "published";
071: private static final String SORT_USER_STATUS = "userStatus";
072: private static final String SORT_SITE_TERM = "siteTerm";
073: /** Our log (commons). */
074: private static Log LOG = LogFactory.getLog(SiteListBean.class);
075: /** Resource bundle */
076: private transient ResourceLoader msgs = new ResourceLoader(
077: "org.sakaiproject.umem.tool.bundle.Messages");
078: /** Controller fields */
079: private List userSitesRows;
080: /** Getter vars */
081: private boolean refreshQuery = false;
082: private boolean allowed = false;
083: private String this UserId = null;
084: private String userId = null;
085: private boolean sitesSortAscending = true;
086: private String sitesSortColumn = SORT_SITE_NAME;
087: /** Resource properties */
088: private final static String PROP_SITE_TERM = "term";
089: /** Sakai APIs */
090: private SessionManager M_session = (SessionManager) ComponentManager
091: .get(SessionManager.class.getName());
092: private SqlService M_sql = (SqlService) ComponentManager
093: .get(SqlService.class.getName());
094: private SiteService M_site = (SiteService) ComponentManager
095: .get(SiteService.class.getName());
096: private ToolManager M_tm = (ToolManager) ComponentManager
097: .get(ToolManager.class.getName());
098: private Authz authz = (Authz) ComponentManager.get(Authz.class
099: .getName());
100: /** Private vars */
101: private Collator collator = Collator.getInstance();
102: private long timeSpentInGroups = 0;
103: private String portalURL = ServerConfigurationService
104: .getPortalUrl();
105: private String message = "";
106:
107: // ######################################################################################
108: // UserSitesRow CLASS
109: // ######################################################################################
110:
111: public class UserSitesRow implements Serializable {
112: private static final long serialVersionUID = 1L;
113: private Site site;
114: private String siteId;
115: private String siteTitle;
116: private String siteType;
117: private String siteURL;
118: private String groups;
119: private String roleName;
120: private String pubView;
121: private String userStatus;
122: private String siteTerm;
123:
124: public UserSitesRow() {
125: }
126:
127: public UserSitesRow(String siteId, String siteTitle,
128: String siteType, String groups, String roleName,
129: String pubView, String userStatus, String term) {
130: this .siteId = siteId;
131: this .siteTitle = siteTitle;
132: this .siteType = siteType;
133: this .groups = groups;
134: this .roleName = roleName;
135: this .pubView = pubView;
136: this .userStatus = userStatus;
137: this .siteTerm = term;
138: }
139:
140: public UserSitesRow(Site site, String groups, String roleName) {
141: this .siteId = site.getId();
142: this .siteTitle = site.getTitle();
143: this .siteType = site.getType();
144: this .groups = groups;
145: this .roleName = roleName;
146: this .pubView = site.isPublished() ? msgs
147: .getString("status_published") : msgs
148: .getString("status_unpublished");
149: this .userStatus = site.getMember(userId).isActive() ? msgs
150: .getString("site_user_status_active") : msgs
151: .getString("site_user_status_inactive");
152: this .siteTerm = site.getProperties().getProperty(
153: PROP_SITE_TERM);
154: }
155:
156: public String getSiteId() {
157: return siteId;
158: }
159:
160: public String getSiteTitle() {
161: return siteTitle;
162: }
163:
164: public String getSiteType() {
165: return siteType;
166: }
167:
168: public String getSiteURL() {
169: try {
170: // return M_ss.getSite(siteId).getUrl();
171: if (site != null)
172: return site.getUrl();
173: else
174: return portalURL + "/site/" + siteId;
175: } catch (Exception e) {
176: LOG.warn("Unable to generate URL for site id: "
177: + siteId);
178: return "";
179: }
180: }
181:
182: public String getGroups() {
183: return groups;
184: }
185:
186: public String getRoleName() {
187: return roleName;
188: }
189:
190: public String getPubView() {
191: return pubView;
192: }
193:
194: public String getUserStatus() {
195: return this .userStatus;
196: }
197:
198: public String getSiteTerm() {
199: return siteTerm;
200: }
201:
202: }
203:
204: public static final Comparator getUserSitesRowComparator(
205: final String fieldName, final boolean sortAscending,
206: final Collator collator) {
207: return new Comparator() {
208: public int compare(Object o1, Object o2) {
209: if (o1 instanceof UserSitesRow
210: && o2 instanceof UserSitesRow) {
211: UserSitesRow r1 = (UserSitesRow) o1;
212: UserSitesRow r2 = (UserSitesRow) o2;
213: try {
214: if (fieldName.equals(SORT_SITE_NAME)) {
215: String s1 = r1.getSiteTitle();
216: String s2 = r2.getSiteTitle();
217: int res = collator.compare(s1 != null ? s1
218: .toLowerCase() : "",
219: s2 != null ? s2.toLowerCase() : "");
220: if (sortAscending)
221: return res;
222: else
223: return -res;
224: } else if (fieldName.equals(SORT_SITE_TYPE)) {
225: String s1 = r1.getSiteType();
226: String s2 = r2.getSiteType();
227: int res = collator.compare(s1 != null ? s1
228: .toLowerCase() : "",
229: s2 != null ? s2.toLowerCase() : "");
230: if (sortAscending)
231: return res;
232: else
233: return -res;
234: } else if (fieldName.equals(SORT_SITE_RID)) {
235: String s1 = r1.getRoleName();
236: String s2 = r2.getRoleName();
237: int res = collator.compare(s1 != null ? s1
238: .toLowerCase() : "",
239: s2 != null ? s2.toLowerCase() : "");
240: if (sortAscending)
241: return res;
242: else
243: return -res;
244: } else if (fieldName.equals(SORT_SITE_PV)) {
245: String s1 = r1.getPubView();
246: String s2 = r2.getPubView();
247: int res = collator.compare(s1 != null ? s1
248: .toLowerCase() : "",
249: s2 != null ? s2.toLowerCase() : "");
250: if (sortAscending)
251: return res;
252: else
253: return -res;
254: } else if (fieldName.equals(SORT_USER_STATUS)) {
255: String s1 = r1.getUserStatus();
256: String s2 = r2.getUserStatus();
257: int res = collator.compare(s1 != null ? s1
258: .toLowerCase() : "",
259: s2 != null ? s2.toLowerCase() : "");
260: if (sortAscending)
261: return res;
262: else
263: return -res;
264: } else if (fieldName.equals(SORT_SITE_TERM)) {
265: String s1 = r1.getSiteTerm();
266: String s2 = r2.getSiteTerm();
267: int res = collator.compare(s1 != null ? s1
268: .toLowerCase() : "",
269: s2 != null ? s2.toLowerCase() : "");
270: if (sortAscending)
271: return res;
272: else
273: return -res;
274: }
275: } catch (Exception e) {
276: LOG.warn("Error occurred while sorting by: "
277: + fieldName, e);
278: }
279: }
280: return 0;
281: }
282: };
283: }
284:
285: // ######################################################################################
286: // Main methods
287: // ######################################################################################
288:
289: public String getInitValues() {
290: if (isAllowed()) {
291: if (userId == null) {
292: String param = (String) FacesContext
293: .getCurrentInstance().getExternalContext()
294: .getRequestParameterMap().get("userId");
295: if (param != null) {
296: userId = param;
297: }
298: }
299:
300: if (refreshQuery) {
301: LOG.debug("Refreshing query...");
302: doSearch();
303: refreshQuery = false;
304: }
305:
306: if (userSitesRows != null && userSitesRows.size() > 0)
307: Collections.sort(userSitesRows,
308: getUserSitesRowComparator(sitesSortColumn,
309: sitesSortAscending, collator));
310: }
311: return "";
312: }
313:
314: /**
315: * Uses complex SQL for site membership, user role and group membership.<br>
316: * For a 12 site users it takes < 1 secs!
317: */
318: private void doSearch() {
319: userSitesRows = new ArrayList();
320: try {
321: Connection c = M_sql.borrowConnection();
322: String sql = "select ss.SITE_ID, ss.TITLE, ss.TYPE, ss.PUBLISHED, srr.ROLE_NAME, srrg.ACTIVE, "
323: + " (select VALUE from SAKAI_SITE_PROPERTY ssp where ss.SITE_ID = ssp.SITE_ID and ssp.NAME = 'term') TERM "
324: + "from SAKAI_SITE ss, SAKAI_REALM sr, SAKAI_REALM_RL_GR srrg, SAKAI_REALM_ROLE srr "
325: + "where sr.REALM_ID = CONCAT('/site/',ss.SITE_ID) "
326: + "and sr.REALM_KEY = srrg.REALM_KEY "
327: + "and srrg.ROLE_KEY = srr.ROLE_KEY "
328: + "and srrg.USER_ID = ? "
329: + "and ss.IS_USER = 0 "
330: + "and ss.IS_SPECIAL = 0 " + "ORDER BY ss.TITLE";
331: PreparedStatement pst = c.prepareStatement(sql);
332: pst.setString(1, userId);
333: ResultSet rs = pst.executeQuery();
334: while (rs.next()) {
335: String id = rs.getString("SITE_ID");
336: String t = rs.getString("TITLE");
337: String tp = rs.getString("TYPE");
338: String pv = rs.getString("PUBLISHED").equals("1") ? msgs
339: .getString("status_published")
340: : msgs.getString("status_unpublished");
341: ;
342: String rn = rs.getString("ROLE_NAME");
343: String grps = getGroups(userId, id);
344: String active = rs.getString("ACTIVE").trim().equals(
345: "1") ? msgs
346: .getString("site_user_status_active") : msgs
347: .getString("site_user_status_inactive");
348: String term = rs.getString("TERM");
349: term = term == null ? "" : term;
350: UserSitesRow row = new UserSitesRow(id, t, tp, grps,
351: rn, pv, active, term);
352: userSitesRows.add(row);
353: }
354: rs.close();
355: pst.close();
356: M_sql.returnConnection(c);
357: } catch (SQLException e) {
358: LOG.warn(
359: "SQL error occurred while retrieving user memberships for user: "
360: + userId, e);
361: LOG
362: .warn("UserMembership will use alternative methods for retrieving user memberships.");
363: doSearch3();
364: }
365: }
366:
367: /**
368: * Uses ONLY Sakai API for site membership, user role and group membership.
369: */
370: private void doSearch2() {
371: long start = (new Date()).getTime();
372: userSitesRows = new ArrayList();
373: this UserId = M_session.getCurrentSessionUserId();
374: setSakaiSessionUser(userId);
375: LOG.debug("Switched CurrentSessionUserId: "
376: + M_session.getCurrentSessionUserId());
377: List siteList = org.sakaiproject.site.cover.SiteService
378: .getSites(SelectionType.ACCESS, null, null, null,
379: SortType.TITLE_ASC, null);
380: setSakaiSessionUser(this UserId);
381:
382: Iterator i = siteList.iterator();
383: while (i.hasNext()) {
384: Site s = (Site) i.next();
385: UserSitesRow row = new UserSitesRow(s, getGroups(userId, s
386: .getId()), getActiveUserRoleInSite(userId, s));
387: userSitesRows.add(row);
388: }
389: long end = (new Date()).getTime();
390: LOG.debug("doSearch2() took total of " + ((end - start) / 1000)
391: + " sec.");
392: }
393:
394: /**
395: * Uses single simple SQL for site membership, uses API for user role and
396: * group membership.<br>
397: * For a 12 site users it takes ~30secs!
398: * @deprecated
399: */
400: private void doSearch3() {
401: userSitesRows = new ArrayList();
402: timeSpentInGroups = 0;
403: try {
404: Connection c = M_sql.borrowConnection();
405: String sql = "select distinct(SAKAI_SITE_USER.SITE_ID) from SAKAI_SITE_USER,SAKAI_SITE where SAKAI_SITE.SITE_ID=SAKAI_SITE_USER.SITE_ID and IS_USER=0 and IS_SPECIAL=0 and USER_ID=?";
406: PreparedStatement pst = c.prepareStatement(sql);
407: pst.setString(1, userId);
408: ResultSet rs = pst.executeQuery();
409: while (rs.next()) {
410: String id = rs.getString("SITE_ID");
411: try {
412: Site site = M_site.getSite(id);
413: UserSitesRow row = new UserSitesRow(site,
414: getGroups(userId, site),
415: getActiveUserRoleInSite(userId, site));
416: userSitesRows.add(row);
417: } catch (IdUnusedException e) {
418: LOG.warn("Unable to retrieve site for site id: "
419: + id, e);
420: }
421: }
422: rs.close();
423: pst.close();
424: M_sql.returnConnection(c);
425: } catch (SQLException e) {
426: LOG.warn(
427: "SQL error occurred while retrieving user memberships for user: "
428: + userId, e);
429: LOG
430: .warn("UserMembership will use alternative methods for retrieving user memberships (ONLY Published sites will be listed).");
431: doSearch2();
432: }
433: LOG.debug("Group ops took " + (timeSpentInGroups / 1000)
434: + " secs");
435: }
436:
437: /**
438: * Uses SQL queries for getting group membership (very very fast).
439: * @param userId The user ID.
440: * @param site The Site object
441: * @return A String with group list.
442: */
443: private String getGroups2(String userId, String siteId) {
444: StringBuffer groups = new StringBuffer();
445: try {
446: Connection c = M_sql.borrowConnection();
447: String sql = "select TITLE "
448: + "from SAKAI_SITE_GROUP,SAKAI_REALM,SAKAI_REALM_RL_GR "
449: + "where SITE_ID=? "
450: + "and SAKAI_REALM.REALM_ID=CONCAT('/site/',CONCAT(SITE_ID,CONCAT('/group/',GROUP_ID))) "
451: + "and SAKAI_REALM.REALM_KEY=SAKAI_REALM_RL_GR.REALM_KEY "
452: + "and SAKAI_REALM_RL_GR.USER_ID=? "
453: + "ORDER BY TITLE";
454: PreparedStatement pst = c.prepareStatement(sql);
455: pst.setString(1, siteId);
456: pst.setString(2, userId);
457: ResultSet rs = pst.executeQuery();
458: while (rs.next()) {
459: String t = rs.getString("TITLE");
460: if (groups.length() != 0)
461: groups.append(", ");
462: groups.append(t);
463: }
464: rs.close();
465: pst.close();
466: M_sql.returnConnection(c);
467: } catch (SQLException e) {
468: LOG.error(
469: "SQL error occurred while retrieving group memberships for user: "
470: + userId, e);
471: }
472: return groups.toString();
473: }
474:
475: /**
476: * Uses Sakai API for getting group membership (very very slow).
477: * @param userId The user ID.
478: * @param site The Site object
479: * @return A String with group list.
480: */
481: public String getGroups(String userId, Site site) {
482: long start = (new Date()).getTime();
483: StringBuffer groups = new StringBuffer();
484: Iterator ig = site.getGroupsWithMember(userId).iterator();
485: while (ig.hasNext()) {
486: Group g = (Group) ig.next();
487: if (groups.length() != 0)
488: groups.append(", ");
489: groups.append(g.getTitle());
490: }
491: long end = (new Date()).getTime();
492: timeSpentInGroups += (end - start);
493: LOG.debug("getGroups(" + userId + ", " + site.getTitle()
494: + ") took " + ((end - start) / 1000) + " sec.");
495: return groups.toString();
496: }
497:
498: public String getGroups(String userId, String siteId) {
499: long start = (new Date()).getTime();
500: StringBuffer groups = new StringBuffer();
501: String siteReference = M_site.siteReference(siteId);
502: try {
503: Connection c = M_sql.borrowConnection();
504: String sql = "select SS.GROUP_ID, SS.TITLE, SS.DESCRIPTION "
505: + "from SAKAI_SITE_GROUP SS, SAKAI_REALM R, SAKAI_REALM_RL_GR RRG "
506: + "where R.REALM_ID = concat(concat('"
507: + siteReference
508: + "','/group/'), SS.GROUP_ID) "
509: + "and R.REALM_KEY = RRG.REALM_KEY "
510: + "and RRG.USER_ID = ? "
511: + "and SS.SITE_ID = ? "
512: + "ORDER BY TITLE";
513: PreparedStatement pst = c.prepareStatement(sql);
514: pst.setString(1, userId);
515: pst.setString(2, siteId);
516: ResultSet rs = pst.executeQuery();
517: while (rs.next()) {
518: String t = rs.getString("SS.TITLE");
519: if (groups.length() != 0)
520: groups.append(", ");
521: groups.append(t);
522: }
523: rs.close();
524: pst.close();
525: M_sql.returnConnection(c);
526: } catch (SQLException e) {
527: LOG.error(
528: "SQL error occurred while retrieving group memberships for user: "
529: + userId, e);
530: }
531: long end = (new Date()).getTime();
532: timeSpentInGroups += (end - start);
533: LOG.debug("getGroups(" + userId + ", " + siteId + ") took "
534: + ((end - start) / 1000) + " sec.");
535: return groups.toString();
536: }
537:
538: /**
539: * Uses Sakai API for getting user role in site.
540: * @param userId The user ID.
541: * @param site The Site object.
542: * @return The user role in site as String.
543: */
544: protected String getActiveUserRoleInSite(String userId, Site site) {
545: Role r = site.getUserRole(userId);
546: return (r != null) ? r.getId() : "";
547: }
548:
549: private synchronized void setSakaiSessionUser(String id) {
550: Session sakaiSession = M_session.getCurrentSession();
551: sakaiSession.setUserId(id);
552: sakaiSession.setUserEid(id);
553: }
554:
555: // ######################################################################################
556: // ActionListener methods
557: // ######################################################################################
558: public String processActionUserId() {
559: try {
560: ExternalContext context = FacesContext.getCurrentInstance()
561: .getExternalContext();
562: Map paramMap = context.getRequestParameterMap();
563: userId = (String) paramMap.get("userId");
564: refreshQuery = true;
565: return "sitelist";
566: } catch (Exception e) {
567: LOG.error("Error getting userId var.");
568: return "userlist";
569: }
570: }
571:
572: public String processActionBack() {
573: return "userlist";
574: }
575:
576: // ######################################################################################
577: // Generic get/set methods
578: // ######################################################################################
579: public boolean isAllowed() {
580: allowed = authz.isUserAbleToViewUmem(M_tm.getCurrentPlacement()
581: .getContext());
582:
583: if (!allowed) {
584: FacesContext fc = FacesContext.getCurrentInstance();
585: message = msgs.getString("unauthorized");
586: fc.addMessage("allowed", new FacesMessage(
587: FacesMessage.SEVERITY_FATAL, message, null));
588: allowed = false;
589: }
590: return allowed;
591: }
592:
593: public List getUserSitesRows() {
594: if (userSitesRows != null && userSitesRows.size() > 0)
595: Collections.sort(userSitesRows, getUserSitesRowComparator(
596: sitesSortColumn, sitesSortAscending, collator));
597: return userSitesRows;
598: }
599:
600: public void setUserSitesRows(List userRows) {
601: this .userSitesRows = userRows;
602: }
603:
604: public boolean isEmptySiteList() {
605: return (userSitesRows == null || userSitesRows.size() <= 0);
606: }
607:
608: public boolean isRenderTable() {
609: return !isEmptySiteList();
610: }
611:
612: public String getUserEid() {
613: try {
614: return UserDirectoryService.getUserEid(userId);
615: } catch (UserNotDefinedException e) {
616: return userId;
617: }
618: }
619:
620: public void setUserId(String id) {
621: this .userId = id;
622: }
623:
624: public boolean getSitesSortAscending() {
625: return this .sitesSortAscending;
626: }
627:
628: public void setSitesSortAscending(boolean sitesSortAscending) {
629: this .sitesSortAscending = sitesSortAscending;
630: }
631:
632: public String getSitesSortColumn() {
633: return this .sitesSortColumn;
634: }
635:
636: public void setSitesSortColumn(String sitesSortColumn) {
637: this .sitesSortColumn = sitesSortColumn;
638: }
639:
640: // ######################################################################################
641: // CSV export
642: // ######################################################################################
643: public void exportAsCsv(ActionEvent event) {
644: String prefix = "Membership_for_" + getUserEid();
645: Export.writeAsCsv(getAsCsv(userSitesRows), prefix);
646: }
647:
648: private String getAsCsv(List list) {
649: StringBuffer sb = new StringBuffer();
650:
651: // Add the headers
652: Export.appendQuoted(sb, msgs.getString("site_name"));
653: sb.append(",");
654: Export.appendQuoted(sb, msgs.getString("site_id"));
655: sb.append(",");
656: Export.appendQuoted(sb, msgs.getString("groups"));
657: sb.append(",");
658: Export.appendQuoted(sb, msgs.getString("site_type"));
659: sb.append(",");
660: Export.appendQuoted(sb, msgs.getString("site_term"));
661: sb.append(",");
662: Export.appendQuoted(sb, msgs.getString("role_name"));
663: sb.append(",");
664: Export.appendQuoted(sb, msgs.getString("status"));
665: sb.append(",");
666: Export.appendQuoted(sb, msgs.getString("site_user_status"));
667: sb.append("\n");
668:
669: // Add the data
670: Iterator i = list.iterator();
671: while (i.hasNext()) {
672: UserSitesRow usr = (UserSitesRow) i.next();
673: // user name
674: Export.appendQuoted(sb, usr.getSiteTitle());
675: sb.append(",");
676: Export.appendQuoted(sb, usr.getSiteId());
677: sb.append(",");
678: Export.appendQuoted(sb, usr.getGroups());
679: sb.append(",");
680: Export.appendQuoted(sb, usr.getSiteType());
681: sb.append(",");
682: Export.appendQuoted(sb, usr.getSiteTerm());
683: sb.append(",");
684: Export.appendQuoted(sb, usr.getRoleName());
685: sb.append(",");
686: Export.appendQuoted(sb, usr.getPubView());
687: sb.append(",");
688: Export.appendQuoted(sb, usr.getUserStatus());
689: sb.append("\n");
690: }
691: return sb.toString();
692: }
693:
694: }
|