added some confirmation alerts
[Evergreen.git] / Open-ILS / web / opac / common / js / opac_utils.js
1 /* - Request ------------------------------------------------------------- */
2
3 /* define it again here for pages that don't load RemoteRequest */
4 function isXUL() { try { if(IAMXUL) return true;}catch(e){return false;}; }
5
6
7 var cookieManager = new HTTP.Cookies();
8
9 var __ilsEvent; /* the last event the occurred */
10
11 function Request(type) {
12
13         var s = type.split(":");
14         if(s[2] == "1" && isXUL()) s[1] += ".staff";
15         this.request = new RemoteRequest(s[0], s[1]);
16         var p = [];
17
18         for( var x = 1; x!= arguments.length; x++ ) {
19                 p.push(arguments[x]);
20                 this.request.addParam(arguments[x]);
21         }
22
23         if( getDebug() ) {
24                 var str = "";
25                 for( var i = 0; i != p.length; i++ ) {
26                         if( i > 0 ) str += ", "
27                         str += js2JSON(p[i]);
28                 }
29                 _debug('request ' + s[0] + ' ' + s[1] + ' ' + str );
30         }
31 }
32
33 Request.prototype.callback = function(cal) {this.request.setCompleteCallback(cal);}
34 Request.prototype.send          = function(block){this.request.send(block);}
35 Request.prototype.result        = function(){return this.request.getResultObject();}
36
37 function showCanvas() {
38         for( var x in G.ui.altcanvas ) {
39                 hideMe(G.ui.altcanvas[x]);
40         }
41         hideMe(G.ui.common.loading);
42         unHideMe(G.ui.common.canvas_main);
43         try{G.ui.searchbar.text.focus();}catch(e){}
44 }
45
46 function swapCanvas(newNode) {
47         for( var x in G.ui.altcanvas ) 
48                 hideMe(G.ui.altcanvas[x]);
49
50         hideMe(G.ui.common.loading);
51         hideMe(G.ui.common.canvas_main);
52         unHideMe(newNode);
53 }
54
55 /* finds the name of the current page */
56 var currentPage = null;
57 function findCurrentPage() {
58         if(currentPage) return currentPage;
59
60         var pages = [];
61         for( var p in config.page ) pages.push(config.page[p]);
62         pages = pages.sort( function(a,b){ return - (a.length - b.length); } );
63
64         var path = location.pathname;
65         if(!path.match(/.*\.xml$/))
66                 path += "index.xml"; /* in case they go to  / */
67
68         var page = null;
69         for( var p in pages ) {
70                 if( path.indexOf(pages[p]) != -1)
71                         page = pages[p];
72         }
73
74         for( var p in config.page ) {
75                 if(config.page[p] == page) {
76                         currentPage = p;
77                         return p;
78                 }
79         }
80         return null;
81 }
82
83
84 /* sets all of the params values  ----------------------------- */
85 function initParams() {
86         var cgi = new CGI();    
87
88         TERM            = cgi.param(PARAM_TERM);
89         STYPE           = cgi.param(PARAM_STYPE);
90         FORM            = cgi.param(PARAM_FORM);
91         LOCATION        = parseInt(cgi.param(PARAM_LOCATION));
92         ORIGLOC = parseInt(cgi.param(PARAM_ORIGLOC));
93         DEPTH           = parseInt(cgi.param(PARAM_DEPTH));
94         OFFSET  = parseInt(cgi.param(PARAM_OFFSET));
95         COUNT           = parseInt(cgi.param(PARAM_COUNT));
96         HITCOUNT        = parseInt(cgi.param(PARAM_HITCOUNT));
97         MRID            = parseInt(cgi.param(PARAM_MRID));
98         RID             = parseInt(cgi.param(PARAM_RID));
99         AUTHTIME        = parseInt(cgi.param(PARAM_AUTHTIME));
100         ADVTERM = cgi.param(PARAM_ADVTERM);
101         ADVTYPE = cgi.param(PARAM_ADVTYPE);
102         RTYPE           = cgi.param(PARAM_RTYPE);
103         SORT            = cgi.param(PARAM_SORT);
104         SORT_DIR        = cgi.param(PARAM_SORT_DIR);
105         DEBUG           = cgi.param(PARAM_DEBUG);
106         CALLNUM = cgi.param(PARAM_CN);
107
108         /* set up some sane defaults */
109         if(isNaN(LOCATION))     LOCATION        = 1;
110         if(isNaN(DEPTH))                DEPTH           = 0;
111         if(isNaN(OFFSET))               OFFSET  = 0;
112         if(isNaN(COUNT))                COUNT           = 10;
113         if(isNaN(HITCOUNT))     HITCOUNT        = 0;
114         if(isNaN(MRID))         MRID            = 0;
115         if(isNaN(RID))                  RID             = 0;
116         if(isNaN(ORIGLOC))      ORIGLOC = 1;
117         if(isNaN(AUTHTIME))     AUTHTIME        = 0;
118         if(ADVTERM==null)               ADVTERM = "";
119 }
120
121 function initCookies() {
122         FONTSIZE = "medium";
123         var font = cookieManager.read(COOKIE_FONT);
124         if(font) FONTSIZE = font;
125         SKIN = cookieManager.read(COOKIE_SKIN);
126 }
127
128 /* URL param accessors */
129 function getTerm(){return TERM;}
130 function getStype(){return STYPE;}
131 function getLocation(){return LOCATION;}
132 function getDepth(){return DEPTH;}
133 function getForm(){return FORM;}
134 function getOffset(){return OFFSET;}
135 function getDisplayCount(){return COUNT;}
136 function getHitCount(){return HITCOUNT;}
137 function getMrid(){return MRID;};
138 function getRid(){return RID;};
139 function getOrigLocation(){return ORIGLOC;}
140 function getAuthtime() { return AUTHTIME; }
141 function getSearchBarExtras(){return SBEXTRAS;}
142 function getFontSize(){return FONTSIZE;};
143 function getSkin(){return SKIN;};
144 function getAdvTerm(){return ADVTERM;}
145 function getAdvType(){return ADVTYPE;}
146 function getRtype(){return RTYPE;}
147 function getSort(){return SORT;}
148 function getSortDir(){return SORT_DIR;}
149 function getDebug(){return DEBUG;}
150 function getCallnumber() { return CALLNUM; }
151
152
153 function findBasePath() {
154         var path = location.pathname;
155         if(!path.match(/.*\.xml$/)) path += "index.xml"; 
156         var idx = path.indexOf(config.page[findCurrentPage()]);
157         return path.substring(0, idx);
158 }
159
160 function findBaseURL(ssl) {
161         var path = findBasePath();
162         var proto = (ssl) ? "https:" : "http:";
163         return proto + "//" + location.host + path;
164         dump( 'ssl: ' + ssl + 'proto ' + proto );
165 }
166
167 /*
168 function buildISBNSrc(isbn) {
169         return "http://" + location.host + "/jackets/" + isbn;
170 }
171 */
172
173 function buildImageLink(name, ssl) {
174         return findBaseURL(ssl) + "../../../../images/" + name;
175 }
176
177 function buildExtrasLink(name, ssl) {
178         return findBaseURL(ssl) + "../../../../extras/" + name;
179 }
180
181 function _debug(str) { try { dump(str + '\n'); } catch(e) {} }
182
183 function  buildOPACLink(args, slim, ssl) {
184
185         if(!args) args = {};
186         var string = "";
187
188         if(!slim) {
189                 string = findBaseURL(ssl);
190                 if(args.page) string += config.page[args.page];
191                 else string += config.page[findCurrentPage()];
192         }
193
194         string += "?";
195
196         for( var x in args ) {
197                 var v = args[x];
198                 if(x == "page" || v == null || v == undefined || v+'' == 'NaN' ) continue;
199                 if(x == PARAM_OFFSET && v == 0) continue;
200                 if(x == PARAM_LOCATION && v == 1) continue;
201                 if(x == PARAM_DEPTH && v == 0) continue;
202                 if(x == PARAM_COUNT && v == 10) continue;
203                 if(x == PARAM_FORM && v == 'all' ) continue;
204                 if( instanceOf(v, Array) && v.length ) {
205                         for( var i = 0; i < v.length; i++ ) {
206                                 string += "&" + x + "=" + encodeURIComponent(v[i]);
207                         }
208                 } else {
209                         string += "&" + x + "=" + encodeURIComponent(v);
210                 }
211         }
212
213         if(getDebug())
214                 string += _appendParam(DEBUG,   PARAM_DEBUG, args, getDebug, string);
215         if(getOrigLocation() != 1) 
216                 string += _appendParam(ORIGLOC, PARAM_ORIGLOC, args, getOrigLocation, string);
217         if(getTerm()) 
218                 string += _appendParam(TERM,            PARAM_TERM, args, getTerm, string);
219         if(getStype()) 
220                 string += _appendParam(STYPE,           PARAM_STYPE, args, getStype, string);
221         if(getLocation() != 1) 
222                 string += _appendParam(LOCATION, PARAM_LOCATION, args, getLocation, string);
223         if(getDepth() != 0) 
224                 string += _appendParam(DEPTH,           PARAM_DEPTH, args, getDepth, string);
225         if(getForm() && (getForm() != 'all') ) 
226                 string += _appendParam(FORM,            PARAM_FORM, args, getForm, string);
227         if(getOffset() != 0) 
228                 string += _appendParam(OFFSET,  PARAM_OFFSET, args, getOffset, string);
229         if(getDisplayCount() != 10) 
230                 string += _appendParam(COUNT,           PARAM_COUNT, args, getDisplayCount, string);
231         if(getHitCount()) 
232                 string += _appendParam(HITCOUNT, PARAM_HITCOUNT, args, getHitCount, string);
233         if(getMrid())
234                 string += _appendParam(MRID,            PARAM_MRID, args, getMrid, string);
235         if(getRid())
236                 string += _appendParam(RID,             PARAM_RID, args, getRid, string);
237         if(getAuthtime())
238                 string += _appendParam(AUTHTIME,        PARAM_AUTHTIME, args, getAuthtime, string);
239         if(getAdvTerm())
240                 string += _appendParam(ADVTERM, PARAM_ADVTERM, args, getAdvTerm, string);
241         if(getAdvType())
242                 string += _appendParam(ADVTYPE, PARAM_ADVTYPE, args, getAdvType, string);
243         if(getRtype())
244                 string += _appendParam(RTYPE,           PARAM_RTYPE, args, getRtype, string);
245
246         return string.replace(/\&$/,'').replace(/\?\&/,"?");    
247 }
248
249 function _appendParam( fieldVar, fieldName, overrideArgs, getFunc, string ) {
250         var ret = "";
251         if( fieldVar != null && (fieldVar +'' != 'NaN') && overrideArgs[fieldName] == null ) 
252                 ret = "&" + fieldName + "=" + encodeURIComponent(getFunc());
253         return ret;
254 }
255
256 /* ----------------------------------------------------------------------- */
257 function cleanISBN(isbn) {
258    if(isbn) {
259       isbn = isbn.toString().replace(/^\s+/,"");
260       var idx = isbn.indexOf(" ");
261       if(idx > -1) { isbn = isbn.substring(0, idx); }
262    } else isbn = "";
263    return isbn;
264 }       
265
266
267 /* builds a link that goes to the title listings for a metarecord */
268 function buildTitleLink(rec, link) {
269         if(!rec) return;
270         link.appendChild(text(normalize(truncate(rec.title(), 65))));
271         var args = {};
272         args.page = RRESULT;
273         args[PARAM_OFFSET] = 0;
274         args[PARAM_MRID] = rec.doc_id();
275         args[PARAM_RTYPE] = RTYPE_MRID;
276         link.setAttribute("href", buildOPACLink(args));
277 }
278
279 function buildTitleDetailLink(rec, link) {
280         if(!rec) return;
281         link.appendChild(text(normalize(truncate(rec.title(), 65))));
282         var args = {};
283         args.page = RDETAIL;
284         args[PARAM_OFFSET] = 0;
285         args[PARAM_RID] = rec.doc_id();
286         link.setAttribute("href", buildOPACLink(args));
287 }
288
289 /* 'type' is one of STYPE_AUTHOR, STYPE_SUBJECT, ... found in config.js 
290         'trunc' is the number of characters to show in the string, defaults to 65 */
291 function buildSearchLink(type, string, linknode, trunc) {
292         if(!trunc) trunc = 65;
293         var args = {};
294         args.page = MRESULT;
295         args[PARAM_OFFSET] = 0;
296         args[PARAM_TERM] = string;
297         args[PARAM_STYPE] = type;
298         linknode.appendChild(text(normalize(truncate(string, trunc))));
299         linknode.setAttribute("href", buildOPACLink(args));
300 }
301
302
303 /* ----------------------------------------------------------------------- */
304 /* user session handling */
305 /* ----------------------------------------------------------------------- */
306 /* session is the login session.  If no session is provided, we attempt
307         to find one in the cookies.  If 'force' is true we retrieve the 
308         user from the server even if there is already a global user present.
309         if ses != G.user.session, we also force a grab */
310 function grabUser(ses, force) {
311
312         if(!ses && isXUL()) ses = xulG['authtoken'];
313         if(!ses) ses = cookieManager.read(COOKIE_SES);
314         if(!ses) return false;
315
316         if(!force) 
317                 if(G.user && G.user.session == ses)
318                         return G.user;
319
320         /* first make sure the session is valid */
321         var request = new Request(FETCH_SESSION, ses, 1 );
322         request.send(true);
323         var user = request.result();
324
325         if(checkILSEvent(user)) {
326                 __ilsEvent = user;
327                 doLogout();
328                 return false; /* unable to grab the session */
329         }
330
331         if( !(typeof user == 'object' && user._isfieldmapper) ) {
332                 doLogout();
333                 return false;
334         }
335
336         G.user = user;
337         G.user.fleshed = false;
338         G.user.session = ses;
339         cookieManager.write(COOKIE_SES, ses, '+1y');
340
341         grabUserPrefs();
342         if(G.user.prefs['opac.hits_per_page'])
343                 COUNT = parseInt(G.user.prefs['opac.hits_per_page']);
344
345         var at = getAuthtime();
346         if(isXUL()) at = xulG['authtime'];
347
348         if(at) new AuthTimer(at).run(); 
349         return G.user;
350 }
351
352
353 /* sets the 'prefs' field of the user object to their preferences 
354         and returns the preferences */
355 function grabUserPrefs(user, force) {
356         if(user == null) user = G.user;
357         if(!force && user.prefs) return user.prefs;     
358         var req = new Request(FETCH_USER_PREFS, user.session, user.id());
359         req.send(true);
360         user.prefs = req.result();
361         return user.prefs;
362 }
363
364 function grabFleshedUser() {
365
366         if(!G.user || !G.user.session) {
367                 grabUser();     
368                 if(!G.user || !G.user.session) return null;
369         }
370
371         if(G.user.fleshed) return G.user;
372
373    var req = new Request(FETCH_FLESHED_USER, G.user.session);
374         req.send(true);
375
376         G.user = req.result();
377
378         if(!G.user || G.user.length == 0) { 
379                 G.user = null; return false; 
380                 cookieManager.remove(COOKIE_SES);
381         }
382
383         G.user.session = ses;
384         G.user.fleshed = true;
385
386         cookieManager.write(COOKIE_SES, ses, '+1y'); /*  update the cookie */
387         return G.user;
388 }
389
390 function checkUserSkin(new_skin) {
391
392         return; /* XXX do some debugging with this... */
393
394         var user_skin = getSkin();
395         var cur_skin = grabSkinFromURL();
396
397         if(new_skin) user_skin = new_skin;
398
399         if(!user_skin) {
400
401                 if(grabUser()) {
402                         if(grabUserPrefs()) {
403                                 user_skin = G.user.prefs["opac.skin"];
404                                 cookieManager.write( COOKIE_SKIN, user_skin, '+1y' );
405                         }
406                 }
407         }
408
409         if(!user_skin) return;
410
411         if( cur_skin != user_skin ) {
412                 var url = buildOPACLink();
413                 goTo(url.replace(cur_skin, user_skin));
414         }
415 }
416
417 function updateUserSetting(setting, value, user) {
418         if(user == null) user = G.user;
419         var a = {};
420         a[setting] = value;
421         var req = new Request( UPDATE_USER_PREFS, user.session, a );
422         req.send(true);
423         return req.result();
424 }
425
426 function commitUserPrefs() {
427         var req = new Request( 
428                 UPDATE_USER_PREFS, G.user.session, null, G.user.prefs );
429         req.send(true);
430         return req.result();
431 }
432
433 function grabSkinFromURL() {
434         var path = findBasePath();
435         path = path.replace("/xml/", "");
436         var skin = "";
437         for( var i = path.length - 1; i >= 0; i-- ) {
438                 var ch = path.charAt(i);
439                 if(ch == "/") break;
440                 skin += ch;
441         }
442
443         var skin2 = "";
444         for( i = skin.length - 1; i >= 0; i--)
445                 skin2 += skin.charAt(i);
446
447         return skin2;
448 }
449
450
451 /* returns a fleshed G.user on success, false on failure */
452 function doLogin() {
453
454         abortAllRequests();
455
456         var uname = G.ui.login.username.value;
457         var passwd = G.ui.login.password.value; 
458
459         var init_request = new Request( LOGIN_INIT, uname );
460    init_request.send(true);
461    var seed = init_request.result();
462
463    if( ! seed || seed == '0') {
464       alert( "Error Communicating with Authentication Server" );
465       return null;
466    }
467
468         var args = {
469                 password : hex_md5(seed + hex_md5(passwd)), 
470                 type            : "opac", 
471                 org             : getOrigLocation()
472         };
473
474         if( uname.match(REGEX_BARCODE) ) args.barcode = uname; /* make this better */
475         else args.username = uname;
476
477    var auth_request = new Request( LOGIN_COMPLETE, args );
478
479    auth_request.send(true);
480    var auth_result = auth_request.result();
481
482         var code = checkILSEvent(auth_result);
483         if(code) { alertILSEvent(code); return null; }
484
485         AUTHTIME = parseInt(auth_result.payload.authtime);
486         var u = grabUser(auth_result.payload.authtoken, true);
487         if(u) runEvt( "common", "locationChanged", u.home_ou(), findOrgDepth(u.home_ou()) );
488
489         checkUserSkin();
490
491         return u;
492 }
493
494 function doLogout(noredirect) {
495
496         /* cancel everything else */
497         abortAllRequests();
498
499         /* be nice and delete the session from the server */
500         if(G.user && G.user.session) { 
501                 var req = new Request(LOGIN_DELETE, G.user.session);
502       req.send(true);
503                 try { req.result(); } catch(E){}
504     }
505
506         G.user = null;
507         cookieManager.remove(COOKIE_SES);
508         cookieManager.remove(COOKIE_SKIN);
509         checkUserSkin("default");
510         COUNT = 10;
511
512         var args = {};
513         args[PARAM_TERM] = "";
514         args[PARAM_LOCATION] = globalOrgTree.id();
515         args[PARAM_DEPTH] = findOrgDepth(globalOrgTree);
516         args.page = "home";
517
518         var nored = false;
519         try{ if(isFrontPage) nored = true; } catch(e){nored = false;}
520         if(!nored) goTo(buildOPACLink(args));
521 }
522
523
524 function hideMe(obj) { addCSSClass(obj, config.css.hide_me); } 
525 function unHideMe(obj) { removeCSSClass(obj, config.css.hide_me); }
526
527
528 /* ----------------------------------------------------------------------- */
529 /* build the org tree */
530 /* ----------------------------------------------------------------------- */
531 function drawOrgTree() {
532         //setTimeout( 'buildOrgSelector(G.ui.common.org_tree, orgTreeSelector);', 10 );
533         setTimeout( 'buildOrgSelector(G.ui.common.org_tree, orgTreeSelector);', 1 );
534 }
535         
536 var orgTreeSelector;
537 function buildOrgSelector(node) {
538         var tree = new SlimTree(node,'orgTreeSelector');
539         orgTreeSelector = tree;
540         for( var i in orgArraySearcher ) { 
541                 var node = orgArraySearcher[i];
542                 if( node == null ) continue;
543                 if(node.parent_ou() == null)
544                         tree.addNode(node.id(), -1, node.name(), 
545                                 "javascript:orgSelect(" + node.id() + ");", node.name());
546                 else {
547                         tree.addNode(node.id(), findOrgUnit(node.parent_ou()).id(), node.name(), 
548                                 "javascript:orgSelect(" + node.id() + ");", node.name());
549                 }
550         }
551         hideMe($('org_loading_div'));
552         unHideMe($('org_selector_tip'));
553         return tree;
554 }
555
556 function orgSelect(id) {
557         showCanvas();
558         runEvt("common", "locationChanged", id, findOrgDepth(id) );
559
560         removeChildren(G.ui.common.now_searching);
561         G.ui.common.now_searching.appendChild(text(findOrgUnit(id).name()));
562 }
563
564 var fontCookie = new HTTP.Cookies();
565 function setFontSize(size) {
566         scaleFonts(size);
567         fontCookie.write(COOKIE_FONT, size, '+1y');
568 }
569
570
571 var resourceFormats = [
572    "text",
573    "moving image",
574    "sound recording", "software, multimedia",
575    "still images",
576    "cartographic",
577    "mixed material",
578    "notated music",
579    "three dimensional object" ];
580
581
582 function modsFormatToMARC(format) {
583    switch(format) {
584       case "text":
585          return "at";
586       case "moving image":
587          return "g";
588       case "sound recording":
589          return "ij";
590       case "sound recording-nonmusical":
591          return "i";
592       case "sound recording-musical":
593          return "j";
594       case "software, multimedia":
595          return "m";
596       case "still images":
597          return "k";
598       case "cartographic":
599          return "ef";
600       case "mixed material":
601          return "op";
602       case "notated music":
603          return "cd";
604       case "three dimensional object":
605          return "r";
606    }
607    return "at";
608 }
609
610
611 function MARCFormatToMods(format) {
612    switch(format) {
613       case "a":
614       case "t":
615          return "text";
616       case "g":
617          return "moving image";
618       case "i":
619          return "sound recording-nonmusical";
620       case "j":
621          return "sound recording-musical";
622       case "m":
623          return "software, multimedia";
624       case "k":
625          return "still images";
626       case "e":
627       case "f":
628          return "cartographic";
629       case "o":
630       case "p":
631          return "mixed material";
632       case "c":
633       case "d":
634          return "notated music";
635       case "r":
636          return "three dimensional object";
637    }
638    return "text";
639 }
640
641 function setResourcePic( img, resource ) {
642         img.setAttribute( "src", "../../../../images/tor/" + resource + ".jpg");
643         img.title = resource;
644 }
645
646
647
648 function msg( text ) {
649         try { alert( text ); } catch(e) {}
650 }
651
652 function findRecord(id,type) {
653         try {
654                 for( var i = 0; i != recordsCache.length; i++ ) {
655                         var rec = recordsCache[i];
656                         if( rec && rec.doc_id() == id ) return rec;
657                 }
658         } catch(E){}
659         var meth = FETCH_RMODS
660         if(type == 'M') meth = FETCH_MRMODS;
661         var req = new Request(meth, id);
662         req.send(true);
663         return req.result();
664 }
665
666 function Timer(name, node){
667         this.name = name;
668         this.count = 1;
669         this.node = node;
670 }
671 Timer.prototype.start = 
672         function(){_timerRun(this.name);}
673 Timer.prototype.stop = 
674         function(){this.done = true;}
675 function _timerRun(tname) {
676         var _t;
677         eval('_t='+tname);
678         if(_t.done) return;
679         if(_t.count > 100) return;
680         var str = ' . ';
681         if( (_t.count % 5) == 0 ) 
682                 str = _t.count / 5;
683         _t.node.appendChild(text(str));
684         setTimeout("_timerRun('"+tname+"');", 200);
685         _t.count++;
686 }
687
688 function checkILSEvent(obj) {
689         if( obj.ilsevent != null && obj.ilsevent != 0 )
690                 return parseInt(obj.ilsevent);
691         return null;
692 }
693 function alertILSEvent(code, msg) {
694    if(!msg) msg = "";
695         alert( msg + '\n' + $('ilsevent.' + code).innerHTML );
696 }
697
698
699 var __authTimer;
700 function AuthTimer(time) { 
701         this.time = (time - LOGOUT_WARNING_TIME) * 1000; 
702         __authTimer = this;
703 }
704
705 AuthTimer.prototype.run = function() {
706         setTimeout('_authTimerAlert()', this.time);
707 }
708
709 function _authTimerAlert() {
710         alert( $('auth_session_expiring').innerHTML );
711         if(!grabUser(null, true)) doLogout();
712 }
713
714
715 function grabUserByBarcode( authtoken, barcode ) {
716         var req = new Request( FETCH_USER_BYBARCODE, authtoken, barcode );
717         req.send(true);
718         return req.result();
719 }
720
721
722 function goHome() {
723         goTo(buildOPACLink({page:HOME}));
724 }
725
726
727