Group._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * $Id: Group.pike,v 1.8 2010/01/26 12:09:01 astra Exp $
18  */
19 inherit "/classes/Object" : __object;
20 inherit "/base/member" : __member;
21 #include <macros.h>
22 #include <roles.h>
23 #include <assert.h>
24 #include <classes.h>
25 #include <events.h>
26 #include <access.h>
27 #include <database.h>
28 #include <attributes.h>
29 #include <types.h>
30 class Group : public Object,member{
31 public:
32 
33 
34 
35 
36 import Roles;
37 
38 
39 private int iGroupRoles; /* special privileges of the group */
40 private RoleList groupRoles; /* all roles for this groups */
41 
42 private string sGroupName; /* the groups name */
43 private string sGroupPW; /* password for the group */
44  array aoGroupMembers; /* members of the group */
45  array aoInvites; /* invited users */
46  array aPending; /* waiting users */
47  array aoExclusiveGroups; /* groups with mutual exclusive members*/
48  object oParent; /* the groups parent */
49 
50 
51 #define GROUP_ADMIN_ACCESS SANCTION_ALL
52 
53 /**
54  * Initialization of the object.
55  *
56  * @see create_object
57  */
58 protected:
59  void init()
60 {
61  ::init();
62  ::init_member(); // groups are also group members !
63  aoGroupMembers = ({ });
64  aoInvites = ({ });
65  aPending = ({ });
66  groupRoles = RoleList();
67 
68  sGroupPW = "";
69  add_data_storage(STORE_GROUP,retrieve_group_data, restore_group_data);
70 }
71 
72 public:
73 
74 /**
75  * Constructor of the group.
76  *
77  * @see duplicate
78  */
79 protected:
80  void
81 create_object()
82 {
83  ::create_object();
84  iGroupRoles = 0;
85  sGroupName = "";
86 }
87 
88 public:
89 
90 /**
91  * Create a duplicate of this object.
92  *
93  * @return the duplicate object
94  * @see create_object
95  */
96 mapping do_duplicate(void|mapping vars)
97 {
98  mapping dup = ::do_duplicate(vars);
99 
100  foreach( aoGroupMembers, object member ) {
101  dup_obj->add_member(member);
102  }
103  return dup;
104 }
105 
106 /**
107  * Set the parent group of this group.
108  *
109  * @param object grp - the new parent
110  * @see get_parent
111  */
112 void set_parent(object grp)
113 {
114  if ( _Server->is_a_factory(CALLER) ) {
115  oParent = grp;
116  require_save(STORE_GROUP);
117  }
118 }
119 
120 /**
121  * Get the parent group. The group is identified by
122  * (parent->identifier).(groups name)
123  *
124  * @return the parent group or zero
125  */
126 object get_parent()
127 {
128  return oParent;
129 }
130 
131 object get_workroom() {
132  return do_query_attribute(GROUP_WORKROOM);
133 }
134 
135 /**
136  * Get the sub groups of this group (the groups that are members of this
137  * group).
138  *
139  * @see get_sub_groups_recursive
140  * @see get_members
141  * @see get_members_recursive
142  *
143  * @return an array containing all groups that are members of this group
144  */
145 array get_sub_groups()
146 {
147  return get_members( CLASS_GROUP );
148 }
149 
150 /**
151  * Get the sub groups of this group recursively (the groups that are members
152  * of this group or any group that is a member of this group, and so on).
153  *
154  * @see get_subgroups
155  * @see get_members
156  * @see get_members_recursive
157  *
158  * @return an array containing all groups that are members of this group,
159  * recursively
160  */
161 array get_sub_groups_recursive() {
162  return get_members_recursive( CLASS_GROUP );
163 }
164 
165 /**
166  * Called when created to register the group in the database.
167  *
168  * @param string name - register as name
169  */
170 protected:
171  void database_registration(string name)
172 {
173  sGroupName = name;
174  "Registration of group " + name + " failed !");
175  require_save(STORE_GROUP);
176 }
177 
178 public:
179 
180 /**
181  * Set the group's name.
182  *
183  * @param string name - the new name of the group
184  */
185 void set_group_name(string name)
186 {
187  if ( CALLER != _Server->get_factory(CLASS_GROUP) )
188  THROW("Invalid call to set_group_name !", E_ACCESS);
189  sGroupName = name;
190  string old_name = do_query_attribute( OBJ_NAME );
191  mixed new_name = name / ".";
192  new_name = new_name[ sizeof(new_name) - 1 ];
193  object workroom = do_query_attribute(GROUP_WORKROOM);
194  if ( objectp(workroom) ) {
195  if ( workroom->query_attribute(OBJ_NAME) == old_name+"'s workarea" )
196  workroom->set_attribute( OBJ_NAME, new_name + "'s workarea" );
197  else
198  workroom->update_path();
199  }
200  require_save(STORE_GROUP);
201 }
202 
203 string get_name()
204 {
205  return get_identifier();
206 }
207 
208 string get_steam_email()
209 {
210  return get_identifier() + "@" + _Server->get_server_name();
211 }
212 
213 /**
214  * Get the group's name.
215  *
216  * @return the name of the group
217  */
218 string get_group_name()
219 {
220  return sGroupName;
221 }
222 
223 /**
224  * The destructor of the group object. Removes all members for instance.
225  *
226  * @see create
227  */
228 protected:
229  void
230 delete_object()
231 {
232  // let the factory delete the group, because it can do this with the
233  // permissions of the group creator:
234 }
235 
236 public:
237 
238 /**
239  * This is an internal function that can only to be called by the group
240  * factory. It recursively deletes the group.
241  */
242 void low_delete_object()
243 {
244  if ( !_Server->is_a_factory(CALLER) )
245  steam_error("Illegal call to Group.low_delete_object !");
246 
247  object member;
248  object obj;
249 
250  // delete subgroups:
251  foreach ( get_sub_groups(), object grp ) {
252  remove_member( grp );
253  grp->delete();
254  }
255  // remove remaining members:
256  foreach ( aoGroupMembers, member ) {
257  }
258  // remove from parents:
259  foreach ( get_groups(), object grp ) {
260  }
261  // delete workroom and calendar:
262  obj = query_attribute( GROUP_WORKROOM );
263  if ( objectp(obj) && !obj->delete() )
264  werror( "Failed to delete group '%s' workroom (object id %d)\n",
265  get_identifier(), obj->get_object_id() );
266  obj = query_attribute( GROUP_CALENDAR );
267  if ( objectp(obj) && !obj->delete() )
268  werror( "Failed to delete group '%s' calendar (object id %d)\n",
269  get_identifier(), obj->get_object_id() );
270 
271  __object::delete_object();
272  __member::delete_object();
273 }
274 
275 bool leave_group(object grp, void|object parent)
276 {
277  return ::leave_group(grp);
278 }
279 
280 bool join_group(object grp)
281 {
282  // if the user is currently member of no group move the group
283  int sz = sizeof(get_groups());
284  bool result = ::join_group(grp);
285  if ( sz==0 ) {
286  oParent = grp;
287  }
288  return result;
289 }
290 
291 
292 /**
293  * Checks if the group features some special privileges.
294  *
295  * @param permission - does the group feature this permission?
296  * @return true or false
297  * @see add_permission
298  */
299 final bool
300 features(int permission, void|mixed ctx)
301 {
302  if ( iGroupRoles & permission )
303  return true;
304  return groupRoles->check(permission, ctx);
305 }
306 
307 /**
308  * Returns an integer describing the special privileges of the group.
309  *
310  * @return permissions of the group
311  * @see add_permission
312  */
313 final int
314 get_permission()
315 {
316  return iGroupRoles;
317 }
318 
319 /**
320  * Add special privileges to the group.
321  *
322  * @param permission - add the permission to roles of group
323  * @see features
324  */
325 final bool
326 add_permission(int permission)
327 {
328  try_event(EVENT_GRP_ADD_PERMISSION, CALLER, permission);
329  iGroupRoles |= permission;
330  require_save(STORE_GROUP);
331  run_event(EVENT_GRP_ADD_PERMISSION, CALLER, permission);
332  return true;
333 }
334 
335 /**
336  * Set new default permissions for the group. These are role permissions
337  * like read-everything,write-everything,etc. which is usually only
338  * valid for the ADMIN gorup.
339  *
340  * @param int permission - permission bit array.
341  * @return true or throw and error.
342  * @see get_permission
343  */
344 final bool
345 set_permission(int permission)
346 {
347  try_event(EVENT_GRP_ADD_PERMISSION, CALLER, permission);
348  Role r = Role("general", permission, 0);
349  groupRoles->add(r);
350  iGroupRoles = permission;
351  require_save(STORE_GROUP);
352  run_event(EVENT_GRP_ADD_PERMISSION, CALLER, permission);
353  return true;
354 }
355 
356 
357 final void add_role(Role r)
358 {
359  try_event(EVENT_GRP_ADD_PERMISSION, CALLER, r);
360  groupRoles->add(r);
361  require_save(STORE_GROUP);
362  run_event(EVENT_GRP_ADD_PERMISSION, CALLER, r);
363 }
364 
365 final RoleList get_roles()
366 {
367  return groupRoles;
368 }
369 
370 /**
371  * Check if a given user is member of this group.
372  *
373  * @param user - the user to check
374  * @return true of false
375  * @see add_member
376  * @see remove_member
377  */
378 final bool
379 is_member(object user)
380 {
381  for ( int i = sizeof(aoGroupMembers) - 1; i >= 0; i-- ) {
382  if ( aoGroupMembers[i] == user )
383  return true;
384  }
385  return false;
386 }
387 
388 /**
389  * Check if a given user is member of this group or a subgroup
390  *
391  * @param user - the user to check
392  * @return true of false
393  * @see add_member
394  * @see remove_member
395  */
396 final bool
397 is_virtual_member(object user)
398 {
399  for ( int i = sizeof(aoGroupMembers) - 1; i >= 0; i-- ) {
400  if ( aoGroupMembers[i] == user )
401  return true;
402  else if ( aoGroupMembers[i]->get_object_class() & CLASS_GROUP )
403  if ( aoGroupMembers[i]->is_virtual_member(user) )
404  return true;
405  }
406  return false;
407 }
408 
409 final bool
410 is_virtual_parent(object group)
411 {
412  if (!objectp(oParent))
413  return false;
414 
415  if (oParent == group)
416  return true;
417  return oParent->is_virtual_parent(group);
418 }
419 
420 
421 
422 /**
423  * See if a user is admin of this group. It doesnt require
424  * membership in the group.
425  *
426  * @param user - the user to check for admin
427  * @return true of false
428  * @see is_member
429  */
430 final bool
431 is_admin(object user)
432 {
433  if ( !objectp(user) )
434  return false;
435  ASSERTINFO(IS_PROXY(user), "User is not a proxy !");
436  return (query_sanction(user)&GROUP_ADMIN_ACCESS) == GROUP_ADMIN_ACCESS;
437 }
438 
439 /**
440  * Get all admins of a group. Other groups might be admins of a group
441  * too.
442  *
443  * @return array of admin objects (Users)
444  * @see is_admin
445  */
446 final array get_admins()
447 {
448  array admins = ({ });
449 
450  foreach( aoGroupMembers, object member) {
451  if ( is_admin(member) ) {
452  if ( member->get_object_class() & CLASS_GROUP )
453  admins += member->get_admins();
454  admins += ({ member });
455  }
456  }
457  return admins;
458 }
459 
460 
461 /**
462  * Check a group password against a string passed.
463  * @param string pass - the group password
464  * @return 1 - ok, 0 - failed
465  */
466 final bool
467 check_group_pw(string pass)
468 {
469  return stringp(pass) && stringp(sGroupPW) && strlen(sGroupPW)!=0 && pass==sGroupPW;
470 }
471 
472 
473 /**
474  * Checks whether the group is password protected.
475  * @return 1 if the group has a password, 0 if it has no password
476  */
477 final bool has_password () {
478  return stringp(sGroupPW) && sizeof(sGroupPW) > 0;
479 }
480 
481 
482 /**
483  * Add a new member to this group. Optionally a password can be
484  * passed to the function so the user joins with a password directly.
485  *
486  * @param user - new member
487  * @param string|void pass - the group password
488  * @see remove_member
489  * @see is_member
490  * @return 1 - ok, 0 - failed, -1 pending, -2 pending failed
491  */
492 final int
493 add_member(object user, string|void pass)
494 {
495  int i;
496 
497  ASSERTINFO(IS_PROXY(user), "User is not a proxy !");
498  // guest cannot be in any group
499  steam_error("The guest user cannot be part of any group !");
500 
501  return 0;
502 
503  object caller = CALLER;
504 
505  /* run the event
506  * Pass right password to security...
507  * The user may add himself to the group with the appropriate password.
508  * Invited users may also join
509  */
510  try_event(EVENT_ADD_MEMBER, caller, user,
511  (user == this_user() || geteuid() == user) &&
512  (search(aoInvites, user) >= 0 ||
513  (stringp(pass) && stringp(sGroupPW) && strlen(sGroupPW) != 0 && pass == sGroupPW)));
514 
515  // make sure there won't be any loops
516  if ( _SECURITY->valid_group(user) ) {
517  array grp;
518  array mems;
519 
520  grp = ({ user });
521  i = 0;
522  while ( i < sizeof(grp) ) {
523  mems = grp[i]->get_members();
524  foreach(mems, object m) {
525  LOG("Member:"+m->get_identifier()+"\n");
526  THROW("add_member() recursion detected !",
527  E_ERROR|E_LOOP);
528  if ( _SECURITY->valid_group(m) )
529  grp += ({ m });
530  }
531  i++;
532  }
533  // is this group virtually a parent - recursion possible
534  if (is_virtual_parent(user)) {
535  steam_error("Cannot add a group that is a virtual parent of this group!");
536  }
537  }
538  // kick user from all exclusive parent groups sub-groups of this group ;)
539  foreach( get_groups(), object group) {
540  // user joins a subgroup
541  LOG("Group to check:" + group->get_identifier()+"\n");
542  if ( group->query_attribute(GROUP_EXCLUSIVE_SUBGROUPS) == 1 ) {
543  foreach ( group->get_members(), object xgroup )
544  if ( xgroup->get_object_class() & CLASS_GROUP &&
545  xgroup->is_member(user) )
546  xgroup->remove_member(user);
547  }
548  }
549  int size = do_query_attribute(GROUP_MAXSIZE);
550  if ( size == 0 ||
551  ( user->get_object_class() & CLASS_GROUP) ||
552  count_members() < size )
553  {
554  do_add_member(user);
555  run_event(EVENT_ADD_MEMBER, CALLER, user);
556  return 1;
557  }
558  else
559  return add_pending(user, pass);
560 }
561 
562 protected:
563  void do_add_member(object user)
564 {
565  steam_error("The user cannot join the group !");
566  aoGroupMembers += ({ user });
567 
568  // remove membership request:
569  remove_from_attribute(GROUP_MEMBERSHIP_REQS, user);
570 
571  if ( arrayp(aoInvites) )
572  aoInvites -= ({ user });
573 
574  /* Users must be able to read the group for tell and say events */
575  set_sanction(user, query_sanction(user)|SANCTION_READ);
576 
577  require_save(STORE_ACCESS);
578  require_save(STORE_GROUP);
579 }
580 
581 public:
582 
583 /**
584  * Get the number of members (users only)
585  *
586  * @return the number of member users of this group
587  */
588 int count_members()
589 {
590  int cnt = 0;
591  foreach(aoGroupMembers, object member)
592  if ( member->get_object_class() & CLASS_USER )
593  cnt++;
594  return cnt;
595 }
596 
597 
598 
599 /**
600  * Add a request to become member to this group. That is the current
601  * use will become member of the group.
602  *
603  */
604 void
605 add_membership_request(void|object user)
606 {
607  if ( !objectp(user) )
608  user = this_user();
609  if ( user == USER("guest") )
610  steam_error("Cannot add guest user ...");
611  do_append_attribute(GROUP_MEMBERSHIP_REQS, user);
612 }
613 
614 /**
615  * Check whether a given user requested membership for this group.
616  *
617  * @param object user - the user to check
618  * @return true or false
619  */
620 bool requested_membership(object user)
621 {
622  return !arrayp(do_query_attribute(GROUP_MEMBERSHIP_REQS)) ||
623  search(do_query_attribute(GROUP_MEMBERSHIP_REQS), user) >= 0;
624 }
625 
626 /**
627  * Remove a request for membership from the list of membership
628  * requests of this group.
629  *
630  * @param object user - remove the request of the user.
631  * @see add_membership_request
632  */
633 void remove_membership_request(object user)
634 {
635  if ( (user==this_user()) || (user==geteuid())
636  || is_admin( this_user() ) || is_admin( geteuid() ) ) {
637  remove_from_attribute(GROUP_MEMBERSHIP_REQS, user);
638  }
639 }
640 
641 /**
642  * Get the array (copied) of membership requests for this group.
643  *
644  * @return array of user objects requesting membership.
645  */
646 array get_requests()
647 {
648  return copy_value(do_query_attribute(GROUP_MEMBERSHIP_REQS));
649 }
650 
651 
652 /**
653  * Promote a user to group administration. The user does not need to be member.
654  *
655  * @param object user - the new admin user of this group
656  * @see is_admin
657  */
658 void set_admin(object user)
659 {
660  sanction_object(user, GROUP_ADMIN_ACCESS);
661 }
662 
663 /**
664  * Remove an administrator from the groups administration
665  *
666  * @param object user - the admin user
667  * @see is_admin
668  */
669 void remove_admin(object user)
670 {
671  sanction_object(user, 0);
672 }
673 
674 /**
675  * allows free entry to this group (everyone can join)
676  *
677  * @param int entry - boolean value if users can join for free or not
678  *
679  */
680 void set_free_entry(int entry)
681 {
682  if ( entry )
683  sanction_object(GROUP("everyone"), SANCTION_INSERT);
684  else
685  sanction_object(GROUP("everyone"), 0);
686 }
687 
688 
689 
690 /**
691  * Invite a user to join this group. If the current user has the
692  * appropriate permissions the given user will be marked as invited
693  * and may join for free.
694  *
695  * @param object user - the user to invite.
696  * @see is_invited
697  */
698 void invite_user(object user)
699 {
700  try_event(EVENT_ADD_MEMBER, CALLER, user, 0);
701  if ( search(aoInvites, user) >= 0 )
702  THROW("Failed to invite user - user already invited !", E_ERROR);
703  aoInvites += ({ user });
704  require_save(STORE_GROUP);
705  run_event(EVENT_ADD_MEMBER, CALLER, user);
706 }
707 
708 void remove_invite(object user)
709 {
710  try_event(EVENT_REMOVE_MEMBER, CALLER, user);
711  if ( search(aoInvites, user) == -1 )
712  THROW("Failed to remove invitation for user - user not invited !", E_ERROR);
713  aoInvites -= ({ user });
714  require_save(STORE_GROUP);
715  run_event(EVENT_REMOVE_MEMBER, CALLER, user);
716 }
717 
718 /**
719  * Check if a given user is invited to join this group.
720  *
721  * @param object user - the user to check.
722  * @return true of false.
723  * @see invite_user
724  */
725 bool is_invited(object user)
726 {
727  if ( !arrayp(aoInvites) )
728  aoInvites = ({ });
729 
730  return search(aoInvites, user) >= 0;
731 }
732 
733 
734 /**
735  * Get all invited users of this group.
736  *
737  * @return array of invited users
738  */
739 array get_invited()
740 {
741  return copy_value(aoInvites);
742 }
743 
744 public void check_consistency()
745 {
746  bool consistent = true;
747 
748  foreach(aoGroupMembers, object o) {
749  if (o->status() < 0 || o->status() == 3) {
750  consistent = false;
751  break;
752  }
753  }
754  if (!consistent) {
755  array fixed_members = ({ });
756  foreach(aoGroupMembers, object o) {
757  if (o->status() >= 0 && o->status() != 3) {
758  fixed_members += ({ o });
759  }
760  }
761  aoGroupMembers = fixed_members;
762  require_save(STORE_GROUP);
763  }
764 }
765 
766 /**
767  * remove a member from the group.
768  *
769  * @param user - the member to remove
770  * @return if successfully
771  * @see add_member
772  */
773 final bool
774 remove_member(object user)
775 {
776  LOG("remove_member");
777  ASSERTINFO(!objectp(user) || IS_PROXY(user), "User is not a proxy !");
778 
779  check_consistency();
780  if ( !is_member(user) && !is_pending(user) )
781  return false;
782 
783  if (is_pending(user))
784  {
785  LOG("is pending");
786  remove_pending(user);
787  require_save(STORE_GROUP);
788  }
789  else
790  {
791  LOG("actual member?");
792  try_event(EVENT_REMOVE_MEMBER, CALLER, user);
793  set_sanction(user, 0);
794  aoGroupMembers -= ({ user });
795  require_save(STORE_USER);
796  require_save(STORE_ACCESS);
797  run_event(EVENT_REMOVE_MEMBER, CALLER, user);
798 
799  // try to fill group with first pending
800  if (arrayp(aPending) && sizeof(aPending) > 0 )
801  {
802  catch {
803  add_member(aPending[0][0], aPending[0][1]);
804  string msg = do_query_attribute(GROUP_MSG_ACCEPT);
805  if (!msg)
806  msg = "You have been accepted to group:"+
807  do_query_attribute(OBJ_NAME);
808  aPending[0][0]->message(msg);
809  aPending = aPending[1..];
810  require_save(STORE_USER);
811  };
812  }
813  }
814  return true;
815 }
816 
817 
818 /**
819  * Returns the groups members.
820  *
821  * @see get_members_recursive
822  * @see get_sub_groups
823  * @see get_sub_groups_recursive
824  * @see add_member
825  *
826  * @param classes (optional) limit the result to the specified class (e.g.
827  * CLASS_USER or CLASS_GROUP)
828  * @return the groups members
829  *
830  */
831 final array
832 get_members(int|void classes)
833 {
834  if ( classes != 0 ) {
835  array members = ({ });
836  foreach(aoGroupMembers, object o) {
837  if ( o->get_object_class() & classes )
838  members += ({ o });
839  }
840  return members;
841  }
842  return copy_value(aoGroupMembers);
843 }
844 
845 
846 /**
847  * get the class of the object
848  *
849  * @return the class of the object
850  */
851 int
852 get_object_class()
853 {
854  return ::get_object_class() | CLASS_GROUP;
855 }
856 
857 /**
858  * Returns the members of the group and all subgroups (recursively).
859  * If no parameter is specified, then this returns only users, not
860  * sub group objects.
861  *
862  * @see get_members
863  * @see get_sub_groups
864  * @see get_sub_groups_recursive
865  * @see add_member
866  *
867  * @param classes (optional) limit the result to the specified class (e.g.
868  * CLASS_USER or CLASS_GROUP), default: CLASS_USER
869  * @return members of this group and all subgroups
870  *
871  */
872 array get_members_recursive ( void|int classes ) {
873  if ( zero_type(classes) ) classes = CLASS_USER; // backwards compatibility
874  array result = ({ });
875  foreach ( get_members(), object obj ) {
876  if ( !objectp(obj) || obj->status() < 0 ) continue; // only valid objs
877  int obj_class = obj->get_object_class();
878  if ( obj_class & classes )
879  result |= ({ obj });
880  if ( obj_class & CLASS_GROUP)
881  result |= obj->get_members_recursive( classes );
882  }
883  return result;
884 }
885 
886 
887 
888 
889 /**
890  * Get the mails of a user.
891  *
892  * @return array of objects of mail documents
893  */
894 array get_mails(void|int from_obj, void|int to_obj)
895 {
896  array mails = get_annotations();
897  if ( sizeof(mails) == 0 )
898  return mails;
899 
900  if ( !intp(to_obj) )
901  to_obj = sizeof(mails);
902  if ( !intp(from_obj) )
903  from_obj = 1;
904  return mails[from_obj-1..to_obj-1];
905 }
906 
907 
908 /**
909  * Returns the group's emails, optionally filtered by object class,
910  * attribute values or pagination.
911  * The description of the filters and sort options can be found in the
912  * filter_objects_array() function of the "searching" module.
913  *
914  * Example:
915  * Return the 10 newest mails whose subjects do not start with "{SPAM}",
916  * sorted by date.
917  * get_mails_filtered(
918  * ({ // filters:
919  * ({ "-", "attribute", "OBJ_DESC", "prefix", "{SPAM}" }),
920  * ({ "+", "class", CLASS_DOCUMENT }),
921  * }),
922  * ({ // sort:
923  * ({ ">", "attribute", "OBJ_CREATION_TIME" })
924  * }), 0, 10 );
925  *
926  * @param mail_folder (optional) mail folder from which to return the mails
927  * (if not specified, then the inbox of the group is used)
928  * @param filters (optional) an array of filters (each an array as described
929  * in the "searching" module) that specify which objects to return
930  * @param sort (optional) an array of sort entries (each an array as described
931  * in the "searching" module) that specify the order of the items
932  * @param offset (optional) only return the objects starting at (and including)
933  * this index
934  * @param length (optional) only return a maximum of this many objects
935  * @return a mapping ([ "objects":({...}), "total":nr, "length":nr,
936  * "start":nr, "page":nr ]), where the "objects" value is an array of
937  * objects that match the specified filters, sort order and pagination.
938  * The other indices contain pagination information ("total" is the total
939  * number of objects after filtering but before applying "length", "length"
940  * is the requested number of items to return (as in the parameter list),
941  * "start" is the start index of the result in the total number of objects,
942  * and "page" is the page number (starting with 1) of pages with "length"
943  * objects each, or 0 if invalid).
944  */
945 mapping get_mails_paginated ( object|void mail_folder, array|void filters, array|void sort, int|void offset, int|void length )
946 {
947  return get_module( "searching" )->paginate_object_array(
948  mail_folder->get_annotations(), filters, sort, offset, length );
949 }
950 
951 /**
952  * Returns the group's emails, optionally filtered, sorted and limited by
953  * offset and length. This returns the same as the "objects" index in the
954  * result of get_mails_paginated() and is here for compatibility reasons and
955  * ease of use (if you don't need pagination information).
956  *
957  * @see get_mails_paginated
958  */
959 array get_mails_filtered ( object|void mail_folder, array|void filters, array|void sort, int|void offset, int|void length )
960 {
961  return get_mails_paginated( mail_folder, filters, sort, offset, length )["objects"];
962 }
963 
964 object get_mailbox()
965 {
966 }
967 
968 
969 /**
970  * Send an internal mail to all members of this group.
971  * If the sending user has activated sent mail storage, then a copy of the
972  * mail will be stored in her sent mail folder.
973  *
974  * @param msg the message body (can be a plaintext or html string, a document
975  * or a mapping)
976  * @param subject an optional subject
977  * @param sender an optional sender mail address
978  * @param mimetype optional mime type of the message body
979  */
980 final void mail(string|object|mapping msg, string|mapping|void subject, void|string sender, void|string mimetype)
981 {
982  object user = geteuid() || this_user();
983  if ( !objectp(user) ) user = _ROOT;
984  if ( mappingp(subject) )
985  subject = subject[do_query_attribute("language")||"english"];
986  if ( objectp(msg) && !stringp(subject) )
987  subject = msg->query_attribute( OBJ_DESC ) || msg->get_identifier();
988  if ( !stringp(subject) )
989  subject = "Message from " + user->get_identifier();
990  if ( !stringp(mimetype) )
991  mimetype = "text/html";
992 
993  object message;
994  if ( objectp(msg) ) {
995  message = msg;
996  // OBJ_DESC is subject of messages
997  string desc = msg->query_attribute(OBJ_DESC);
998  if ( !stringp(desc) || desc == "" )
999  msg->set_attribute(OBJ_DESC, msg->get_identifier());
1000  }
1001  else {
1002  object factory = _Server->get_factory(CLASS_DOCUMENT);
1003  message = factory->execute( ([ "name": replace(subject, "/", "_"),
1004  "mimetype": mimetype,
1005  ]) );
1006  if ( mappingp(msg) )
1007  msg = msg[do_query_attribute("language")||"english"];
1008  message->set_attribute(OBJ_DESC, subject);
1009  if ( lower_case(mimetype) == "text/html" && stringp(msg) ) {
1010  // check whether <html> and <body> tags are missing:
1011  msg = Messaging.fix_html( msg );
1012  }
1013  message->set_content(msg);
1014  }
1015 
1016  array targets = get_members_recursive();
1017  string mailsetting = do_query_attribute(GROUP_MAIL_SETTINGS) || "open";
1018  if ( mailsetting == "closed" ) {
1019  if ( !is_member(geteuid() || this_user()) )
1020  steam_user_error("Group accepts only messages from members!");
1021  }
1022  object tmod = get_module("tasks");
1023  object sending_user = this_user();
1024  if ( !objectp(sending_user) ) sending_user = geteuid();
1025  if ( objectp(tmod) ) {
1026  Task.Task task = Task.Task(send_mail);
1027  task->params = ({ message, subject, sender, mimetype, targets, headers,
1028  sending_user });
1029  tmod->run_task(task);
1030  }
1031  else {
1032  send_mail( message, subject, sender, mimetype, targets, headers,
1033  sending_user );
1034  }
1035 }
1036 
1037 protected:
1038  void
1039 send_mail(string|object|mapping msg, string|mapping|void subject, void|string sender, void|string mimetype, array targets, mapping headers, object user)
1040 {
1041  object mail_obj = do_send_mail( msg, subject, sender, mimetype, targets,
1042  headers, user );
1043  if ( objectp(mail_obj) && objectp(user) && user->is_storing_sent_mail() &&
1044  objectp(user->get_sent_mail_folder()) ) {
1045  object mail_copy = mail_obj->duplicate();
1046  if ( objectp(mail_copy) ) {
1047  mail_copy->sanction_object( user, SANCTION_ALL );
1048  object old_euid = geteuid();
1049  mixed euid_err = catch(seteuid( user ));
1050  get_module( "table:read-documents" )->download_document( 0, mail_copy, UNDEFINED ); // mark as read
1051  foreach ( mail_copy->get_annotations(), object ann )
1052  get_module( "table:read-documents" )->download_document( 0, ann, UNDEFINED ); // mark as read
1053  if ( !euid_err ) seteuid( old_euid );
1054  user->get_sent_mail_folder()->add_annotation( mail_copy );
1055  }
1056  }
1057 }
1058 
1059 public:
1060 
1061 protected:
1062  object do_send_mail(string|object|mapping msg, string|mapping|void subject, void|string sender, void|string mimetype, array targets, mapping headers, object user)
1063 {
1064  object msg_obj;
1065  array failed = ({ });
1066  foreach (targets, object member) {
1067  if ( !objectp(member) ) continue;
1068  mixed err = catch {
1069  mixed mailmsg;
1070  if ( objectp(msg) ) {
1071  mailmsg = msg->duplicate();
1072  }
1073  else
1074  mailmsg = msg;
1075 
1076  object tmp_msg_obj;
1077  if ( mappingp(headers) ) {
1078  tmp_msg_obj = member->do_mail(mailmsg, subject, sender, mimetype, headers);
1079  }
1080  else {
1081  tmp_msg_obj = member->do_mail(mailmsg, subject, sender, mimetype);
1082  }
1083  if ( !objectp(msg_obj) && objectp(tmp_msg_obj) )
1084  msg_obj = tmp_msg_obj;
1085  };
1086  if ( err ) {
1087  FATAL("Error while sending group mail to %O: %O\n%O",
1088  member,
1089  err[0], err[1]);
1090  failed += ({ member->get_identifier() + "( " + member->get_name()+ " )" });
1091  }
1092  }
1093  // also notify the user about failed mailing
1094  if ( sizeof(failed) > 0 ) {
1095  if ( objectp(user) ) {
1096  user->do_mail(
1097  sprintf("Failed to send message '%O' to the following recipients:" +
1098  failed * "<br />",
1099  subject),
1100  "Failed to send message", "postmaster", "text/html");
1101  }
1102  }
1103 
1104  // store message as annotation
1105  mixed err = catch {
1106  do_add_annotation(msg->duplicate());
1107  };
1108  if ( err ) {
1109  FATAL("Failed to store annotation on group: %O\n%O", err[0], err[1]);
1110  }
1111 
1112 
1113  return msg_obj;
1114 }
1115 
1116 public:
1117 
1118 /**
1119  * Set a new password for this group. A password is used to
1120  * allow users to join the group without waiting for someone to
1121  * accept their membership request.
1122  *
1123  * @param string pw - the new group password.
1124  * @return true or false
1125  */
1126 bool set_group_password(string pw)
1127 {
1128  THROW("Unauthorized call to set_group_password() !", E_ACCESS);
1129  LOG("set_group_password("+pw+")");
1130  sGroupPW = pw;
1131  require_save(STORE_GROUP);
1132  return true;
1133 }
1134 
1135 /**
1136  * get the data of the group for saving
1137  *
1138  * @return array of group data
1139 private:
1140  * @see restore_group_data
1141  */
1142 mapping
1143 private:
1144 retrieve_group_data()
1145 {
1146  ASSERTINFO(CALLER == _Database,
1147  "retrieve_group_data() must be called by database !");
1148  return ([
1149  "GroupMembers":aoGroupMembers,
1150  "GroupRoles":iGroupRoles,
1151  "Groups": aoGroups,
1152  "GroupPassword": sGroupPW,
1153  "GroupInvites": aoInvites,
1154  "GroupName": sGroupName,
1155  "GroupPending": aPending,
1156  "Parent": oParent,
1157  "ExclusiveGroups": aoExclusiveGroups,
1158  "Roles": groupRoles->save(),
1159  ]);
1160 }
1161 
1162 public:
1163 
1164 /**
1165  * restore the data of the group: must be called by Database
1166  *
1167  * @param data - the data to restore
1168 private:
1169  * @see retrieve_group_data
1170  */
1171 void
1172 private:
1173 restore_group_data(mixed data)
1174 {
1175  ASSERTINFO(CALLER == _Database, "Caller must be database !");
1176 
1177  aoGroupMembers = data["GroupMembers"];
1178  iGroupRoles = data["GroupRoles"];
1179  aoGroups = data["Groups"];
1180  sGroupPW = data["GroupPassword"];
1181  aoInvites = data["GroupInvites"];
1182  sGroupName = data["GroupName"];
1183  aPending = data["GroupPending"];
1184  oParent = data["Parent"];
1185  aoExclusiveGroups = data["ExclusiveGroups"];
1186 
1187  // loading the roles of this group
1188  // Role code is in libraries/Roles.pmod
1189  groupRoles = RoleList();
1190  if ( arrayp(data["Roles"]) )
1191  groupRoles->load(data->Roles);
1192 
1193  if ( !stringp(sGroupName) || sGroupName == "undefined" )
1194  sGroupName = get_identifier();
1195  if ( arrayp(aoGroupMembers) )
1196  aoGroupMembers -= ({ 0 });
1197 }
1198 
1199 public:
1200 
1201 /**
1202  * send a message to the group - will only call the SAY_EVENT
1203  *
1204  * @param msg - the message to send
1205  */
1206 void message(string msg)
1207 {
1208  try_event(EVENT_SAY, CALLER, msg);
1209  run_event(EVENT_SAY, CALLER, msg);
1210 }
1211 
1212 /**
1213  * add a user to the pending list, the pendnig list is a list of users
1214  * waiting for acceptance due to the groups size exceeding the GROUP_MAXSIZE
1215  *
1216  * @param user - the user to add
1217  * @param pass - optional password to pass to add_member
1218  * @see add_member
1219  */
1220 protected:
1221 final bool
1222 add_pending(object user, string|void pass)
1223 {
1224  int iSizePending;
1225  return false;
1226 
1227  if (!iSizePending ||(iSizePending > sizeof(aPending)))
1228  {
1229  aPending += ({ ({ user, pass }) });
1230  require_save(STORE_USER);
1231  return -1;
1232  }
1233  return -2;
1234 }
1235 
1236 public:
1237 
1238 /*
1239  * check if a user is already waiting for acceptance on the pending list
1240  * @param user - the user to check for
1241  * @see add_pending
1242  * @see add_member
1243  */
1244 final bool
1245 is_pending(object user)
1246 {
1247  if ( arrayp(aPending) ) {
1248  foreach( aPending, mixed pend_arr )
1249  if ( arrayp(pend_arr) && sizeof(pend_arr) >= 2 )
1250  if ( pend_arr[0] == user )
1251  return true;
1252  }
1253  return false;
1254 }
1255 
1256 final bool
1257 remove_pending(object user)
1258 {
1259  if (arrayp(aPending))
1260  {
1261  mixed res;
1262  res = map(aPending, lambda(mixed a)
1263  { return a[0]->get_object_id();} );
1264  if (res)
1265  {
1266  int p = search(res, user->get_object_id());
1267  if (p!=-1)
1268  {
1269  aPending[p]=0;
1270  aPending -= ({0});
1271  require_save(STORE_USER);
1272  return true;
1273  }
1274  }
1275  }
1276 }
1277 
1278 /*
1279  * get the list of users waiting to be accepted to the group, in case the
1280  * maximum group size is limited.
1281  * @return - (array)object (the users)
1282  *
1283  */
1284 final array get_pending()
1285 {
1286  return map(aPending, lambda(mixed a) { return a[0];} );
1287 }
1288 
1289 
1290 /*
1291  * add a group to the mutual list, A user may be only member to one
1292  * group of this list. Aquiring membership in one of theese groups will
1293  * automatically remove the user from all other groups of this list.
1294  * @param group - the group to add to the cluster
1295  */
1296 final bool add_to_mutual_list(object group)
1297 {
1298  try_event(EVENT_GRP_ADDMUTUAL, CALLER, group);
1299 
1300  foreach(aoExclusiveGroups, object g)
1301  g->low_add_to_mutual_list(group);
1302 
1303  group->low_add_to_mutual_list( aoExclusiveGroups +({this_object()}));
1304  aoExclusiveGroups |= ({ group });
1305 
1306  require_save(STORE_GROUP);
1307 }
1308 
1309 /*
1310  * this function will be called from other groups to indicate, this
1311  * group isn't required to inform other groups about this addition.
1312  * To add a group to the cluster call add_to_mutual_list
1313  * @param group - the group beeing informed
1314  */
1315 final bool low_add_to_mutual_list(array group)
1316 {
1317  ASSERTINFO(_SECURITY && _SECURITY->valid_group(CALLER),
1318  "low_add_to_mutal was called from non group object");
1319  // try_event(EVENT_GRP_ADDMUTUAL, CALLER, group);
1320  // this is not necessary since SECURITY knows about clusters
1321  aoExclusiveGroups |= group;
1322  require_save(STORE_GROUP);
1323 }
1324 
1325 
1326 /*
1327  * get the list of groups connected in a mutual exclusive list
1328  * @return an array of group objects
1329  */
1330 final array get_mutual_list()
1331 {
1332  return copy_value(aoExclusiveGroups);
1333 }
1334 
1335 string get_identifier()
1336 {
1337  if ( stringp(sGroupName) && strlen(sGroupName) > 0 )
1338  return sGroupName;
1339  return query_attribute(OBJ_NAME);
1340 }
1341 
1342 string parent_and_group_name()
1343 {
1344  if ( objectp(get_parent()) )
1345  return get_parent()->query_attribute(OBJ_NAME) + "." +
1346  do_query_attribute(OBJ_NAME);
1347  return do_query_attribute(OBJ_NAME);
1348 }
1349 
1350 bool query_join_everyone()
1351 {
1352  return ((query_sanction(_WORLDUSER) & (SANCTION_READ|SANCTION_INSERT)) ==
1353  (SANCTION_READ|SANCTION_INSERT));
1354 }
1355 
1356 function get_function(string func)
1357 {
1358  object caller = CALLER;
1359  THROW( sprintf("Only database is allowed to get function pointer.\nNOT %O\n%O",
1360  caller, backtrace()), E_ACCESS);
1361  if ( func == "do_add_member" )
1362  return do_add_member;
1363  return ::get_function(func);
1364 }
1365 
1366 
1367 };