User_group.php 28 KB

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