d52351a23409d6f35cc598784ef0a0596ae3eb9a
[citadel.git] / webcit / roomlist.c
1 /*
2  * room listings and filters.
3  */
4
5 #include "webcit.h"
6 #include "webserver.h"
7
8 HashList *GetWhoKnowsHash(StrBuf *Target, WCTemplputParams *TP)
9 {
10         wcsession *WCC = WC;
11         StrBuf *Line;
12         StrBuf *Token;
13         long State;
14         HashList *Whok = NULL;
15         int Done = 0;
16         int n;
17
18         serv_puts("WHOK");
19         Line = NewStrBuf();
20         StrBuf_ServGetln(Line);
21         if (GetServerStatus(Line, &State) == 1) 
22         {
23                 Whok = NewHash(1, Flathash);
24                 while(!Done && (StrBuf_ServGetln(Line) >= 0) )
25                         if ( (StrLength(Line)==3) && 
26                              !strcmp(ChrPtr(Line), "000")) 
27                         {
28                                 Done = 1;
29                         }
30                         else
31                         {
32                         
33                                 const char *Pos = NULL;
34                                 Token = NewStrBufPlain (NULL, StrLength(Line));
35                                 StrBufExtract_NextToken(Token, Line, &Pos, '|');
36
37                                 Put(Whok, 
38                                     IKEY(n),
39                                     Token, 
40                                     HFreeStrBuf);
41                                 n++;
42                         }
43         }
44         else if (State == 550)
45                 StrBufAppendBufPlain(WCC->ImportantMsg,
46                                      _("Higher access is required to access this function."), -1, 0);
47
48
49         FreeStrBuf(&Line);
50         return Whok;
51 }
52
53
54 void DeleteFloor(void *vFloor)
55 {
56         Floor *pFloor;
57         pFloor = (Floor*) vFloor;
58         FreeStrBuf(&pFloor->Name);
59         free(pFloor);
60 }
61
62 int SortFloorsByNameOrder(const void *vfloor1, const void *vfloor2) 
63 {
64         Floor *f1 = (Floor*) GetSearchPayload(vfloor1);
65         Floor *f2 = (Floor*) GetSearchPayload(vfloor2);
66         
67         /* prefer My floor over alpabetical sort */
68         if (f1->ID == VIRTUAL_MY_FLOOR)
69                 return 1;
70         if (f2->ID == VIRTUAL_MY_FLOOR)
71                 return -1;
72
73         return strcmp(ChrPtr(f1->Name), ChrPtr(f2->Name));
74 }
75
76 HashList *GetFloorListHash(StrBuf *Target, WCTemplputParams *TP) 
77 {
78         int Done = 0;
79         const char *Err;
80         StrBuf *Buf;
81         HashList *floors;
82         HashList *floorsbyname;
83         HashPos *it;
84         Floor *pFloor;
85         void *vFloor;
86         const char *Pos;
87         int i;
88         wcsession *WCC = WC;
89         const char *HashKey;
90         long HKLen;
91
92
93         if (WCC->Floors != NULL)
94                 return WCC->Floors;
95         WCC->Floors = floors = NewHash(1, Flathash);
96         WCC->FloorsByName = floorsbyname = NewHash(1, NULL);
97         Buf = NewStrBuf();
98
99         pFloor = (Floor*) malloc(sizeof(Floor));
100         pFloor->ID = VIRTUAL_MY_FLOOR;
101         pFloor->Name = NewStrBufPlain(_("My Folders"), -1);
102         pFloor->NRooms = 0;
103         
104         Put(floors, IKEY(pFloor->ID), pFloor, DeleteFloor);
105         Put(floorsbyname, SKEY(pFloor->Name), pFloor, reference_free_handler);
106
107         serv_puts("LFLR"); /* get floors */
108         StrBufTCP_read_line(Buf, &WC->serv_sock, 0, &Err); /* '100', we hope */
109         if (GetServerStatus(Buf, NULL) == 1) 
110         {
111                 while(!Done && StrBuf_ServGetln(Buf) >= 0)
112                         if ( (StrLength(Buf)==3) && 
113                              !strcmp(ChrPtr(Buf), "000")) 
114                         {
115                                 Done = 1;
116                         }
117                         else
118                         {
119                         
120                                 Pos = NULL;
121
122                                 pFloor = (Floor*) malloc(sizeof(Floor));
123                                 pFloor->ID = StrBufExtractNext_int(Buf, &Pos, '|');
124                                 pFloor->Name = NewStrBufPlain(NULL, StrLength(Buf));
125                                 StrBufExtract_NextToken(pFloor->Name, Buf, &Pos, '|');
126                                 pFloor->NRooms = StrBufExtractNext_long(Buf, &Pos, '|');
127
128                                 Put(floors, IKEY(pFloor->ID), pFloor, DeleteFloor);
129                                 Put(floorsbyname, SKEY(pFloor->Name), pFloor, reference_free_handler);
130                         }
131         }
132         FreeStrBuf(&Buf);
133         
134         /* now lets pre-sort them alphabeticaly. */
135         i = 1;
136         SortByPayload(floors, SortFloorsByNameOrder);
137         it = GetNewHashPos(floors, 0);
138         while ( GetNextHashPos(floors, it, &HKLen, &HashKey, &vFloor)) 
139                 ((Floor*) vFloor)->AlphaN = i++;
140         DeleteHashPos(&it);
141         SortByHashKeyStr(floors);
142
143         return floors;
144 }
145
146 HashList *GetZappedRoomListHash(StrBuf *Target, WCTemplputParams *TP) 
147 {
148         wcsession *WCC = WC;
149
150         if (WCC->Floors == NULL)
151                 GetFloorListHash(Target, TP);
152         serv_puts("LZRM -1");
153         return GetRoomListHash(Target, TP);
154 }
155 HashList *GetRoomListHashLKRA(StrBuf *Target, WCTemplputParams *TP) 
156 {
157         wcsession *WCC = WC;
158
159         if (WCC->Floors == NULL)
160                 GetFloorListHash(Target, TP);
161         if (WCC->Rooms == NULL) 
162         {
163                 serv_puts("LKRA");
164                 WCC->Rooms =  GetRoomListHash(Target, TP);
165         }
166         return WCC->Rooms;
167 }
168
169
170 void FlushIgnetCfgs(folder *room)
171 {
172         int i;
173         if (room->IgnetCfgs[maxRoomNetCfg] == (HashList*) StrBufNOTNULL)
174         {
175                 for (i = ignet_push_share; i < maxRoomNetCfg; i++)
176                         DeleteHash(&room->IgnetCfgs[i]);
177         }
178         memset(&room->IgnetCfgs, 0, sizeof(HashList *) * (maxRoomNetCfg + 1));
179
180 }
181
182 void FlushFolder(folder *room)
183 {
184         int i;
185
186         FreeStrBuf(&room->XAPass);
187         FreeStrBuf(&room->Directory);
188         FreeStrBuf(&room->RoomAide);
189         FreeStrBuf(&room->XInfoText);
190         room->XHaveInfoTextLoaded = 0;
191
192         FreeStrBuf(&room->name);
193
194         FlushIgnetCfgs(room);
195
196         if (room->RoomNameParts != NULL)
197         {
198                 for (i=0; i < room->nRoomNameParts; i++)
199                         FreeStrBuf(&room->RoomNameParts[i]);
200                 free(room->RoomNameParts);
201         }
202         memset(room, 0, sizeof(folder));
203 }
204
205 void vDeleteFolder(void *vFolder)
206 {
207         folder *room;
208
209         room = (folder*) vFolder;
210         FlushFolder(room);
211
212         free(room);
213 }
214
215
216 HashList *GetRoomListHash(StrBuf *Target, WCTemplputParams *TP) 
217 {
218         int Done = 0;
219         HashList *rooms;
220         folder *room;
221         StrBuf *Buf;
222         const char *Pos;
223         void *vFloor;
224         wcsession *WCC = WC;
225         CompareFunc SortIt;
226         WCTemplputParams SubTP;
227
228         Buf = NewStrBuf();
229         rooms = NewHash(1, NULL);
230         StrBuf_ServGetln(Buf);
231         if (GetServerStatus(Buf, NULL) == 1) 
232         {
233                 while(!Done && (StrBuf_ServGetln(Buf) >= 0))
234                         if ( (StrLength(Buf)==3) && 
235                              !strcmp(ChrPtr(Buf), "000")) 
236                         {
237                                 Done = 1;
238                         }
239                         else
240                         {                               
241                                 Pos = NULL;
242                                 room = (folder*) malloc (sizeof(folder));
243                                 memset(room, 0, sizeof(folder));
244
245                                 /* Load the base data from the server reply */
246                                 room->name = NewStrBufPlain(NULL, StrLength(Buf));
247                                 StrBufExtract_NextToken(room->name, Buf, &Pos, '|');
248
249                                 room->QRFlags = StrBufExtractNext_long(Buf, &Pos, '|');
250                                 room->floorid = StrBufExtractNext_int(Buf, &Pos, '|');
251                                 room->Order = StrBufExtractNext_long(Buf, &Pos, '|');
252                                 room->QRFlags2 = StrBufExtractNext_long(Buf, &Pos, '|');
253
254                                 room->RAFlags = StrBufExtractNext_long(Buf, &Pos, '|');
255
256 /*
257   ACWHUT?
258   room->ACL = NewStrBufPlain(NULL, StrLength(Buf));
259   StrBufExtract_NextToken(room->ACL, Buf, &Pos, '|');
260 */
261
262                                 room->view = StrBufExtractNext_long(Buf, &Pos, '|');
263                                 room->defview = StrBufExtractNext_long(Buf, &Pos, '|');
264                                 room->lastchange = StrBufExtractNext_long(Buf, &Pos, '|');
265
266                                 /* Evaluate the Server sent data for later use */
267                                 /* find out, whether we are in a sub-room */
268                                 room->nRoomNameParts = StrBufNum_tokens(room->name, '\\');
269                                 if (room->nRoomNameParts > 1)
270                                 {
271                                         int i;
272
273                                         Pos = NULL;
274                                         room->RoomNameParts = malloc(sizeof(StrBuf*) * (room->nRoomNameParts + 1));
275                                         memset(room->RoomNameParts, 0, sizeof(StrBuf*) * (room->nRoomNameParts + 1));
276                                         for (i=0; i < room->nRoomNameParts; i++)
277                                         {
278                                                 room->RoomNameParts[i] = NewStrBuf();
279                                                 StrBufExtract_NextToken(room->RoomNameParts[i],
280                                                                         room->name, &Pos, '\\');
281                                         }
282                                 }
283
284                                 /* Private mailboxes on the main floor get remapped to the personal folder */
285                                 if ((room->QRFlags & QR_MAILBOX) && 
286                                     (room->floorid == 0))
287                                 {
288                                         room->floorid = VIRTUAL_MY_FLOOR;
289                                         if ((room->nRoomNameParts == 1) && 
290                                             (StrLength(room->name) == 4) && 
291                                             (strcmp(ChrPtr(room->name), "Mail") == 0))
292                                         {
293                                                 room->is_inbox = 1;
294                                         }
295
296                                 }
297                                 /* get a pointer to the floor we're on: */
298                                 GetHash(WCC->Floors, IKEY(room->floorid), &vFloor);
299                                 room->Floor = (const Floor*) vFloor;
300
301
302
303                                 /* now we know everything, remember it... */
304                                 Put(rooms, SKEY(room->name), room, vDeleteFolder);
305                         }
306         }
307
308         SubTP.Filter.ContextType = CTX_ROOMS;
309         SortIt = RetrieveSort(&SubTP, NULL, 0, HKEY("fileunsorted"), 0);
310         if (SortIt != NULL)
311                 SortByPayload(rooms, SortIt);
312         else 
313                 SortByPayload(rooms, SortRoomsByListOrder);
314         FreeStrBuf(&Buf);
315         return rooms;
316 }
317
318 HashList *GetNetConfigHash(StrBuf *Target, WCTemplputParams *TP) 
319 {
320         wcsession *WCC = WC;
321         StrBuf *Line;
322         StrBuf *Token;
323         StrBuf *Content;
324         long WantThisOne;
325         long PutTo;
326         long State;
327         
328         WantThisOne = GetTemplateTokenNumber(Target, TP, 5, -1);
329         if ((WantThisOne < 0) || (WantThisOne > maxRoomNetCfg))
330                 return NULL;
331         if (WCC->CurRoom.IgnetCfgs[maxRoomNetCfg] == (HashList*) StrBufNOTNULL)
332                 return WCC->CurRoom.IgnetCfgs[WantThisOne];
333
334         WCC->CurRoom.IgnetCfgs[maxRoomNetCfg] = (HashList*) StrBufNOTNULL;
335         serv_puts("GNET");
336         Line = NewStrBuf();
337         Token = NewStrBuf();
338         StrBuf_ServGetln(Line);
339         if (GetServerStatus(Line, &State) == 1) 
340         {
341                 const char *Pos = NULL;
342                 int Done = 0;
343
344                 while(!Done && (StrBuf_ServGetln(Line) >= 0))
345                         if ( (StrLength(Line)==3) && 
346                              !strcmp(ChrPtr(Line), "000"))
347                         {
348                                 Done = 1;
349                         }
350                         else
351                         {
352                                 StrBufExtract_NextToken(Token, Line, &Pos, '|');
353                                 PutTo = GetTokenDefine(SKEY(Token), -1);
354                                 if ((PutTo >= 0) && 
355                                     (PutTo < maxRoomNetCfg) &&
356                                     (Pos != StrBufNOTNULL))
357                                 {
358                                         int n;
359                                         HashList *SubH;
360                                         
361                                         if (WCC->CurRoom.IgnetCfgs[PutTo] == NULL)
362                                         {
363                                                 n = 0;
364                                                 WCC->CurRoom.IgnetCfgs[PutTo] = NewHash(1, NULL);
365                                         }
366                                         else 
367                                         {
368                                                 n = GetCount(WCC->CurRoom.IgnetCfgs[PutTo]);
369                                         }
370                                         SubH = NewHash(1, NULL);
371                                         Put(WCC->CurRoom.IgnetCfgs[PutTo], 
372                                             IKEY(n),
373                                             SubH, 
374                                             HDeleteHash);
375                                         n = 1; /* #0 is the type... */
376                                         while (Pos != StrBufNOTNULL) {
377                                                 Content = NewStrBuf();
378                                                 StrBufExtract_NextToken(Content, Line, &Pos, '|');
379                                                 Put(SubH, 
380                                                     IKEY(n),
381                                                     Content, 
382                                                     HFreeStrBuf);
383                                                 n++;
384                                         }
385                                 }
386                                 Pos = NULL;
387                         }
388         }
389         else if (State == 550)
390                 StrBufAppendBufPlain(WCC->ImportantMsg,
391                                      _("Higher access is required to access this function."), -1, 0);
392
393
394         return WCC->CurRoom.IgnetCfgs[WantThisOne];
395 }
396
397 /** Unused function that orders rooms by the listorder flag */
398 int SortRoomsByListOrder(const void *room1, const void *room2) 
399 {
400         folder *r1 = (folder*) GetSearchPayload(room1);
401         folder *r2 = (folder*) GetSearchPayload(room2);
402   
403         if (r1->Order == r2->Order) return 0;
404         if (r1->Order > r2->Order) return 1;
405         return -1;
406 }
407
408 int CompareRoomListByFloorRoomPrivFirst(const void *room1, const void *room2) 
409 {
410         folder *r1 = (folder*) GetSearchPayload(room1);
411         folder *r2 = (folder*) GetSearchPayload(room2);
412   
413         if ((r1->Floor == NULL)  ||
414             (r2->Floor == NULL))
415                 return 0;
416                 
417         /**
418          * are we on the same floor? else sort by floor.
419          */
420         if (r1->Floor != r2->Floor)
421         {
422                 /**
423                  * the private rooms are first in any case.
424                  */
425                 if (r1->Floor->ID == VIRTUAL_MY_FLOOR)
426                         return -1;
427                 if (r2->Floor->ID == VIRTUAL_MY_FLOOR)
428                         return 1;
429                 /**
430                  * else decide alpaheticaly by floorname
431                  */
432                 return (r1->Floor->AlphaN > r2->Floor->AlphaN)? 1 : -1;
433         }
434
435         /**
436          * if we have different levels of subdirectories, 
437          * we want the toplevel to be first, regardless of sort
438          * sequence.
439          */
440         if (((r1->nRoomNameParts > 1) || 
441             (r2->nRoomNameParts > 1)    )&&
442             (r1->nRoomNameParts != r2->nRoomNameParts))
443         {
444                 int i, ret;
445                 int nparts = (r1->nRoomNameParts > r2->nRoomNameParts)?
446                         r2->nRoomNameParts : r1->nRoomNameParts;
447
448                 for (i=0; i < nparts; i++)
449                 {
450                         ret = strcmp (ChrPtr(r1->name), 
451                                       ChrPtr(r2->name));
452                         /**
453                          * Deltas in common parts? exit here.
454                          */
455                         if (ret != 0) 
456                                 return ret;
457                 }
458
459                 /**
460                  * who's a subdirectory of whom?
461                  */
462                 if (r1->nRoomNameParts > r2->nRoomNameParts)
463                         return 1;
464                 else
465                         return -1;
466
467         }
468
469         /**
470          * else just sort alphabeticaly.
471          */
472         return strcmp (ChrPtr(r1->name), 
473                        ChrPtr(r2->name));
474 }
475
476 int CompareRoomListByFloorRoomPrivFirstRev(const void *room1, const void *room2) 
477 {
478         folder *r1 = (folder*) GetSearchPayload(room1);
479         folder *r2 = (folder*) GetSearchPayload(room2);
480
481         if ((r1->Floor == NULL)  ||
482             (r2->Floor == NULL))
483                 return 0;
484
485         /**
486          * are we on the same floor? else sort by floor.
487          */
488         if (r2->Floor != r1->Floor)
489         {
490                 /**
491                  * the private rooms are first in any case.
492                  */
493                 if (r1->Floor->ID == VIRTUAL_MY_FLOOR)
494                         return -1;
495                 if (r2->Floor->ID == VIRTUAL_MY_FLOOR)
496                         return 1;
497                 /**
498                  * else decide alpaheticaly by floorname
499                  */
500
501                 return (r1->Floor->AlphaN < r2->Floor->AlphaN)? 1 : -1;
502         }
503
504         /**
505          * if we have different levels of subdirectories, 
506          * we want the toplevel to be first, regardless of sort
507          * sequence.
508          */
509         if (((r1->nRoomNameParts > 1) || 
510             (r2->nRoomNameParts > 1)    )&&
511             (r1->nRoomNameParts != r2->nRoomNameParts))
512         {
513                 int i, ret;
514                 int nparts = (r1->nRoomNameParts > r2->nRoomNameParts)?
515                         r2->nRoomNameParts : r1->nRoomNameParts;
516
517                 for (i=0; i < nparts; i++)
518                 {
519                         /**
520                          * special cases if one room is top-level...
521                          */
522                         if (r2->nRoomNameParts == 1)
523                                 ret = strcmp (ChrPtr(r2->name), 
524                                               ChrPtr(r1->RoomNameParts[i]));
525                         else if (r1->nRoomNameParts == 1)
526                                 ret = strcmp (ChrPtr(r2->RoomNameParts[i]),
527                                               ChrPtr(r1->name));
528                         else 
529                                 ret = strcmp (ChrPtr(r2->RoomNameParts[i]), 
530                                               ChrPtr(r1->RoomNameParts[i]));
531                         /**
532                          * Deltas in common parts? exit here.
533                          */
534                         if (ret != 0) 
535                                 return ret;
536                 }
537
538                 /**
539                  * who's a subdirectory of whom?
540                  */
541                 if (r1->nRoomNameParts > r2->nRoomNameParts)
542                         return 1;
543                 else
544                         return -1;
545         }
546
547         return strcmp (ChrPtr(r2->name), 
548                        ChrPtr(r1->name));
549 }
550
551 int GroupchangeRoomListByFloorRoomPrivFirst(const void *room1, const void *room2) 
552 {
553         folder *r1 = (folder*) room1;
554         folder *r2 = (folder*) room2;
555   
556
557         if ((r1->Floor == NULL)  ||
558             (r2->Floor == NULL))
559                 return 0;
560                 
561         if (r1->Floor == r2->Floor)
562                 return 0;
563         else 
564         {
565                 wcsession *WCC = WC;
566                 static int columns = 3;
567                 int boxes_per_column = 0;
568                 int nf;
569
570                 nf = GetCount(WCC->Floors);
571                 while (nf % columns != 0) ++nf;
572                 boxes_per_column = (nf / columns);
573                 if (boxes_per_column < 1)
574                         boxes_per_column = 1;
575                 if (r1->Floor->AlphaN % boxes_per_column == 0)
576                         return 2;
577                 else 
578                         return 1;
579         }
580 }
581
582
583 int CompareRooms(const folder *room1, const folder *room2) 
584 {
585         if ((room1 == NULL) || (room2 == NULL))
586                 return -1;
587         return CompareRoomListByFloorRoomPrivFirst(room1, room2);
588 }
589
590 int ConditionalRoomIsRESTSubRoom(StrBuf *Target, WCTemplputParams *TP)
591 {
592         wcsession  *WCC = WC;
593         folder     *Folder = (folder *)CTX;
594         HashPos    *it;
595         StrBuf     * Dir;
596         void       *vDir;
597         long        len;
598         const char *Key;
599         int i, j, urlp;
600         int delta;
601
602
603         /* list only folders relative to the current floor... */
604         if (Folder->Floor != WCC->CurrentFloor)
605                 return 0;
606
607         urlp = GetCount(WCC->Directory);
608         delta = Folder->nRoomNameParts - urlp + 1;
609
610         syslog(0, "\n->%s: %d - %ld ", 
611                ChrPtr(Folder->name), 
612                urlp, 
613                Folder->nRoomNameParts);
614         /* list only the floors which are in relation to the dav_depth header */
615         if (WCC->Hdr->HR.dav_depth != delta) {
616                 syslog(0, "1\n");
617                 return 0;
618         }
619
620
621         it = GetNewHashPos(WCC->Directory, 0);
622         /* Fast forward the floorname we checked above... */
623         GetNextHashPos(WCC->Directory, it, &len, &Key, &vDir);
624
625         if (Folder->nRoomNameParts > 1) {               
626                 for (i = 0, j = 1; 
627                      (i > Folder->nRoomNameParts) && (j > urlp); 
628                      i++, j++)
629                 {
630                         if (!GetNextHashPos(WCC->Directory, 
631                                             it, &len, &Key, &vDir) ||
632                             (vDir == NULL))
633                         {
634                                 DeleteHashPos(&it);
635
636                                 syslog(0, "3\n");
637                                 return 0;
638                         }
639                         Dir = (StrBuf*) vDir;
640                         if (strcmp(ChrPtr(Folder->RoomNameParts[i]), 
641                                    ChrPtr(Dir)) != 0)
642                         {
643                                 DeleteHashPos(&it);
644                                 syslog(0, "4\n");
645                                 return 0;
646                         }
647                 }
648                 DeleteHashPos(&it);
649                 return 1;
650         }
651         else {
652                 if (!GetNextHashPos(WCC->Directory, 
653                                     it, &len, &Key, &vDir) ||
654                     (vDir == NULL))
655                 {
656                         DeleteHashPos(&it);
657                         
658                         syslog(0, "5\n");
659                         return WCC->Hdr->HR.dav_depth == 1;
660                 }
661                 DeleteHashPos(&it);
662                 Dir = (StrBuf*) vDir;
663                 if (WCC->Hdr->HR.dav_depth == 0) {
664                         return (strcmp(ChrPtr(Folder->name), 
665                                        ChrPtr(Dir))
666                                 == 0);
667
668                 }
669                 return 0;
670         }
671 }
672
673
674 void 
675 InitModule_ROOMLIST
676 (void)
677 {
678
679         RegisterIterator("ITERATE:THISROOM:WHO_KNOWS", 0, NULL, GetWhoKnowsHash, NULL, DeleteHash, CTX_STRBUF, CTX_NONE, IT_NOFLAG);
680         RegisterIterator("ITERATE:THISROOM:GNET", 1, NULL, GetNetConfigHash, NULL, NULL, CTX_STRBUFARR, CTX_NONE, IT_NOFLAG);
681
682         RegisterIterator("LFLR", 0, NULL, GetFloorListHash, NULL, NULL, CTX_FLOORS, CTX_NONE, IT_FLAG_DETECT_GROUPCHANGE);
683         RegisterIterator("LKRA", 0, NULL, GetRoomListHashLKRA, NULL, NULL, CTX_ROOMS, CTX_NONE, IT_FLAG_DETECT_GROUPCHANGE);
684         RegisterIterator("LZRM", 0, NULL, GetZappedRoomListHash, NULL, DeleteHash, CTX_ROOMS, CTX_NONE, IT_FLAG_DETECT_GROUPCHANGE);
685
686         RegisterConditional(HKEY("COND:ROOM:REST:ISSUBROOM"), 0, ConditionalRoomIsRESTSubRoom, CTX_ROOMS);
687
688         RegisterSortFunc(HKEY("byfloorroom"),
689                          NULL, 0,
690                          CompareRoomListByFloorRoomPrivFirst,
691                          CompareRoomListByFloorRoomPrivFirstRev,
692                          GroupchangeRoomListByFloorRoomPrivFirst,
693                          CTX_ROOMS);
694
695 }