]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - classes/User_group.php
merge 0.9.x into 1.0.x
[quix0rs-gnu-social.git] / classes / User_group.php
1 <?php
2 /**
3  * Table Definition for user_group
4  */
5
6 class User_group extends Memcached_DataObject
7 {
8     const JOIN_POLICY_OPEN = 0;
9     const JOIN_POLICY_MODERATE = 1;
10
11     ###START_AUTOCODE
12     /* the code below is auto generated do not remove the above tag */
13
14     public $__table = 'user_group';                      // table name
15     public $id;                              // int(4)  primary_key not_null
16     public $nickname;                        // varchar(64)
17     public $fullname;                        // varchar(255)
18     public $homepage;                        // varchar(255)
19     public $description;                     // text
20     public $location;                        // varchar(255)
21     public $original_logo;                   // varchar(255)
22     public $homepage_logo;                   // varchar(255)
23     public $stream_logo;                     // varchar(255)
24     public $mini_logo;                       // varchar(255)
25     public $design_id;                       // int(4)
26     public $created;                         // datetime   not_null default_0000-00-00%2000%3A00%3A00
27     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
28     public $uri;                             // varchar(255)  unique_key
29     public $mainpage;                        // varchar(255)
30     public $join_policy;                     // tinyint
31     public $force_scope;                     // tinyint
32
33     /* Static get */
34     function staticGet($k,$v=NULL) {
35         return Memcached_DataObject::staticGet('User_group',$k,$v);
36     }
37
38     /* the code above is auto generated do not remove the tag below */
39     ###END_AUTOCODE
40
41     function defaultLogo($size)
42     {
43         static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
44                                   AVATAR_STREAM_SIZE => 'stream',
45                                   AVATAR_MINI_SIZE => 'mini');
46         return Theme::path('default-avatar-'.$sizenames[$size].'.png');
47     }
48
49     function homeUrl()
50     {
51         $url = null;
52         if (Event::handle('StartUserGroupHomeUrl', array($this, &$url))) {
53             // normally stored in mainpage, but older ones may be null
54             if (!empty($this->mainpage)) {
55                 $url = $this->mainpage;
56             } else {
57                 $url = common_local_url('showgroup',
58                                         array('nickname' => $this->nickname));
59             }
60         }
61         Event::handle('EndUserGroupHomeUrl', array($this, &$url));
62         return $url;
63     }
64
65     function getUri()
66     {
67         $uri = null;
68         if (Event::handle('StartUserGroupGetUri', array($this, &$uri))) {
69             if (!empty($this->uri)) {
70                 $uri = $this->uri;
71             } else {
72                 $uri = common_local_url('groupbyid',
73                                         array('id' => $this->id));
74             }
75         }
76         Event::handle('EndUserGroupGetUri', array($this, &$uri));
77         return $uri;
78     }
79
80     function permalink()
81     {
82         $url = null;
83         if (Event::handle('StartUserGroupPermalink', array($this, &$url))) {
84             $url = common_local_url('groupbyid',
85                                     array('id' => $this->id));
86         }
87         Event::handle('EndUserGroupPermalink', array($this, &$url));
88         return $url;
89     }
90
91     function getNotices($offset, $limit, $since_id=null, $max_id=null)
92     {
93         $stream = new GroupNoticeStream($this);
94
95         return $stream->getNotices($offset, $limit, $since_id, $max_id);
96     }
97
98
99     function allowedNickname($nickname)
100     {
101         static $blacklist = array('new');
102         return !in_array($nickname, $blacklist);
103     }
104
105     function getMembers($offset=0, $limit=null)
106     {
107         $qry =
108           'SELECT profile.* ' .
109           'FROM profile JOIN group_member '.
110           'ON profile.id = group_member.profile_id ' .
111           'WHERE group_member.group_id = %d ' .
112           'ORDER BY group_member.created DESC ';
113
114         if ($limit != null) {
115             if (common_config('db','type') == 'pgsql') {
116                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
117             } else {
118                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
119             }
120         }
121
122         $members = new Profile();
123
124         $members->query(sprintf($qry, $this->id));
125         return $members;
126     }
127
128     /**
129      * Get pending members, who have not yet been approved.
130      *
131      * @param int $offset
132      * @param int $limit
133      * @return Profile
134      */
135     function getRequests($offset=0, $limit=null)
136     {
137         $qry =
138           'SELECT profile.* ' .
139           'FROM profile JOIN group_join_queue '.
140           'ON profile.id = group_join_queue.profile_id ' .
141           'WHERE group_join_queue.group_id = %d ' .
142           'ORDER BY group_join_queue.created DESC ';
143
144         if ($limit != null) {
145             if (common_config('db','type') == 'pgsql') {
146                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
147             } else {
148                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
149             }
150         }
151
152         $members = new Profile();
153
154         $members->query(sprintf($qry, $this->id));
155         return $members;
156     }
157
158     function getMemberCount()
159     {
160         // XXX: WORM cache this
161
162         $members = $this->getMembers();
163         $member_count = 0;
164
165         /** $member->count() doesn't work. */
166         while ($members->fetch()) {
167             $member_count++;
168         }
169
170         return $member_count;
171     }
172
173     function getAdmins($offset=0, $limit=null)
174     {
175         $qry =
176           'SELECT profile.* ' .
177           'FROM profile JOIN group_member '.
178           'ON profile.id = group_member.profile_id ' .
179           'WHERE group_member.group_id = %d ' .
180           'AND group_member.is_admin = 1 ' .
181           'ORDER BY group_member.modified ASC ';
182
183         if ($limit != null) {
184             if (common_config('db','type') == 'pgsql') {
185                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
186             } else {
187                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
188             }
189         }
190
191         $admins = new Profile();
192
193         $admins->query(sprintf($qry, $this->id));
194         return $admins;
195     }
196
197     function getBlocked($offset=0, $limit=null)
198     {
199         $qry =
200           'SELECT profile.* ' .
201           'FROM profile JOIN group_block '.
202           'ON profile.id = group_block.blocked ' .
203           'WHERE group_block.group_id = %d ' .
204           'ORDER BY group_block.modified DESC ';
205
206         if ($limit != null) {
207             if (common_config('db','type') == 'pgsql') {
208                 $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
209             } else {
210                 $qry .= ' LIMIT ' . $offset . ', ' . $limit;
211             }
212         }
213
214         $blocked = new Profile();
215
216         $blocked->query(sprintf($qry, $this->id));
217         return $blocked;
218     }
219
220     function setOriginal($filename)
221     {
222         $imagefile = new ImageFile($this->id, Avatar::path($filename));
223
224         $orig = clone($this);
225         $this->original_logo = Avatar::url($filename);
226         $this->homepage_logo = Avatar::url($imagefile->resize(AVATAR_PROFILE_SIZE));
227         $this->stream_logo = Avatar::url($imagefile->resize(AVATAR_STREAM_SIZE));
228         $this->mini_logo = Avatar::url($imagefile->resize(AVATAR_MINI_SIZE));
229         common_debug(common_log_objstring($this));
230         return $this->update($orig);
231     }
232
233     function getBestName()
234     {
235         return ($this->fullname) ? $this->fullname : $this->nickname;
236     }
237
238     /**
239      * Gets the full name (if filled) with nickname as a parenthetical, or the nickname alone
240      * if no fullname is provided.
241      *
242      * @return string
243      */
244     function getFancyName()
245     {
246         if ($this->fullname) {
247             // TRANS: Full name of a profile or group followed by nickname in parens
248             return sprintf(_m('FANCYNAME','%1$s (%2$s)'), $this->fullname, $this->nickname);
249         } else {
250             return $this->nickname;
251         }
252     }
253
254     function getAliases()
255     {
256         $aliases = array();
257
258         // XXX: cache this
259
260         $alias = new Group_alias();
261
262         $alias->group_id = $this->id;
263
264         if ($alias->find()) {
265             while ($alias->fetch()) {
266                 $aliases[] = $alias->alias;
267             }
268         }
269
270         $alias->free();
271
272         return $aliases;
273     }
274
275     function setAliases($newaliases) {
276
277         $newaliases = array_unique($newaliases);
278
279         $oldaliases = $this->getAliases();
280
281         // Delete stuff that's old that not in new
282
283         $to_delete = array_diff($oldaliases, $newaliases);
284
285         // Insert stuff that's in new and not in old
286
287         $to_insert = array_diff($newaliases, $oldaliases);
288
289         $alias = new Group_alias();
290
291         $alias->group_id = $this->id;
292
293         foreach ($to_delete as $delalias) {
294             $alias->alias = $delalias;
295             $result = $alias->delete();
296             if (!$result) {
297                 common_log_db_error($alias, 'DELETE', __FILE__);
298                 return false;
299             }
300         }
301
302         foreach ($to_insert as $insalias) {
303             $alias->alias = $insalias;
304             $result = $alias->insert();
305             if (!$result) {
306                 common_log_db_error($alias, 'INSERT', __FILE__);
307                 return false;
308             }
309         }
310
311         return true;
312     }
313
314     static function getForNickname($nickname, $profile=null)
315     {
316         $nickname = common_canonical_nickname($nickname);
317
318         // Are there any matching remote groups this profile's in?
319         if ($profile) {
320             $group = $profile->getGroups();
321             while ($group->fetch()) {
322                 if ($group->nickname == $nickname) {
323                     // @fixme is this the best way?
324                     return clone($group);
325                 }
326             }
327         }
328
329         // If not, check local groups.
330
331         $group = Local_group::staticGet('nickname', $nickname);
332         if (!empty($group)) {
333             return User_group::staticGet('id', $group->group_id);
334         }
335         $alias = Group_alias::staticGet('alias', $nickname);
336         if (!empty($alias)) {
337             return User_group::staticGet('id', $alias->group_id);
338         }
339         return null;
340     }
341
342     function getDesign()
343     {
344         return Design::staticGet('id', $this->design_id);
345     }
346
347     function getUserMembers()
348     {
349         // XXX: cache this
350
351         $user = new User();
352         if(common_config('db','quote_identifiers'))
353             $user_table = '"user"';
354         else $user_table = 'user';
355
356         $qry =
357           'SELECT id ' .
358           'FROM '. $user_table .' JOIN group_member '.
359           'ON '. $user_table .'.id = group_member.profile_id ' .
360           'WHERE group_member.group_id = %d ';
361
362         $user->query(sprintf($qry, $this->id));
363
364         $ids = array();
365
366         while ($user->fetch()) {
367             $ids[] = $user->id;
368         }
369
370         $user->free();
371
372         return $ids;
373     }
374
375     static function maxDescription()
376     {
377         $desclimit = common_config('group', 'desclimit');
378         // null => use global limit (distinct from 0!)
379         if (is_null($desclimit)) {
380             $desclimit = common_config('site', 'textlimit');
381         }
382         return $desclimit;
383     }
384
385     static function descriptionTooLong($desc)
386     {
387         $desclimit = self::maxDescription();
388         return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
389     }
390
391     function asAtomEntry($namespace=false, $source=false)
392     {
393         $xs = new XMLStringer(true);
394
395         if ($namespace) {
396             $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
397                            'xmlns:thr' => 'http://purl.org/syndication/thread/1.0');
398         } else {
399             $attrs = array();
400         }
401
402         $xs->elementStart('entry', $attrs);
403
404         if ($source) {
405             $xs->elementStart('source');
406             $xs->element('id', null, $this->permalink());
407             $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
408             $xs->element('link', array('href' => $this->permalink()));
409             $xs->element('updated', null, $this->modified);
410             $xs->elementEnd('source');
411         }
412
413         $xs->element('title', null, $this->nickname);
414         $xs->element('summary', null, common_xml_safe_str($this->description));
415
416         $xs->element('link', array('rel' => 'alternate',
417                                    'href' => $this->permalink()));
418
419         $xs->element('id', null, $this->permalink());
420
421         $xs->element('published', null, common_date_w3dtf($this->created));
422         $xs->element('updated', null, common_date_w3dtf($this->modified));
423
424         $xs->element(
425             'content',
426             array('type' => 'html'),
427             common_xml_safe_str($this->description)
428         );
429
430         $xs->elementEnd('entry');
431
432         return $xs->getString();
433     }
434
435     function asAtomAuthor()
436     {
437         $xs = new XMLStringer(true);
438
439         $xs->elementStart('author');
440         $xs->element('name', null, $this->nickname);
441         $xs->element('uri', null, $this->permalink());
442         $xs->elementEnd('author');
443
444         return $xs->getString();
445     }
446
447     /**
448      * Returns an XML string fragment with group information as an
449      * Activity Streams <activity:subject> element.
450      *
451      * Assumes that 'activity' namespace has been previously defined.
452      *
453      * @return string
454      */
455     function asActivitySubject()
456     {
457         return $this->asActivityNoun('subject');
458     }
459
460     /**
461      * Returns an XML string fragment with group information as an
462      * Activity Streams noun object with the given element type.
463      *
464      * Assumes that 'activity', 'georss', and 'poco' namespace has been
465      * previously defined.
466      *
467      * @param string $element one of 'actor', 'subject', 'object', 'target'
468      *
469      * @return string
470      */
471     function asActivityNoun($element)
472     {
473         $noun = ActivityObject::fromGroup($this);
474         return $noun->asString('activity:' . $element);
475     }
476
477     function getAvatar()
478     {
479         return empty($this->homepage_logo)
480             ? User_group::defaultLogo(AVATAR_PROFILE_SIZE)
481             : $this->homepage_logo;
482     }
483
484     static function register($fields) {
485         if (!empty($fields['userid'])) {
486             $profile = Profile::staticGet('id', $fields['userid']);
487             if ($profile && !$profile->hasRight(Right::CREATEGROUP)) {
488                 common_log(LOG_WARNING, "Attempted group creation from banned user: " . $profile->nickname);
489
490                 // TRANS: Client exception thrown when a user tries to create a group while banned.
491                 throw new ClientException(_('You are not allowed to create groups on this site.'), 403);
492             }
493         }
494
495         // MAGICALLY put fields into current scope
496         // @fixme kill extract(); it makes debugging absurdly hard
497
498         extract($fields);
499
500         $group = new User_group();
501
502         $group->query('BEGIN');
503
504         if (empty($uri)) {
505             // fill in later...
506             $uri = null;
507         }
508         if (empty($mainpage)) {
509             $mainpage = common_local_url('showgroup', array('nickname' => $nickname));
510         }
511
512         $group->nickname    = $nickname;
513         $group->fullname    = $fullname;
514         $group->homepage    = $homepage;
515         $group->description = $description;
516         $group->location    = $location;
517         $group->uri         = $uri;
518         $group->mainpage    = $mainpage;
519         $group->created     = common_sql_now();
520
521         if (isset($fields['join_policy'])) {
522             $group->join_policy = intval($fields['join_policy']);
523         } else {
524             $group->join_policy = 0;
525         }
526
527         if (isset($fields['force_scope'])) {
528             $group->force_scope = intval($fields['force_scope']);
529         } else {
530             $group->force_scope = 0;
531         }
532
533         if (Event::handle('StartGroupSave', array(&$group))) {
534
535             $result = $group->insert();
536
537             if (!$result) {
538                 common_log_db_error($group, 'INSERT', __FILE__);
539                 // TRANS: Server exception thrown when creating a group failed.
540                 throw new ServerException(_('Could not create group.'));
541             }
542
543             if (!isset($uri) || empty($uri)) {
544                 $orig = clone($group);
545                 $group->uri = common_local_url('groupbyid', array('id' => $group->id));
546                 $result = $group->update($orig);
547                 if (!$result) {
548                     common_log_db_error($group, 'UPDATE', __FILE__);
549                     // TRANS: Server exception thrown when updating a group URI failed.
550                     throw new ServerException(_('Could not set group URI.'));
551                 }
552             }
553
554             $result = $group->setAliases($aliases);
555
556             if (!$result) {
557                 // TRANS: Server exception thrown when creating group aliases failed.
558                 throw new ServerException(_('Could not create aliases.'));
559             }
560
561             $member = new Group_member();
562
563             $member->group_id   = $group->id;
564             $member->profile_id = $userid;
565             $member->is_admin   = 1;
566             $member->created    = $group->created;
567
568             $result = $member->insert();
569
570             if (!$result) {
571                 common_log_db_error($member, 'INSERT', __FILE__);
572                 // TRANS: Server exception thrown when setting group membership failed.
573                 throw new ServerException(_('Could not set group membership.'));
574             }
575
576             self::blow('profile:groups:%d', $userid);
577             
578             if ($local) {
579                 $local_group = new Local_group();
580
581                 $local_group->group_id = $group->id;
582                 $local_group->nickname = $nickname;
583                 $local_group->created  = common_sql_now();
584
585                 $result = $local_group->insert();
586
587                 if (!$result) {
588                     common_log_db_error($local_group, 'INSERT', __FILE__);
589                     // TRANS: Server exception thrown when saving local group information failed.
590                     throw new ServerException(_('Could not save local group info.'));
591                 }
592             }
593
594             $group->query('COMMIT');
595
596             Event::handle('EndGroupSave', array($group));
597         }
598
599         return $group;
600     }
601
602     /**
603      * Handle cascading deletion, on the model of notice and profile.
604      *
605      * This should handle freeing up cached entries for the group's
606      * id, nickname, URI, and aliases. There may be other areas that
607      * are not de-cached in the UI, including the sidebar lists on
608      * GroupsAction
609      */
610     function delete()
611     {
612         if ($this->id) {
613
614             // Safe to delete in bulk for now
615
616             $related = array('Group_inbox',
617                              'Group_block',
618                              'Group_member',
619                              'Related_group');
620
621             Event::handle('UserGroupDeleteRelated', array($this, &$related));
622
623             foreach ($related as $cls) {
624
625                 $inst = new $cls();
626                 $inst->group_id = $this->id;
627
628                 if ($inst->find()) {
629                     while ($inst->fetch()) {
630                         $dup = clone($inst);
631                         $dup->delete();
632                     }
633                 }
634             }
635
636             // And related groups in the other direction...
637             $inst = new Related_group();
638             $inst->related_group_id = $this->id;
639             $inst->delete();
640
641             // Aliases and the local_group entry need to be cleared explicitly
642             // or we'll miss clearing some cache keys; that can make it hard
643             // to create a new group with one of those names or aliases.
644             $this->setAliases(array());
645             $local = Local_group::staticGet('group_id', $this->id);
646             if ($local) {
647                 $local->delete();
648             }
649
650             // blow the cached ids
651             self::blow('user_group:notice_ids:%d', $this->id);
652
653         } else {
654             common_log(LOG_WARN, "Ambiguous user_group->delete(); skipping related tables.");
655         }
656         parent::delete();
657     }
658
659     function isPrivate()
660     {
661         return ($this->join_policy == self::JOIN_POLICY_MODERATE &&
662                 $this->force_scope == 1);
663     }
664 }