GroupPrivateMessagePlugin.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <?php
  2. /**
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2011, StatusNet, Inc.
  5. *
  6. * Private groups for StatusNet 0.9.x
  7. *
  8. * PHP version 5
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. * @category Privacy
  24. * @package StatusNet
  25. * @author Evan Prodromou <evan@status.net>
  26. * @copyright 2011 StatusNet, Inc.
  27. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
  28. * @link http://status.net/
  29. */
  30. if (!defined('STATUSNET')) {
  31. // This check helps protect against security problems;
  32. // your code file can't be executed directly from the web.
  33. exit(1);
  34. }
  35. /**
  36. * Private groups
  37. *
  38. * This plugin allows users to send private messages to a group.
  39. *
  40. * @category Privacy
  41. * @package StatusNet
  42. * @author Evan Prodromou <evan@status.net>
  43. * @copyright 2011 StatusNet, Inc.
  44. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
  45. * @link http://status.net/
  46. */
  47. class GroupPrivateMessagePlugin extends Plugin
  48. {
  49. const PLUGIN_VERSION = '2.0.0';
  50. /**
  51. * Database schema setup
  52. *
  53. * @see Schema
  54. * @see ColumnDef
  55. *
  56. * @return boolean hook value
  57. */
  58. function onCheckSchema()
  59. {
  60. $schema = Schema::get();
  61. // For storing user-submitted flags on profiles
  62. $schema->ensureTable('group_privacy_settings', Group_privacy_settings::schemaDef());
  63. $schema->ensureTable('group_message', Group_message::schemaDef());
  64. $schema->ensureTable('group_message_profile', Group_message_profile::schemaDef());
  65. return true;
  66. }
  67. /**
  68. * Map URLs to actions
  69. *
  70. * @param URLMapper $m path-to-action mapper
  71. *
  72. * @return boolean hook value
  73. */
  74. public function onRouterInitialized(URLMapper $m)
  75. {
  76. $m->connect('group/:nickname/inbox',
  77. ['action' => 'groupinbox'],
  78. ['nickname' => Nickname::DISPLAY_FMT]);
  79. $m->connect('group/message/:id',
  80. ['action' => 'showgroupmessage'],
  81. ['id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}']);
  82. $m->connect('group/:nickname/message/new',
  83. ['action' => 'newgroupmessage'],
  84. ['nickname' => Nickname::DISPLAY_FMT]);
  85. return true;
  86. }
  87. /**
  88. * Add group inbox to the menu
  89. *
  90. * @param Action $action The current action handler. Use this to
  91. * do any output.
  92. *
  93. * @return boolean hook value; true means continue processing, false means stop.
  94. *
  95. * @see Action
  96. */
  97. function onEndGroupGroupNav(Menu $groupnav)
  98. {
  99. $action = $groupnav->action;
  100. $group = $groupnav->group;
  101. $action->menuItem(common_local_url('groupinbox',
  102. array('nickname' => $group->nickname)),
  103. // TRANS: Menu item in group page.
  104. _m('MENU','Inbox'),
  105. // TRANS: Menu title in group page.
  106. _m('Private messages for this group.'),
  107. $action->trimmed('action') == 'groupinbox',
  108. 'nav_group_inbox');
  109. return true;
  110. }
  111. /**
  112. * Create default group privacy settings at group create time
  113. *
  114. * @param User_group $group Group that was just created
  115. *
  116. * @result boolean hook value
  117. */
  118. function onEndGroupSave($group)
  119. {
  120. $gps = new Group_privacy_settings();
  121. $gps->group_id = $group->id;
  122. $gps->allow_privacy = Group_privacy_settings::SOMETIMES;
  123. $gps->allow_sender = Group_privacy_settings::MEMBER;
  124. $gps->created = common_sql_now();
  125. $gps->modified = $gps->created;
  126. // This will throw an exception on error
  127. $gps->insert();
  128. return true;
  129. }
  130. /**
  131. * Show group privacy controls on group edit form
  132. *
  133. * @param GroupEditForm $form form being shown
  134. */
  135. function onEndGroupEditFormData(GroupEditForm $form)
  136. {
  137. $gps = null;
  138. if (!empty($form->group)) {
  139. $gps = Group_privacy_settings::getKV('group_id', $form->group->id);
  140. }
  141. $form->out->elementStart('li');
  142. $form->out->dropdown('allow_privacy',
  143. // TRANS: Dropdown label in group settings page for if group allows private messages.
  144. _m('Private messages'),
  145. // TRANS: Dropdown option in group settings page for allowing private messages.
  146. array(Group_privacy_settings::SOMETIMES => _m('Sometimes'),
  147. // TRANS: Dropdown option in group settings page for allowing private messages.
  148. Group_privacy_settings::ALWAYS => _m('Always'),
  149. // TRANS: Dropdown option in group settings page for allowing private messages.
  150. Group_privacy_settings::NEVER => _m('Never')),
  151. // TRANS: Dropdown title in group settings page for if group allows private messages.
  152. _m('Whether to allow private messages to this group.'),
  153. false,
  154. (empty($gps)) ? Group_privacy_settings::SOMETIMES : $gps->allow_privacy);
  155. $form->out->elementEnd('li');
  156. $form->out->elementStart('li');
  157. $form->out->dropdown('allow_sender',
  158. // TRANS: Dropdown label in group settings page for who can send private messages to the group.
  159. _m('Private senders'),
  160. // TRANS: Dropdown option in group settings page for who can send private messages.
  161. array(Group_privacy_settings::EVERYONE => _m('Everyone'),
  162. // TRANS: Dropdown option in group settings page for who can send private messages.
  163. Group_privacy_settings::MEMBER => _m('Member'),
  164. // TRANS: Dropdown option in group settings page for who can send private messages.
  165. Group_privacy_settings::ADMIN => _m('Admin')),
  166. // TRANS: Dropdown title in group settings page for who can send private messages to the group.
  167. _m('Who can send private messages to the group.'),
  168. false,
  169. (empty($gps)) ? Group_privacy_settings::MEMBER : $gps->allow_sender);
  170. $form->out->elementEnd('li');
  171. return true;
  172. }
  173. function onEndGroupSaveForm(Action $action)
  174. {
  175. // The Action class must contain this method
  176. assert(is_callable(array($action, 'getGroup')));
  177. $gps = null;
  178. if ($action->getGroup() instanceof User_group) {
  179. $gps = Group_privacy_settings::getKV('group_id', $action->getGroup()->id);
  180. }
  181. $orig = null;
  182. if (empty($gps)) {
  183. $gps = new Group_privacy_settings();
  184. $gps->group_id = $action->getGroup()->id;
  185. } else {
  186. $orig = clone($gps);
  187. }
  188. $gps->allow_privacy = $action->trimmed('allow_privacy');
  189. $gps->allow_sender = $action->trimmed('allow_sender');
  190. if (empty($orig)) {
  191. $gps->created = common_sql_now();
  192. $gps->insert();
  193. } else {
  194. $gps->update($orig);
  195. }
  196. return true;
  197. }
  198. /**
  199. * Overload 'd' command to send private messages to groups.
  200. *
  201. * 'd !group word word word' will send the private message
  202. * 'word word word' to the group 'group'.
  203. *
  204. * @param string $cmd Command being run
  205. * @param string $arg Rest of the message (including address)
  206. * @param User $user User sending the message
  207. * @param Command &$result The resulting command object to be run.
  208. *
  209. * @return boolean hook value
  210. */
  211. function onStartInterpretCommand($cmd, $arg, User $user, &$result)
  212. {
  213. if ($cmd == 'd' || $cmd == 'dm') {
  214. $this->debug('Got a d command');
  215. // Break off the first word as the address
  216. $pieces = explode(' ', $arg, 2);
  217. if (count($pieces) == 1) {
  218. $pieces[] = null;
  219. }
  220. list($addr, $msg) = $pieces;
  221. if (!empty($addr) && $addr[0] == '!') {
  222. $result = new GroupMessageCommand($user, substr($addr, 1), $msg);
  223. Event::handle('EndInterpretCommand', array($cmd, $arg, $user, $result));
  224. return false;
  225. }
  226. }
  227. return true;
  228. }
  229. /**
  230. * To add a "Message" button to the group profile page
  231. *
  232. * @param Widget $widget The showgroup action being shown
  233. * @param User_group $group The current group
  234. *
  235. * @return boolean hook value
  236. */
  237. function onEndGroupActionsList(Widget $widget, User_group $group)
  238. {
  239. $cur = common_current_user();
  240. $action = $widget->out;
  241. if (empty($cur)) {
  242. return true;
  243. }
  244. try {
  245. Group_privacy_settings::ensurePost($cur, $group);
  246. } catch (Exception $e) {
  247. return true;
  248. }
  249. $action->elementStart('li', 'entity_send-a-message');
  250. $action->element('a', array('href' => common_local_url('newgroupmessage', array('nickname' => $group->nickname)),
  251. // TRANS: Title for action in group actions list.
  252. 'title' => _m('Send a direct message to this group.')),
  253. // TRANS: Link text for action in group actions list to send a private message to a group.
  254. _m('LINKTEXT','Message'));
  255. // $form = new GroupMessageForm($action, $group);
  256. // $form->hidden = true;
  257. // $form->show();
  258. $action->elementEnd('li');
  259. return true;
  260. }
  261. /**
  262. * When saving a notice, check its groups. If any of them has
  263. * privacy == always, force a group private message to all mentioned groups.
  264. * If any of the groups disallows private messages, skip it.
  265. *
  266. * @param
  267. */
  268. function onStartNoticeSave(Notice &$notice) {
  269. // Look for group tags
  270. // FIXME: won't work for remote groups
  271. // @fixme if Notice::saveNew is refactored so we can just pull its list
  272. // of groups between processing and saving, make use of it
  273. $count = preg_match_all('/(?:^|\s)!(' . Nickname::DISPLAY_FMT . ')/',
  274. strtolower($notice->content),
  275. $match);
  276. $groups = array();
  277. $ignored = array();
  278. $forcePrivate = false;
  279. $profile = $notice->getProfile();
  280. if ($count > 0) {
  281. /* Add them to the database */
  282. foreach (array_unique($match[1]) as $nickname) {
  283. $group = User_group::getForNickname($nickname, $profile);
  284. if (empty($group)) {
  285. continue;
  286. }
  287. $gps = Group_privacy_settings::forGroup($group);
  288. switch ($gps->allow_privacy) {
  289. case Group_privacy_settings::ALWAYS:
  290. $forcePrivate = true;
  291. // fall through
  292. case Group_privacy_settings::SOMETIMES:
  293. $groups[] = $group;
  294. break;
  295. case Group_privacy_settings::NEVER:
  296. $ignored[] = $group;
  297. break;
  298. }
  299. }
  300. if ($forcePrivate) {
  301. foreach ($ignored as $group) {
  302. common_log(LOG_NOTICE,
  303. "Notice forced to group direct message ".
  304. "but group ".$group->nickname." does not allow them.");
  305. }
  306. $user = User::getKV('id', $notice->profile_id);
  307. if (empty($user)) {
  308. common_log(LOG_WARNING,
  309. "Notice forced to group direct message ".
  310. "but profile ".$notice->profile_id." is not a local user.");
  311. } else {
  312. foreach ($groups as $group) {
  313. Group_message::send($user, $group, $notice->content);
  314. }
  315. }
  316. // Don't save the notice!
  317. // FIXME: this is probably cheating.
  318. // TRANS: Client exception thrown when a private group message has to be forced.
  319. throw new ClientException(sprintf(_m('Forced notice to private group message.')),
  320. 200);
  321. }
  322. }
  323. return true;
  324. }
  325. /**
  326. * Show an indicator that the group is (essentially) private on the group page
  327. *
  328. * @param Action $action The action being shown
  329. * @param User_group $group The group being shown
  330. *
  331. * @return boolean hook value
  332. */
  333. function onEndGroupProfileElements(Action $action, User_group $group)
  334. {
  335. $gps = Group_privacy_settings::forGroup($group);
  336. if ($gps->allow_privacy == Group_privacy_settings::ALWAYS) {
  337. // TRANS: Indicator on the group page that the group is (essentially) private.
  338. $action->element('p', 'privategroupindicator', _m('Private'));
  339. }
  340. return true;
  341. }
  342. function onStartShowExportData(Action $action)
  343. {
  344. if ($action instanceof ShowgroupAction) {
  345. $gps = Group_privacy_settings::forGroup($action->getGroup());
  346. if ($gps->allow_privacy == Group_privacy_settings::ALWAYS) {
  347. return false;
  348. }
  349. }
  350. return true;
  351. }
  352. public function onPluginVersion(array &$versions): bool
  353. {
  354. $versions[] = array('name' => 'GroupPrivateMessage',
  355. 'version' => self::PLUGIN_VERSION,
  356. 'author' => 'Evan Prodromou',
  357. 'homepage' => GNUSOCIAL_ENGINE_REPO_URL . 'tree/master/plugins/GroupPrivateMessage',
  358. 'rawdescription' =>
  359. // TRANS: Plugin description.
  360. _m('Allow posting private messages to groups.'));
  361. return true;
  362. }
  363. }