Profile_tag.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 profile_tag
  19. */
  20. class Profile_tag extends Managed_DataObject
  21. {
  22. public $__table = 'profile_tag'; // table name
  23. public $tagger; // int(4) primary_key not_null
  24. public $tagged; // int(4) primary_key not_null
  25. public $tag; // varchar(64) primary_key not_null
  26. public $modified; // datetime() not_null default_CURRENT_TIMESTAMP
  27. public static function schemaDef()
  28. {
  29. return array(
  30. 'fields' => array(
  31. 'tagger' => array('type' => 'int', 'not null' => true, 'description' => 'user making the tag'),
  32. 'tagged' => array('type' => 'int', 'not null' => true, 'description' => 'profile tagged'),
  33. 'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this notice'),
  34. 'modified' => array('type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date the tag was added'),
  35. ),
  36. 'primary key' => array('tagger', 'tagged', 'tag'),
  37. 'foreign keys' => array(
  38. 'profile_tag_tagger_fkey' => array('profile', array('tagger' => 'id')),
  39. 'profile_tag_tagged_fkey' => array('profile', array('tagged' => 'id')),
  40. 'profile_tag_tag_fkey' => array('profile_list', array('tag' => 'tag')),
  41. ),
  42. 'indexes' => array(
  43. 'profile_tag_modified_idx' => array('modified'),
  44. 'profile_tag_tagger_tag_idx' => array('tagger', 'tag'),
  45. 'profile_tag_tagged_idx' => array('tagged'),
  46. ),
  47. );
  48. }
  49. public function links()
  50. {
  51. return array('tagger,tag' => 'profile_list:tagger,tag');
  52. }
  53. public function getMeta()
  54. {
  55. return Profile_list::pkeyGet(array('tagger' => $this->tagger, 'tag' => $this->tag));
  56. }
  57. public static function getSelfTagsArray(Profile $target)
  58. {
  59. return self::getTagsArray($target->getID(), $target->getID(), $target);
  60. }
  61. public static function setSelfTags(Profile $target, array $newtags, array $privacy = [])
  62. {
  63. return self::setTags($target->getID(), $target->getID(), $newtags, $privacy);
  64. }
  65. public static function getTags($tagger, $tagged, $auth_user = null)
  66. {
  67. $profile_list = new Profile_list();
  68. $include_priv = 1;
  69. if (!($auth_user instanceof User ||
  70. $auth_user instanceof Profile) ||
  71. ($auth_user->id !== $tagger)) {
  72. $profile_list->private = false;
  73. $include_priv = 0;
  74. }
  75. $key = sprintf('profile_tag:tagger_tagged_privacy:%d-%d-%d', $tagger, $tagged, $include_priv);
  76. $tags = Profile_list::getCached($key);
  77. if ($tags !== false) {
  78. return $tags;
  79. }
  80. $qry = 'select profile_list.* from profile_list left join '.
  81. 'profile_tag on (profile_list.tag = profile_tag.tag and '.
  82. 'profile_list.tagger = profile_tag.tagger) where '.
  83. 'profile_tag.tagger = %d and profile_tag.tagged = %d ';
  84. $qry = sprintf($qry, $tagger, $tagged);
  85. if (!$include_priv) {
  86. $qry .= ' AND profile_list.private = FALSE';
  87. }
  88. $profile_list->query($qry);
  89. Profile_list::setCache($key, $profile_list);
  90. return $profile_list;
  91. }
  92. public static function getTagsArray($tagger, $tagged, Profile $scoped = null)
  93. {
  94. $ptag = new Profile_tag();
  95. $qry = sprintf(
  96. 'SELECT profile_tag.tag '.
  97. 'FROM profile_tag INNER JOIN profile_list '.
  98. ' ON (profile_tag.tagger = profile_list.tagger ' .
  99. ' and profile_tag.tag = profile_list.tag) ' .
  100. 'WHERE profile_tag.tagger = %d ' .
  101. 'AND profile_tag.tagged = %d ',
  102. $tagger,
  103. $tagged
  104. );
  105. if (!$scoped instanceof Profile || $scoped->getID() !== $tagger) {
  106. $qry .= 'AND profile_list.private = FALSE';
  107. }
  108. $tags = array();
  109. $ptag->query($qry);
  110. while ($ptag->fetch()) {
  111. $tags[] = $ptag->tag;
  112. }
  113. return $tags;
  114. }
  115. public static function setTags($tagger, $tagged, array $newtags, array $privacy = [])
  116. {
  117. $newtags = array_unique($newtags);
  118. $oldtags = self::getTagsArray($tagger, $tagged, Profile::getByID($tagger));
  119. $ptag = new Profile_tag();
  120. // Delete stuff that's in old and not in new
  121. $to_delete = array_diff($oldtags, $newtags);
  122. // Insert stuff that's in new and not in old
  123. $to_insert = array_diff($newtags, $oldtags);
  124. foreach ($to_delete as $deltag) {
  125. self::unTag($tagger, $tagged, $deltag);
  126. }
  127. foreach ($to_insert as $instag) {
  128. $private = isset($privacy[$instag]) ? $privacy[$instag] : false;
  129. self::setTag($tagger, $tagged, $instag, null, $private);
  130. }
  131. return true;
  132. }
  133. # set a single tag
  134. public static function setTag($tagger, $tagged, $tag, $desc=null, $private = false)
  135. {
  136. $ptag = Profile_tag::pkeyGet(array('tagger' => $tagger,
  137. 'tagged' => $tagged,
  138. 'tag' => $tag));
  139. # if tag already exists, return it
  140. if ($ptag instanceof Profile_tag) {
  141. return $ptag;
  142. }
  143. $tagger_profile = Profile::getByID($tagger);
  144. $tagged_profile = Profile::getByID($tagged);
  145. if (Event::handle('StartTagProfile', array($tagger_profile, $tagged_profile, $tag))) {
  146. if (!$tagger_profile->canTag($tagged_profile)) {
  147. // TRANS: Client exception thrown trying to set a tag for a user that cannot be tagged.
  148. throw new ClientException(_('You cannot tag this user.'));
  149. }
  150. $tags = new Profile_list();
  151. $tags->tagger = $tagger;
  152. $count = (int) $tags->count('distinct tag');
  153. if ($count >= common_config('peopletag', 'maxtags')) {
  154. // TRANS: Client exception thrown trying to set more tags than allowed.
  155. throw new ClientException(sprintf(
  156. _('You already have created %d or more tags ' .
  157. 'which is the maximum allowed number of tags. ' .
  158. 'Try using or deleting some existing tags.'),
  159. common_config('peopletag', 'maxtags')
  160. ));
  161. }
  162. $plist = new Profile_list();
  163. $plist->query('BEGIN');
  164. $profile_list = Profile_list::ensureTag($tagger, $tag, $desc, $private);
  165. if ($profile_list->taggedCount() >= common_config('peopletag', 'maxpeople')) {
  166. // TRANS: Client exception thrown when trying to add more people than allowed to a list.
  167. throw new ClientException(sprintf(
  168. _('You already have %1$d or more people in list %2$s, ' .
  169. 'which is the maximum allowed number. ' .
  170. 'Try unlisting others first.'),
  171. common_config('peopletag', 'maxpeople'),
  172. $tag
  173. ));
  174. }
  175. $newtag = new Profile_tag();
  176. $newtag->tagger = $tagger;
  177. $newtag->tagged = $tagged;
  178. $newtag->tag = $tag;
  179. $result = $newtag->insert();
  180. if (!$result) {
  181. common_log_db_error($newtag, 'INSERT', __FILE__);
  182. $plist->query('ROLLBACK');
  183. return false;
  184. }
  185. try {
  186. $plist->query('COMMIT');
  187. Event::handle('EndTagProfile', array($newtag));
  188. } catch (Exception $e) {
  189. $newtag->delete();
  190. $profile_list->delete();
  191. throw $e;
  192. }
  193. $profile_list->taggedCount(true);
  194. self::blowCaches($tagger, $tagged);
  195. }
  196. return $newtag;
  197. }
  198. public static function unTag($tagger, $tagged, $tag)
  199. {
  200. $ptag = Profile_tag::pkeyGet(array('tagger' => $tagger,
  201. 'tagged' => $tagged,
  202. 'tag' => $tag));
  203. if (!$ptag) {
  204. return true;
  205. }
  206. if (Event::handle('StartUntagProfile', array($ptag))) {
  207. $orig = clone($ptag);
  208. $result = $ptag->delete();
  209. if ($result === false) {
  210. common_log_db_error($this, 'DELETE', __FILE__);
  211. return false;
  212. }
  213. Event::handle('EndUntagProfile', array($orig));
  214. $profile_list = Profile_list::pkeyGet(array('tag' => $tag, 'tagger' => $tagger));
  215. if (!empty($profile_list)) {
  216. $profile_list->taggedCount(true);
  217. }
  218. self::blowCaches($tagger, $tagged);
  219. return true;
  220. }
  221. }
  222. // @fixme: move this to Profile_list?
  223. public static function cleanup($profile_list)
  224. {
  225. $ptag = new Profile_tag();
  226. $ptag->tagger = $profile_list->tagger;
  227. $ptag->tag = $profile_list->tag;
  228. $ptag->find();
  229. while ($ptag->fetch()) {
  230. if (Event::handle('StartUntagProfile', array($ptag))) {
  231. $orig = clone($ptag);
  232. $result = $ptag->delete();
  233. if (!$result) {
  234. common_log_db_error($this, 'DELETE', __FILE__);
  235. }
  236. Event::handle('EndUntagProfile', array($orig));
  237. }
  238. }
  239. }
  240. // move a tag!
  241. public static function moveTag($orig, $new)
  242. {
  243. $tags = new Profile_tag();
  244. $qry = "UPDATE profile_tag SET tag = '%s', tagger = '%s' " .
  245. "WHERE tag = '%s' AND tagger = '%s'";
  246. $result = $tags->query(sprintf(
  247. $qry,
  248. $tags->escape($new->tag),
  249. $tags->escape($new->tagger),
  250. $tags->escape($orig->tag),
  251. $tags->escape($orig->tagger)
  252. ));
  253. if ($result === false) {
  254. common_log_db_error($tags, 'UPDATE', __FILE__);
  255. throw new Exception('Could not move Profile_tag, see db log for details.');
  256. }
  257. return $result;
  258. }
  259. public static function blowCaches($tagger, $tagged)
  260. {
  261. foreach (array(0, 1) as $perm) {
  262. self::blow(sprintf('profile_tag:tagger_tagged_privacy:%d-%d-%d', $tagger, $tagged, $perm));
  263. }
  264. return true;
  265. }
  266. // Return profiles with a given tag
  267. public static function getTagged($tagger, $tag)
  268. {
  269. $profile = new Profile();
  270. $profile->query('SELECT profile.* ' .
  271. 'FROM profile JOIN profile_tag ' .
  272. 'ON profile.id = profile_tag.tagged ' .
  273. 'WHERE profile_tag.tagger = ' . $profile->escape($tagger) . ' ' .
  274. "AND profile_tag.tag = '" . $profile->escape($tag) . "' ");
  275. $tagged = [];
  276. while ($profile->fetch()) {
  277. $tagged[] = clone($profile);
  278. }
  279. return true;
  280. }
  281. public function insert()
  282. {
  283. $result = parent::insert();
  284. if ($result) {
  285. self::blow(
  286. 'profile_list:tagged_count:%d:%s',
  287. $this->tagger,
  288. $this->tag
  289. );
  290. }
  291. return $result;
  292. }
  293. public function delete($useWhere = false)
  294. {
  295. $result = parent::delete($useWhere);
  296. if ($result !== false) {
  297. self::blow(
  298. 'profile_list:tagged_count:%d:%s',
  299. $this->tagger,
  300. $this->tag
  301. );
  302. }
  303. return $result;
  304. }
  305. }