autocomplete.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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. /**
  17. * List profiles and groups for autocompletion
  18. *
  19. * @category Plugin
  20. * @package GNUsocial
  21. * @author Craig Andrews <candrews@integralblue.com>
  22. * @author Mikael Nordfeldth <mmn@hethane.se>
  23. * @copyright 2008-2009 StatusNet, Inc.
  24. * @copyright 2009-2013 Free Software Foundation, Inc http://www.fsf.org
  25. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  26. */
  27. defined('GNUSOCIAL') || die();
  28. /**
  29. * List users for autocompletion
  30. *
  31. * This is the form for adding a new g
  32. *
  33. * @category Plugin
  34. * @package GNUsocial
  35. * @author Craig Andrews <candrews@integralblue.com>
  36. * @author Mikael Nordfeldth <mmn@hethane.se>
  37. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  38. */
  39. class AutocompleteAction extends Action
  40. {
  41. protected $needLogin = true;
  42. private $result;
  43. /**
  44. * Last-modified date for page
  45. *
  46. * When was the content of this page last modified? Based on notice,
  47. * profile, avatar.
  48. *
  49. * @return int last-modified date as unix timestamp
  50. */
  51. public function lastModified()
  52. {
  53. $max = 0;
  54. foreach ($this->profiles as $profile) {
  55. $max = max($max, strtotime($profile->modified));
  56. }
  57. foreach ($this->groups as $group) {
  58. $max = max($max, strtotime($group->modified));
  59. }
  60. // but maybe this file has been modified after that and could
  61. // respond differently
  62. return max($max, filemtime(__FILE__));
  63. }
  64. /**
  65. * An entity tag for this page
  66. *
  67. * Shows the ETag for the page, based on the notice ID and timestamps
  68. * for the notice, profile, and avatar. It's weak, since we change
  69. * the date text "one hour ago", etc.
  70. *
  71. * @return string etag
  72. */
  73. public function etag()
  74. {
  75. return '"' . implode(':', array($this->arg('action'),
  76. common_user_cache_hash(),
  77. // the actual string can have funny characters in we don't want
  78. // showing up in the etag
  79. crc32($this->arg('term')),
  80. $this->arg('limit'),
  81. $this->lastModified())) . '"';
  82. }
  83. protected function prepare(array $args=array())
  84. {
  85. // If we die, show short error messages.
  86. GNUsocial::setApi(true);
  87. parent::prepare($args);
  88. $this->groups = [];
  89. $this->profiles = [];
  90. $term = $this->arg('term');
  91. $limit = $this->arg('limit');
  92. // prevent DOS attacks
  93. if ($limit > 200) {
  94. $limit = 200;
  95. }
  96. if (substr($term, 0, 1) === '@') {
  97. //profile search
  98. $term = substr($term, 1);
  99. $user_table = common_database_tablename('user');
  100. $profile = new Profile();
  101. $profile->_join .= sprintf(
  102. "\n" . <<<'END'
  103. LEFT JOIN (
  104. SELECT id FROM %s
  105. UNION ALL
  106. SELECT subscribed AS id FROM subscription WHERE subscriber = %d
  107. ) AS t1 USING (id)
  108. END,
  109. $user_table,
  110. $this->scoped->id
  111. );
  112. $profile->whereAdd('t1.id IS NOT NULL');
  113. $profile->whereAdd('nickname LIKE \'' . trim($profile->escape($term), '\'') . '%\'');
  114. $profile->limit($limit);
  115. if ($profile->find()) {
  116. while ($profile->fetch()) {
  117. $this->profiles[] = clone($profile);
  118. }
  119. }
  120. }
  121. if (substr($term, 0, 1) === '!') {
  122. //group search
  123. $term = substr($term, 1);
  124. $group = new User_group();
  125. //Can't post to groups we're not subscribed to...:
  126. $group->whereAdd(sprintf(
  127. 'id IN (SELECT group_id FROM group_member WHERE profile_id = %d)',
  128. $this->scoped->id
  129. ));
  130. $group->whereAdd('nickname LIKE \'' . trim($group->escape($term), '\'') . '%\'');
  131. $group->limit($limit);
  132. if ($group->find()) {
  133. while ($group->fetch()) {
  134. $this->groups[] = clone($group);
  135. }
  136. }
  137. }
  138. return true;
  139. }
  140. protected function handle()
  141. {
  142. parent::handle();
  143. $results = array();
  144. foreach ($this->profiles as $profile) {
  145. $avatarUrl = $profile->avatarUrl(AVATAR_MINI_SIZE);
  146. $acct = $profile->getAcctUri();
  147. $identifier = explode(':', $profile->getAcctUri(), 2)[1];
  148. $results[] = array(
  149. 'value' => '@'.$identifier,
  150. 'nickname' => $profile->getNickname(),
  151. 'acct_uri' => $acct,
  152. 'label'=> "${identifier} (".$profile->getFullname().")",
  153. 'avatar' => $avatarUrl,
  154. 'type' => 'user'
  155. );
  156. }
  157. foreach ($this->groups as $group) {
  158. $profile = $group->getProfile();
  159. // sigh.... encapsulate this upstream!
  160. if ($group->mini_logo) {
  161. $avatarUrl = $group->mini_logo;
  162. } else {
  163. $avatarUrl = User_group::defaultLogo(AVATAR_MINI_SIZE);
  164. }
  165. $acct = $profile->getAcctUri();
  166. $identifier = explode(':', $profile->getAcctUri(), 2)[1];
  167. $results[] = array(
  168. 'value' => '!'.$identifier,
  169. 'nickname' => $group->getNickname(),
  170. 'acct_uri' => $acct,
  171. 'label'=> "${identifier} (".$group->getFullname().")",
  172. 'avatar' => $avatarUrl,
  173. 'type' => 'group');
  174. }
  175. print json_encode($results);
  176. }
  177. /**
  178. * Is this action read-only?
  179. *
  180. * @param array $args other arguments
  181. *
  182. * @return boolean is read only action?
  183. */
  184. public function isReadOnly($args)
  185. {
  186. return true;
  187. }
  188. }