User_group.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. defined('GNUSOCIAL') || die();
  17. /**
  18. * Table Definition for user_group
  19. */
  20. class User_group extends Managed_DataObject
  21. {
  22. const JOIN_POLICY_OPEN = 0;
  23. const JOIN_POLICY_MODERATE = 1;
  24. const CACHE_WINDOW = 201;
  25. ###START_AUTOCODE
  26. /* the code below is auto generated do not remove the above tag */
  27. public $__table = 'user_group'; // table name
  28. public $id; // int(4) primary_key not_null
  29. public $profile_id; // int(4) primary_key not_null
  30. public $nickname; // varchar(64)
  31. public $fullname; // varchar(191) not 255 because utf8mb4 takes more space
  32. public $homepage; // varchar(191) not 255 because utf8mb4 takes more space
  33. public $description; // text
  34. public $location; // varchar(191) not 255 because utf8mb4 takes more space
  35. public $original_logo; // varchar(191) not 255 because utf8mb4 takes more space
  36. public $homepage_logo; // varchar(191) not 255 because utf8mb4 takes more space
  37. public $stream_logo; // varchar(191) not 255 because utf8mb4 takes more space
  38. public $mini_logo; // varchar(191) not 255 because utf8mb4 takes more space
  39. public $created; // datetime()
  40. public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
  41. public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
  42. public $mainpage; // varchar(191) not 255 because utf8mb4 takes more space
  43. public $join_policy; // tinyint
  44. public $force_scope; // tinyint
  45. /* the code above is auto generated do not remove the tag below */
  46. ###END_AUTOCODE
  47. public function getObjectType()
  48. {
  49. return ActivityObject::GROUP;
  50. }
  51. public static function schemaDef()
  52. {
  53. return array(
  54. 'fields' => array(
  55. 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'),
  56. 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'),
  57. 'nickname' => array('type' => 'varchar', 'length' => 64, 'description' => 'nickname for addressing'),
  58. 'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name'),
  59. 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'),
  60. 'description' => array('type' => 'text', 'description' => 'group description'),
  61. 'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'related physical location, if any'),
  62. 'original_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'original size logo'),
  63. 'homepage_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'homepage (profile) size logo'),
  64. 'stream_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'stream-sized logo'),
  65. 'mini_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'mini logo'),
  66. 'created' => array('type' => 'datetime', 'description' => 'date this record was created'),
  67. 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
  68. 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'),
  69. 'mainpage' => array('type' => 'varchar', 'length' => 191, 'description' => 'page for group info to link to'),
  70. 'join_policy' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=open; 1=requires admin approval'),
  71. 'force_scope' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=never,1=sometimes,-1=always'),
  72. ),
  73. 'primary key' => array('id'),
  74. 'unique keys' => array(
  75. 'user_group_uri_key' => array('uri'),
  76. // when it's safe and everyone's run upgrade.php 'user_profile_id_key' => array('profile_id'),
  77. ),
  78. 'foreign keys' => array(
  79. 'user_group_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
  80. ),
  81. 'indexes' => array(
  82. 'user_group_nickname_idx' => array('nickname'),
  83. 'user_group_created_id_idx' => array('created', 'id'),
  84. 'user_group_profile_id_idx' => array('profile_id'), //make this unique in future
  85. ),
  86. );
  87. }
  88. protected $_profile = array();
  89. /**
  90. * @return Profile
  91. *
  92. * @throws GroupNoProfileException if user has no profile
  93. */
  94. public function getProfile()
  95. {
  96. if (!isset($this->_profile[$this->profile_id])) {
  97. $profile = Profile::getKV('id', $this->profile_id);
  98. if (!$profile instanceof Profile) {
  99. throw new GroupNoProfileException($this);
  100. }
  101. $this->_profile[$this->profile_id] = $profile;
  102. }
  103. return $this->_profile[$this->profile_id];
  104. }
  105. public function getNickname()
  106. {
  107. return $this->getProfile()->getNickname();
  108. }
  109. public function getFullname()
  110. {
  111. return $this->getProfile()->getFullname();
  112. }
  113. public static function defaultLogo($size)
  114. {
  115. static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
  116. AVATAR_STREAM_SIZE => 'stream',
  117. AVATAR_MINI_SIZE => 'mini');
  118. return Theme::path('default-avatar-'.$sizenames[$size].'.png');
  119. }
  120. public function homeUrl()
  121. {
  122. return $this->getProfile()->getUrl();
  123. }
  124. public function getUri()
  125. {
  126. $uri = null;
  127. if (Event::handle('StartUserGroupGetUri', array($this, &$uri))) {
  128. if (!empty($this->uri)) {
  129. $uri = $this->uri;
  130. } elseif ($this->isLocal()) {
  131. $uri = common_local_url('groupbyid', ['id' => $this->id]);
  132. }
  133. }
  134. Event::handle('EndUserGroupGetUri', array($this, &$uri));
  135. return $uri;
  136. }
  137. public function permalink()
  138. {
  139. $url = null;
  140. if (Event::handle('StartUserGroupPermalink', array($this, &$url))) {
  141. if ($this->isLocal()) {
  142. $url = common_local_url('groupbyid', ['id' => $this->id]);
  143. }
  144. }
  145. Event::handle('EndUserGroupPermalink', array($this, &$url));
  146. return $url;
  147. }
  148. public function getNotices($offset, $limit, $since_id = null, $max_id = null)
  149. {
  150. // FIXME: Get the Profile::current() some other way, to avoid
  151. // possible confusion between current session and queue process.
  152. $stream = new GroupNoticeStream($this, Profile::current());
  153. return $stream->getNotices($offset, $limit, $since_id, $max_id);
  154. }
  155. public function getMembers($offset = 0, $limit = null)
  156. {
  157. $ids = null;
  158. if (is_null($limit) || $offset + $limit > User_group::CACHE_WINDOW) {
  159. $ids = $this->getMemberIDs($offset, $limit);
  160. } else {
  161. $key = sprintf('group:member_ids:%d', $this->id);
  162. $window = self::cacheGet($key);
  163. if ($window === false) {
  164. $window = $this->getMemberIDs(0, User_group::CACHE_WINDOW);
  165. self::cacheSet($key, $window);
  166. }
  167. $ids = array_slice($window, $offset, $limit);
  168. }
  169. return Profile::multiGet('id', $ids);
  170. }
  171. public function getMemberIDs($offset = 0, $limit = null)
  172. {
  173. $gm = new Group_member();
  174. $gm->selectAdd();
  175. $gm->selectAdd('profile_id');
  176. $gm->group_id = $this->id;
  177. $gm->orderBy('created DESC, profile_id DESC');
  178. if (!is_null($limit)) {
  179. $gm->limit($offset, $limit);
  180. }
  181. $ids = array();
  182. if ($gm->find()) {
  183. while ($gm->fetch()) {
  184. $ids[] = $gm->profile_id;
  185. }
  186. }
  187. return $ids;
  188. }
  189. /**
  190. * Get pending members, who have not yet been approved.
  191. *
  192. * @param int $offset
  193. * @param int $limit
  194. * @return Profile
  195. */
  196. public function getRequests($offset = 0, $limit = null)
  197. {
  198. $rq = new Group_join_queue();
  199. $rq->group_id = $this->id;
  200. $members = new Profile();
  201. $members->joinAdd(['id', $rq, 'profile_id']);
  202. if ($limit != null) {
  203. $members->limit($offset, $limit);
  204. }
  205. $members->find();
  206. return $members;
  207. }
  208. public function getAdminCount()
  209. {
  210. $block = new Group_member();
  211. $block->group_id = $this->id;
  212. $block->is_admin = true;
  213. return $block->count();
  214. }
  215. public function getMemberCount()
  216. {
  217. $key = sprintf("group:member_count:%d", $this->id);
  218. $cnt = self::cacheGet($key);
  219. if (is_integer($cnt)) {
  220. return (int) $cnt;
  221. }
  222. $mem = new Group_member();
  223. $mem->group_id = $this->id;
  224. // XXX: why 'distinct'?
  225. $cnt = (int) $mem->count('distinct profile_id');
  226. self::cacheSet($key, $cnt);
  227. return $cnt;
  228. }
  229. public function getBlockedCount()
  230. {
  231. // XXX: WORM cache this
  232. $block = new Group_block();
  233. $block->group_id = $this->id;
  234. return $block->count();
  235. }
  236. public function getQueueCount()
  237. {
  238. // XXX: WORM cache this
  239. $queue = new Group_join_queue();
  240. $queue->group_id = $this->id;
  241. return $queue->count();
  242. }
  243. // offset is null because DataObject wants it, 0 would mean no results
  244. public function getAdmins($offset = null, $limit = null)
  245. {
  246. $admins = new Profile();
  247. $admins->joinAdd(['id', 'group_member:profile_id']);
  248. $admins->whereAdd(sprintf(
  249. 'group_member.group_id = %d AND group_member.is_admin IS TRUE',
  250. $this->getID()
  251. ));
  252. $admins->orderBy('group_member.modified, group_member.profile_id');
  253. $admins->limit($offset, $limit);
  254. $admins->find();
  255. return $admins;
  256. }
  257. // offset is null because DataObject wants it, 0 would mean no results
  258. public function getBlocked($offset = null, $limit = null)
  259. {
  260. $blocked = new Profile();
  261. $blocked->joinAdd(array('id', 'group_block:blocked'));
  262. $blocked->whereAdd(sprintf('group_block.group_id = %u', $this->id));
  263. $blocked->orderBy('group_block.modified DESC, group_block.blocked DESC');
  264. $blocked->limit($offset, $limit);
  265. $blocked->find();
  266. return $blocked;
  267. }
  268. public function setOriginal($filename)
  269. {
  270. // This should be handled by the Profile->setOriginal function so user and group avatars are handled the same
  271. $imagefile = new ImageFile(null, Avatar::path($filename));
  272. $sizes = array('homepage_logo' => AVATAR_PROFILE_SIZE,
  273. 'stream_logo' => AVATAR_STREAM_SIZE,
  274. 'mini_logo' => AVATAR_MINI_SIZE);
  275. $orig = clone($this);
  276. $this->original_logo = Avatar::url($filename);
  277. foreach ($sizes as $name=>$size) {
  278. $filename = Avatar::filename(
  279. $this->profile_id,
  280. image_type_to_extension($imagefile->preferredType()),
  281. $size,
  282. common_timestamp()
  283. );
  284. $imagefile->resizeTo(Avatar::path($filename), array('width'=>$size, 'height'=>$size));
  285. $this->$name = Avatar::url($filename);
  286. }
  287. common_debug(common_log_objstring($this));
  288. return $this->update($orig);
  289. }
  290. public function getBestName()
  291. {
  292. return ($this->fullname) ? $this->fullname : $this->nickname;
  293. }
  294. /**
  295. * Gets the full name (if filled) with nickname as a parenthetical, or the nickname alone
  296. * if no fullname is provided.
  297. *
  298. * @return string
  299. */
  300. public function getFancyName()
  301. {
  302. if ($this->fullname) {
  303. // TRANS: Full name of a profile or group followed by nickname in parens
  304. return sprintf(_m('FANCYNAME', '%1$s (%2$s)'), $this->fullname, $this->nickname);
  305. } else {
  306. return $this->nickname;
  307. }
  308. }
  309. public function getAliases()
  310. {
  311. $aliases = array();
  312. // XXX: cache this
  313. $alias = new Group_alias();
  314. $alias->group_id = $this->id;
  315. if ($alias->find()) {
  316. while ($alias->fetch()) {
  317. $aliases[] = $alias->alias;
  318. }
  319. }
  320. $alias->free();
  321. return $aliases;
  322. }
  323. public function setAliases($newaliases)
  324. {
  325. $newaliases = array_unique($newaliases);
  326. $oldaliases = $this->getAliases();
  327. // Delete stuff that's old that not in new
  328. $to_delete = array_diff($oldaliases, $newaliases);
  329. // Insert stuff that's in new and not in old
  330. $to_insert = array_diff($newaliases, $oldaliases);
  331. $alias = new Group_alias();
  332. $alias->group_id = $this->id;
  333. foreach ($to_delete as $delalias) {
  334. $alias->alias = $delalias;
  335. $result = $alias->delete();
  336. if (!$result) {
  337. common_log_db_error($alias, 'DELETE', __FILE__);
  338. return false;
  339. }
  340. }
  341. foreach ($to_insert as $insalias) {
  342. if ($insalias === $this->nickname) {
  343. continue;
  344. }
  345. $alias->alias = Nickname::normalize($insalias, true);
  346. $result = $alias->insert();
  347. if (!$result) {
  348. common_log_db_error($alias, 'INSERT', __FILE__);
  349. return false;
  350. }
  351. }
  352. return true;
  353. }
  354. public static function getForNickname($nickname, Profile $profile = null)
  355. {
  356. $nickname = Nickname::normalize($nickname);
  357. // Are there any matching remote groups this profile's in?
  358. if ($profile instanceof Profile) {
  359. $group = $profile->getGroups(0, null);
  360. while ($group instanceof User_group && $group->fetch()) {
  361. if ($group->nickname == $nickname) {
  362. // @fixme is this the best way?
  363. return clone($group);
  364. }
  365. }
  366. }
  367. // If not, check local groups.
  368. $group = Local_group::getKV('nickname', $nickname);
  369. if ($group instanceof Local_group) {
  370. return User_group::getKV('id', $group->group_id);
  371. }
  372. $alias = Group_alias::getKV('alias', $nickname);
  373. if ($alias instanceof Group_alias) {
  374. return User_group::getKV('id', $alias->group_id);
  375. }
  376. return null;
  377. }
  378. public function getUserMembers()
  379. {
  380. // XXX: cache this
  381. $user = new User();
  382. $user->query(sprintf(
  383. 'SELECT id FROM %1$s INNER JOIN group_member ' .
  384. 'ON %1$s.id = group_member.profile_id ' .
  385. 'WHERE group_member.group_id = %2$d ',
  386. $user->escapedTableName(),
  387. $this->id
  388. ));
  389. $ids = [];
  390. while ($user->fetch()) {
  391. $ids[] = $user->id;
  392. }
  393. $user->free();
  394. return $ids;
  395. }
  396. public static function maxDescription()
  397. {
  398. $desclimit = common_config('group', 'desclimit');
  399. // null => use global limit (distinct from 0!)
  400. if (is_null($desclimit)) {
  401. $desclimit = common_config('site', 'textlimit');
  402. }
  403. return $desclimit;
  404. }
  405. public static function descriptionTooLong($desc)
  406. {
  407. $desclimit = self::maxDescription();
  408. return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
  409. }
  410. public function asAtomEntry($namespace = false, $source = false)
  411. {
  412. $xs = new XMLStringer(true);
  413. if ($namespace) {
  414. $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
  415. 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0');
  416. } else {
  417. $attrs = array();
  418. }
  419. $xs->elementStart('entry', $attrs);
  420. if ($source) {
  421. $xs->elementStart('source');
  422. $xs->element('id', null, $this->permalink());
  423. $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
  424. $xs->element('link', array('href' => $this->permalink()));
  425. $xs->element('updated', null, $this->modified);
  426. $xs->elementEnd('source');
  427. }
  428. $xs->element('title', null, $this->nickname);
  429. $xs->element('summary', null, common_xml_safe_str($this->description));
  430. $xs->element('link', array('rel' => 'alternate',
  431. 'href' => $this->permalink()));
  432. $xs->element('id', null, $this->permalink());
  433. $xs->element('published', null, common_date_w3dtf($this->created));
  434. $xs->element('updated', null, common_date_w3dtf($this->modified));
  435. $xs->element(
  436. 'content',
  437. array('type' => 'html'),
  438. common_xml_safe_str($this->description)
  439. );
  440. $xs->elementEnd('entry');
  441. return $xs->getString();
  442. }
  443. public function asAtomAuthor()
  444. {
  445. $xs = new XMLStringer(true);
  446. $xs->elementStart('author');
  447. $xs->element('name', null, $this->nickname);
  448. $xs->element('uri', null, $this->permalink());
  449. $xs->elementEnd('author');
  450. return $xs->getString();
  451. }
  452. /**
  453. * Returns an XML string fragment with group information as an
  454. * Activity Streams noun object with the given element type.
  455. *
  456. * Assumes that 'activity', 'georss', and 'poco' namespace has been
  457. * previously defined.
  458. *
  459. * @param string $element one of 'actor', 'subject', 'object', 'target'
  460. *
  461. * @return string
  462. */
  463. public function asActivityNoun($element)
  464. {
  465. $noun = ActivityObject::fromGroup($this);
  466. return $noun->asString('activity:' . $element);
  467. }
  468. public function getAvatar()
  469. {
  470. return empty($this->homepage_logo)
  471. ? User_group::defaultLogo(AVATAR_PROFILE_SIZE)
  472. : $this->homepage_logo;
  473. }
  474. public static function register($fields)
  475. {
  476. if (!empty($fields['userid'])) {
  477. $profile = Profile::getKV('id', $fields['userid']);
  478. if ($profile && !$profile->hasRight(Right::CREATEGROUP)) {
  479. common_log(LOG_WARNING, "Attempted group creation from banned user: " . $profile->nickname);
  480. // TRANS: Client exception thrown when a user tries to create a group while banned.
  481. throw new ClientException(_('You are not allowed to create groups on this site.'), 403);
  482. }
  483. }
  484. $fields['nickname'] = Nickname::normalize($fields['nickname']);
  485. // MAGICALLY put fields into current scope
  486. // @fixme kill extract(); it makes debugging absurdly hard
  487. $defaults = [
  488. 'nickname' => null,
  489. 'fullname' => null,
  490. 'homepage' => null,
  491. 'description' => null,
  492. 'location' => null,
  493. 'uri' => null,
  494. 'mainpage' => null,
  495. 'aliases' => [],
  496. 'userid' => null,
  497. ];
  498. $fields = array_merge($defaults, $fields);
  499. extract($fields);
  500. $group = new User_group();
  501. if (empty($uri)) {
  502. // fill in later...
  503. $uri = null;
  504. }
  505. if (empty($mainpage)) {
  506. $mainpage = common_local_url('showgroup', array('nickname' => $nickname));
  507. }
  508. // We must create a new, incrementally assigned profile_id
  509. $profile = new Profile();
  510. $profile->nickname = $nickname;
  511. $profile->fullname = $fullname;
  512. $profile->profileurl = $mainpage;
  513. $profile->homepage = $homepage;
  514. $profile->bio = $description;
  515. $profile->location = $location;
  516. $profile->created = common_sql_now();
  517. $group->nickname = $profile->nickname;
  518. $group->fullname = $profile->fullname;
  519. $group->homepage = $profile->homepage;
  520. $group->description = $profile->bio;
  521. $group->location = $profile->location;
  522. $group->mainpage = $profile->profileurl;
  523. $group->created = $profile->created;
  524. $profile->query('START TRANSACTION');
  525. $id = $profile->insert();
  526. if ($id === false) {
  527. $profile->query('ROLLBACK');
  528. throw new ServerException(_('Profile insertion failed'));
  529. }
  530. $group->profile_id = $id;
  531. $group->uri = $uri;
  532. if (isset($fields['join_policy'])) {
  533. $group->join_policy = intval($fields['join_policy']);
  534. } else {
  535. $group->join_policy = 0;
  536. }
  537. if (isset($fields['force_scope'])) {
  538. $group->force_scope = intval($fields['force_scope']);
  539. } else {
  540. $group->force_scope = 0;
  541. }
  542. if (Event::handle('StartGroupSave', array(&$group))) {
  543. $result = $group->insert();
  544. if ($result === false) {
  545. common_log_db_error($group, 'INSERT', __FILE__);
  546. // TRANS: Server exception thrown when creating a group failed.
  547. throw new ServerException(_('Could not create group.'));
  548. }
  549. if (!isset($uri) || empty($uri)) {
  550. $orig = clone($group);
  551. $group->uri = common_local_url('groupbyid', array('id' => $group->id));
  552. $result = $group->update($orig);
  553. if (!$result) {
  554. common_log_db_error($group, 'UPDATE', __FILE__);
  555. // TRANS: Server exception thrown when updating a group URI failed.
  556. throw new ServerException(_('Could not set group URI.'));
  557. }
  558. }
  559. $result = $group->setAliases($aliases);
  560. if (!$result) {
  561. // TRANS: Server exception thrown when creating group aliases failed.
  562. throw new ServerException(_('Could not create aliases.'));
  563. }
  564. $member = new Group_member();
  565. $member->group_id = $group->id;
  566. $member->profile_id = $userid;
  567. $member->is_admin = true;
  568. $member->created = $group->created;
  569. $result = $member->insert();
  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. self::blow('profile:groups:%d', $userid);
  576. if ($local) {
  577. $local_group = new Local_group();
  578. $local_group->group_id = $group->id;
  579. $local_group->nickname = $nickname;
  580. $local_group->created = common_sql_now();
  581. $result = $local_group->insert();
  582. if (!$result) {
  583. common_log_db_error($local_group, 'INSERT', __FILE__);
  584. // TRANS: Server exception thrown when saving local group information failed.
  585. throw new ServerException(_('Could not save local group info.'));
  586. }
  587. }
  588. Event::handle('EndGroupSave', array($group));
  589. }
  590. $profile->query('COMMIT');
  591. return $group;
  592. }
  593. /**
  594. * Handle cascading deletion, on the model of notice and profile.
  595. *
  596. * This should handle freeing up cached entries for the group's
  597. * id, nickname, URI, and aliases. There may be other areas that
  598. * are not de-cached in the UI, including the sidebar lists on
  599. * GroupsAction
  600. */
  601. public function delete($useWhere = false)
  602. {
  603. if (empty($this->id)) {
  604. common_log(LOG_WARNING, "Ambiguous User_group->delete(); skipping related tables.");
  605. return parent::delete($useWhere);
  606. }
  607. // Safe to delete in bulk for now
  608. $related = array('Group_inbox',
  609. 'Group_block',
  610. 'Group_member',
  611. 'Related_group');
  612. Event::handle('UserGroupDeleteRelated', array($this, &$related));
  613. foreach ($related as $cls) {
  614. $inst = new $cls();
  615. $inst->group_id = $this->id;
  616. if ($inst->find()) {
  617. while ($inst->fetch()) {
  618. $dup = clone($inst);
  619. $dup->delete();
  620. }
  621. }
  622. }
  623. // And related groups in the other direction...
  624. $inst = new Related_group();
  625. $inst->related_group_id = $this->id;
  626. $inst->delete();
  627. // Aliases and the local_group entry need to be cleared explicitly
  628. // or we'll miss clearing some cache keys; that can make it hard
  629. // to create a new group with one of those names or aliases.
  630. $this->setAliases(array());
  631. // $this->isLocal() but we're using the resulting object
  632. $local = Local_group::getKV('group_id', $this->id);
  633. if ($local instanceof Local_group) {
  634. $local->delete();
  635. }
  636. $result = parent::delete($useWhere);
  637. try {
  638. $profile = $this->getProfile();
  639. $profile->delete();
  640. } catch (GroupNoProfileException $unp) {
  641. common_log(
  642. LOG_INFO,
  643. "Group {$this->nickname} has no profile; continuing deletion."
  644. );
  645. }
  646. // blow the cached ids
  647. self::blow('user_group:notice_ids:%d', $this->id);
  648. return $result;
  649. }
  650. public function update($dataObject=false)
  651. {
  652. // Whenever the User_group is updated, find the Local_group
  653. // and update its nickname too.
  654. if ($this->nickname != $dataObject->nickname) {
  655. $local = Local_group::getKV('group_id', $this->id);
  656. if ($local instanceof Local_group) {
  657. common_debug("Updating Local_group ({$this->id}) nickname from {$dataObject->nickname} to {$this->nickname}");
  658. $local->setNickname($this->nickname);
  659. }
  660. }
  661. // Also make sure the Profile table is up to date!
  662. $fields = array(/*group field => profile field*/
  663. 'nickname' => 'nickname',
  664. 'fullname' => 'fullname',
  665. 'mainpage' => 'profileurl',
  666. 'homepage' => 'homepage',
  667. 'description' => 'bio',
  668. 'location' => 'location',
  669. 'created' => 'created',
  670. 'modified' => 'modified',
  671. );
  672. $profile = $this->getProfile();
  673. $origpro = clone($profile);
  674. foreach ($fields as $gf=>$pf) {
  675. $profile->$pf = $this->$gf;
  676. }
  677. if ($profile->update($origpro) === false) {
  678. throw new ServerException(_('Unable to update profile'));
  679. }
  680. return parent::update($dataObject);
  681. }
  682. public function isPrivate()
  683. {
  684. return ($this->join_policy == self::JOIN_POLICY_MODERATE &&
  685. intval($this->force_scope) === 1);
  686. }
  687. public function isLocal()
  688. {
  689. $local = Local_group::getKV('group_id', $this->id);
  690. return ($local instanceof Local_group);
  691. }
  692. public static function groupsFromText($text, Profile $profile)
  693. {
  694. $groups = array();
  695. /* extract all !group */
  696. $count = preg_match_all(
  697. '/(?:^|\s)!(' . Nickname::DISPLAY_FMT . ')/',
  698. strtolower($text),
  699. $match
  700. );
  701. if (!$count) {
  702. return $groups;
  703. }
  704. foreach (array_unique($match[1]) as $nickname) {
  705. $group = self::getForNickname($nickname, $profile);
  706. if ($group instanceof User_group && $profile->isMember($group)) {
  707. $groups[] = clone($group);
  708. }
  709. }
  710. return $groups;
  711. }
  712. public static function idsFromText($text, Profile $profile)
  713. {
  714. $ids = array();
  715. $groups = self::groupsFromText($text, $profile);
  716. foreach ($groups as $group) {
  717. $ids[$group->id] = true;
  718. }
  719. return array_keys($ids);
  720. }
  721. }