User.php 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082
  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
  19. */
  20. class User extends Managed_DataObject
  21. {
  22. const SUBSCRIBE_POLICY_OPEN = 0;
  23. const SUBSCRIBE_POLICY_MODERATE = 1;
  24. ###START_AUTOCODE
  25. /* the code below is auto generated do not remove the above tag */
  26. public $__table = 'user'; // table name
  27. public $id; // int(4) primary_key not_null
  28. public $nickname; // varchar(64) unique_key
  29. public $password; // varchar(191) not 255 because utf8mb4 takes more space
  30. public $email; // varchar(191) unique_key not 255 because utf8mb4 takes more space
  31. public $incomingemail; // varchar(191) unique_key not 255 because utf8mb4 takes more space
  32. public $emailnotifysub; // bool default_true
  33. public $emailnotifyfav; // tinyint(1) default_null
  34. public $emailnotifynudge; // bool default_true
  35. public $emailnotifymsg; // bool default_true
  36. public $emailnotifyattn; // bool default_true
  37. public $language; // varchar(50)
  38. public $timezone; // varchar(50)
  39. public $emailpost; // bool default_true
  40. public $sms; // varchar(64) unique_key
  41. public $carrier; // int(4)
  42. public $smsnotify; // bool default_false
  43. public $smsreplies; // bool default_false
  44. public $smsemail; // varchar(191) not 255 because utf8mb4 takes more space
  45. public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
  46. public $autosubscribe; // bool default_false
  47. public $subscribe_policy; // tinyint(1)
  48. public $urlshorteningservice; // varchar(50) default_ur1.ca
  49. public $private_stream; // bool default_false
  50. public $created; // datetime() not_null default_0000-00-00%2000%3A00%3A00
  51. public $modified; // datetime() not_null default_CURRENT_TIMESTAMP
  52. /* the code above is auto generated do not remove the tag below */
  53. ###END_AUTOCODE
  54. public static function schemaDef()
  55. {
  56. return array(
  57. 'description' => 'local users',
  58. 'fields' => array(
  59. 'id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'),
  60. 'nickname' => array('type' => 'varchar', 'length' => 64, 'description' => 'nickname or username, duped in profile'),
  61. 'password' => array('type' => 'varchar', 'length' => 191, 'description' => 'salted password, can be null for OpenID users'),
  62. 'email' => array('type' => 'varchar', 'length' => 191, 'description' => 'email address for password recovery etc.'),
  63. 'incomingemail' => array('type' => 'varchar', 'length' => 191, 'description' => 'email address for post-by-email'),
  64. 'emailnotifysub' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of subscriptions'),
  65. 'emailnotifyfav' => array('type' => 'int', 'size' => 'tiny', 'default' => null, 'description' => 'Notify by email of favorites'),
  66. 'emailnotifynudge' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of nudges'),
  67. 'emailnotifymsg' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of direct messages'),
  68. 'emailnotifyattn' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of @-replies'),
  69. 'language' => array('type' => 'varchar', 'length' => 50, 'description' => 'preferred language'),
  70. 'timezone' => array('type' => 'varchar', 'length' => 50, 'description' => 'timezone'),
  71. 'emailpost' => array('type' => 'bool', 'default' => true, 'description' => 'Post by email'),
  72. 'sms' => array('type' => 'varchar', 'length' => 64, 'description' => 'sms phone number'),
  73. 'carrier' => array('type' => 'int', 'description' => 'foreign key to sms_carrier'),
  74. 'smsnotify' => array('type' => 'bool', 'default' => false, 'description' => 'whether to send notices to SMS'),
  75. 'smsreplies' => array('type' => 'bool', 'default' => false, 'description' => 'whether to send notices to SMS on replies'),
  76. 'smsemail' => array('type' => 'varchar', 'length' => 191, 'description' => 'built from sms and carrier'),
  77. 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'),
  78. 'autosubscribe' => array('type' => 'bool', 'default' => false, 'description' => 'automatically subscribe to users who subscribe to us'),
  79. 'subscribe_policy' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => '0 = anybody can subscribe; 1 = require approval'),
  80. 'urlshorteningservice' => array('type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'),
  81. 'private_stream' => array('type' => 'bool', 'default' => false, 'description' => 'whether to limit all notices to followers only'),
  82. 'created' => array('type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'),
  83. 'modified' => array('type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'),
  84. ),
  85. 'primary key' => array('id'),
  86. 'unique keys' => array(
  87. 'user_nickname_key' => array('nickname'),
  88. 'user_email_key' => array('email'),
  89. 'user_incomingemail_key' => array('incomingemail'),
  90. 'user_sms_key' => array('sms'),
  91. 'user_uri_key' => array('uri'),
  92. ),
  93. 'foreign keys' => array(
  94. 'user_id_fkey' => array('profile', array('id' => 'id')),
  95. 'user_carrier_fkey' => array('sms_carrier', array('carrier' => 'id')),
  96. ),
  97. 'indexes' => array(
  98. 'user_smsemail_idx' => array('smsemail'),
  99. ),
  100. );
  101. }
  102. protected $_profile = array();
  103. /**
  104. * @return Profile
  105. *
  106. * @throws UserNoProfileException if user has no profile
  107. */
  108. public function getProfile()
  109. {
  110. if (!isset($this->_profile[$this->id])) {
  111. $profile = Profile::getKV('id', $this->id);
  112. if (!$profile instanceof Profile) {
  113. throw new UserNoProfileException($this);
  114. }
  115. $this->_profile[$this->id] = $profile;
  116. }
  117. return $this->_profile[$this->id];
  118. }
  119. public function sameAs(Profile $other)
  120. {
  121. return $this->getProfile()->sameAs($other);
  122. }
  123. public function getUri()
  124. {
  125. return $this->uri;
  126. }
  127. public function getNickname()
  128. {
  129. return $this->getProfile()->getNickname();
  130. }
  131. public static function getByNickname($nickname)
  132. {
  133. $user = User::getKV('nickname', $nickname);
  134. if (!$user instanceof User) {
  135. throw new NoSuchUserException(array('nickname' => $nickname));
  136. }
  137. return $user;
  138. }
  139. public function isSubscribed(Profile $other)
  140. {
  141. return $this->getProfile()->isSubscribed($other);
  142. }
  143. public function hasPendingSubscription(Profile $other)
  144. {
  145. return $this->getProfile()->hasPendingSubscription($other);
  146. }
  147. /**
  148. * Get the most recent notice posted by this user, if any.
  149. *
  150. * @return mixed Notice or null
  151. */
  152. public function getCurrentNotice()
  153. {
  154. return $this->getProfile()->getCurrentNotice();
  155. }
  156. public function getCarrier()
  157. {
  158. return Sms_carrier::getKV('id', $this->carrier);
  159. }
  160. public function hasBlocked(Profile $other)
  161. {
  162. return $this->getProfile()->hasBlocked($other);
  163. }
  164. /**
  165. * Register a new user account and profile and set up default subscriptions.
  166. * If a new-user welcome message is configured, this will be sent.
  167. *
  168. * @param array $fields associative array of optional properties
  169. * string 'bio'
  170. * string 'email'
  171. * bool 'email_confirmed' pass true to mark email as pre-confirmed
  172. * string 'fullname'
  173. * string 'homepage'
  174. * string 'location' informal string description of geolocation
  175. * float 'lat' decimal latitude for geolocation
  176. * float 'lon' decimal longitude for geolocation
  177. * int 'location_id' geoname identifier
  178. * int 'location_ns' geoname namespace to interpret location_id
  179. * string 'nickname' REQUIRED
  180. * string 'password' (may be missing for eg OpenID registrations)
  181. * string 'code' invite code
  182. * ?string 'uri' permalink to notice; defaults to local notice URL
  183. * @return User object
  184. * @throws Exception on failure
  185. */
  186. public static function register(array $fields, $accept_email_fail = false)
  187. {
  188. // MAGICALLY put fields into current scope
  189. extract($fields);
  190. $profile = new Profile();
  191. if (!empty($email)) {
  192. $email = common_canonical_email($email);
  193. }
  194. // Normalize _and_ check whether it is in use. Throw NicknameException on failure.
  195. $profile->nickname = Nickname::normalize($nickname, true);
  196. $profile->profileurl = common_profile_url($profile->nickname);
  197. if (!empty($fullname)) {
  198. $profile->fullname = $fullname;
  199. }
  200. if (!empty($homepage)) {
  201. $profile->homepage = $homepage;
  202. }
  203. if (!empty($bio)) {
  204. $profile->bio = $bio;
  205. }
  206. if (!empty($location)) {
  207. $profile->location = $location;
  208. $loc = Location::fromName($location);
  209. if (!empty($loc)) {
  210. $profile->lat = $loc->lat;
  211. $profile->lon = $loc->lon;
  212. $profile->location_id = $loc->location_id;
  213. $profile->location_ns = $loc->location_ns;
  214. }
  215. }
  216. $profile->created = common_sql_now();
  217. $user = new User();
  218. $user->nickname = $profile->nickname;
  219. $invite = null;
  220. // Users who respond to invite email have proven their ownership of that address
  221. if (!empty($code)) {
  222. $invite = Invitation::getKV($code);
  223. if ($invite instanceof Invitation && $invite->address && $invite->address_type == 'email' && $invite->address == $email) {
  224. $user->email = $invite->address;
  225. }
  226. }
  227. if (isset($email_confirmed) && $email_confirmed) {
  228. $user->email = $email;
  229. }
  230. // Set default-on options here, otherwise they'll be disabled
  231. // initially for sites using caching, since the initial encache
  232. // doesn't know about the defaults in the database.
  233. $user->emailnotifysub = true;
  234. $user->emailnotifynudge = true;
  235. $user->emailnotifymsg = true;
  236. $user->emailnotifyattn = true;
  237. $user->emailpost = true;
  238. $user->created = common_sql_now();
  239. if (Event::handle('StartUserRegister', array($profile))) {
  240. $profile->query('BEGIN');
  241. $id = $profile->insert();
  242. if ($id === false) {
  243. common_log_db_error($profile, 'INSERT', __FILE__);
  244. $profile->query('ROLLBACK');
  245. // TRANS: Profile data could not be inserted for some reason.
  246. throw new ServerException(_m('Could not insert profile data for new user.'));
  247. }
  248. // Necessary because id has been known to be reissued.
  249. if ($profile->hasRole(Profile_role::DELETED)) {
  250. $profile->revokeRole(Profile_role::DELETED);
  251. }
  252. $user->id = $id;
  253. if (!empty($uri)) {
  254. $user->uri = $uri;
  255. } else {
  256. $user->uri = common_user_uri($user);
  257. }
  258. if (!empty($password)) { // may not have a password for OpenID users
  259. $user->password = common_munge_password($password);
  260. }
  261. $result = $user->insert();
  262. if ($result === false) {
  263. common_log_db_error($user, 'INSERT', __FILE__);
  264. $profile->query('ROLLBACK');
  265. // TRANS: User data could not be inserted for some reason.
  266. throw new ServerException(_m('Could not insert user data for new user.'));
  267. }
  268. // Everyone is subscribed to themself
  269. $subscription = new Subscription();
  270. $subscription->subscriber = $user->id;
  271. $subscription->subscribed = $user->id;
  272. $subscription->created = $user->created;
  273. $result = $subscription->insert();
  274. if (!$result) {
  275. common_log_db_error($subscription, 'INSERT', __FILE__);
  276. $profile->query('ROLLBACK');
  277. // TRANS: Subscription data could not be inserted for some reason.
  278. throw new ServerException(_m('Could not insert subscription data for new user.'));
  279. }
  280. // Mark that this invite was converted
  281. if (!empty($invite)) {
  282. $invite->convert($user);
  283. }
  284. if (!empty($email) && empty($user->email)) {
  285. // The actual email will be sent further down, after the database COMMIT
  286. $confirm = new Confirm_address();
  287. $confirm->code = common_confirmation_code(128);
  288. $confirm->user_id = $user->id;
  289. $confirm->address = $email;
  290. $confirm->address_type = 'email';
  291. $result = $confirm->insert();
  292. if ($result===false) {
  293. common_log_db_error($confirm, 'INSERT', __FILE__);
  294. $profile->query('ROLLBACK');
  295. // TRANS: Email confirmation data could not be inserted for some reason.
  296. throw new ServerException(_m('Could not insert email confirmation data for new user.'));
  297. }
  298. }
  299. if (!empty($code) && $user->email) {
  300. $user->emailChanged();
  301. }
  302. // Default system subscription
  303. $defnick = common_config('newuser', 'default');
  304. if (!empty($defnick)) {
  305. $defuser = User::getKV('nickname', $defnick);
  306. if (empty($defuser)) {
  307. common_log(
  308. LOG_WARNING,
  309. sprintf('Default user %s does not exist.', $defnick),
  310. __FILE__
  311. );
  312. } else {
  313. Subscription::ensureStart($profile, $defuser->getProfile());
  314. }
  315. }
  316. $profile->query('COMMIT');
  317. if (!empty($email) && empty($user->email)) {
  318. try {
  319. $confirm->sendConfirmation();
  320. } catch (EmailException $e) {
  321. common_log(LOG_ERR, "Could not send user registration email for user id=={$profile->getID()}: {$e->getMessage()}");
  322. if (!$accept_email_fail) {
  323. throw $e;
  324. }
  325. }
  326. }
  327. // Welcome message
  328. $welcome = common_config('newuser', 'welcome');
  329. if (!empty($welcome)) {
  330. $welcomeuser = User::getKV('nickname', $welcome);
  331. if (empty($welcomeuser)) {
  332. common_log(
  333. LOG_WARNING,
  334. sprintf('Welcome user %s does not exist.', $defnick),
  335. __FILE__
  336. );
  337. } else {
  338. $notice = Notice::saveNew(
  339. $welcomeuser->id,
  340. // TRANS: Notice given on user registration.
  341. // TRANS: %1$s is the sitename, $2$s is the registering user's nickname.
  342. sprintf(
  343. _('Welcome to %1$s, @%2$s!'),
  344. common_config('site', 'name'),
  345. $profile->getNickname()
  346. ),
  347. 'system'
  348. );
  349. }
  350. }
  351. Event::handle('EndUserRegister', array($profile));
  352. }
  353. if (!$user instanceof User || empty($user->id)) {
  354. throw new ServerException('User could not be registered. Probably an event hook that failed.');
  355. }
  356. return $user;
  357. }
  358. // Things we do when the email changes
  359. public function emailChanged()
  360. {
  361. $invites = new Invitation();
  362. $invites->address = $this->email;
  363. $invites->address_type = 'email';
  364. if ($invites->find()) {
  365. while ($invites->fetch()) {
  366. try {
  367. $other = Profile::getByID($invites->user_id);
  368. Subscription::start($other, $this->getProfile());
  369. } catch (NoResultException $e) {
  370. // profile did not exist
  371. } catch (AlreadyFulfilledException $e) {
  372. // already subscribed to this profile
  373. } catch (Exception $e) {
  374. common_log(LOG_ERR, 'On-invitation-completion subscription failed when subscribing '._ve($invites->user_id).' to '.$this->getProfile()->getID().': '._ve($e->getMessage()));
  375. }
  376. }
  377. }
  378. }
  379. public function mutuallySubscribed(Profile $other)
  380. {
  381. return $this->getProfile()->mutuallySubscribed($other);
  382. }
  383. public function mutuallySubscribedUsers()
  384. {
  385. $user = new User();
  386. // 3-way join; probably should get cached
  387. $user->query(sprintf(
  388. 'SELECT %1$s.* ' .
  389. 'FROM subscription AS sub1 INNER JOIN %1$s ON sub1.subscribed = %1$s.id ' .
  390. 'INNER JOIN subscription AS sub2 ON %1$s.id = sub2.subscriber ' .
  391. 'WHERE sub1.subscriber = %2$d AND sub2.subscribed = %2$d ' .
  392. 'ORDER BY %1$s.nickname',
  393. $user->escapedTableName(),
  394. $this->id
  395. ));
  396. return $user;
  397. }
  398. public function getReplies($offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $before_id = 0)
  399. {
  400. return $this->getProfile()->getReplies($offset, $limit, $since_id, $before_id);
  401. }
  402. public function getTaggedNotices($tag, $offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $before_id = 0)
  403. {
  404. return $this->getProfile()->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id);
  405. }
  406. public function getNotices($offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $before_id = 0)
  407. {
  408. return $this->getProfile()->getNotices($offset, $limit, $since_id, $before_id);
  409. }
  410. public function block(Profile $other)
  411. {
  412. // Add a new block record
  413. // no blocking (and thus unsubbing from) yourself
  414. if ($this->id == $other->id) {
  415. common_log(
  416. LOG_WARNING,
  417. sprintf(
  418. "Profile ID %d (%s) tried to block themself.",
  419. $this->id,
  420. $this->nickname
  421. )
  422. );
  423. return false;
  424. }
  425. $block = new Profile_block();
  426. // Begin a transaction
  427. $block->query('BEGIN');
  428. $block->blocker = $this->id;
  429. $block->blocked = $other->id;
  430. $result = $block->insert();
  431. if (!$result) {
  432. common_log_db_error($block, 'INSERT', __FILE__);
  433. return false;
  434. }
  435. $self = $this->getProfile();
  436. if (Subscription::exists($other, $self)) {
  437. Subscription::cancel($other, $self);
  438. }
  439. if (Subscription::exists($self, $other)) {
  440. Subscription::cancel($self, $other);
  441. }
  442. $block->query('COMMIT');
  443. return true;
  444. }
  445. public function unblock(Profile $other)
  446. {
  447. // Get the block record
  448. $block = Profile_block::exists($this->getProfile(), $other);
  449. if (!$block) {
  450. return false;
  451. }
  452. $result = $block->delete();
  453. if (!$result) {
  454. common_log_db_error($block, 'DELETE', __FILE__);
  455. return false;
  456. }
  457. return true;
  458. }
  459. public function isMember(User_group $group)
  460. {
  461. return $this->getProfile()->isMember($group);
  462. }
  463. public function isAdmin(User_group $group)
  464. {
  465. return $this->getProfile()->isAdmin($group);
  466. }
  467. public function getGroups($offset = 0, $limit = null)
  468. {
  469. return $this->getProfile()->getGroups($offset, $limit);
  470. }
  471. /**
  472. * Request to join the given group.
  473. * May throw exceptions on failure.
  474. *
  475. * @param User_group $group
  476. * @return Group_member
  477. */
  478. public function joinGroup(User_group $group)
  479. {
  480. return $this->getProfile()->joinGroup($group);
  481. }
  482. /**
  483. * Leave a group that this user is a member of.
  484. *
  485. * @param User_group $group
  486. */
  487. public function leaveGroup(User_group $group)
  488. {
  489. return $this->getProfile()->leaveGroup($group);
  490. }
  491. public function getSubscribed($offset = 0, $limit = null)
  492. {
  493. return $this->getProfile()->getSubscribed($offset, $limit);
  494. }
  495. public function getSubscribers($offset = 0, $limit = null)
  496. {
  497. return $this->getProfile()->getSubscribers($offset, $limit);
  498. }
  499. public function getTaggedSubscribers($tag, $offset = 0, $limit = null)
  500. {
  501. return $this->getProfile()->getTaggedSubscribers($tag, $offset, $limit);
  502. }
  503. public function getTaggedSubscriptions($tag, $offset = 0, $limit = null)
  504. {
  505. return $this->getProfile()->getTaggedSubscriptions($tag, $offset, $limit);
  506. }
  507. public function hasRight($right)
  508. {
  509. return $this->getProfile()->hasRight($right);
  510. }
  511. public function delete($useWhere = false)
  512. {
  513. if (empty($this->id)) {
  514. common_log(LOG_WARNING, "Ambiguous User->delete(); skipping related tables.");
  515. return parent::delete($useWhere);
  516. }
  517. try {
  518. if (!$this->hasRole(Profile_role::DELETED)) {
  519. $profile = $this->getProfile();
  520. $profile->delete();
  521. }
  522. } catch (UserNoProfileException $unp) {
  523. common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion.");
  524. }
  525. $related = array(
  526. 'Confirm_address',
  527. 'Remember_me',
  528. 'Foreign_link',
  529. 'Invitation',
  530. );
  531. Event::handle('UserDeleteRelated', array($this, &$related));
  532. foreach ($related as $cls) {
  533. $inst = new $cls();
  534. $inst->user_id = $this->id;
  535. $inst->delete();
  536. }
  537. $this->_deleteTags();
  538. $this->_deleteBlocks();
  539. return parent::delete($useWhere);
  540. }
  541. public function _deleteTags()
  542. {
  543. $tag = new Profile_tag();
  544. $tag->tagger = $this->id;
  545. $tag->delete();
  546. }
  547. public function _deleteBlocks()
  548. {
  549. $block = new Profile_block();
  550. $block->blocker = $this->id;
  551. $block->delete();
  552. // XXX delete group block? Reset blocker?
  553. }
  554. public function hasRole($name)
  555. {
  556. return $this->getProfile()->hasRole($name);
  557. }
  558. public function grantRole($name)
  559. {
  560. return $this->getProfile()->grantRole($name);
  561. }
  562. public function revokeRole($name)
  563. {
  564. return $this->getProfile()->revokeRole($name);
  565. }
  566. public function isSandboxed()
  567. {
  568. return $this->getProfile()->isSandboxed();
  569. }
  570. public function isSilenced()
  571. {
  572. return $this->getProfile()->isSilenced();
  573. }
  574. public function receivesEmailNotifications()
  575. {
  576. // We could do this in one large if statement, but that's not as easy to read
  577. // Don't send notifications if we don't know the user's email address or it is
  578. // explicitly undesired by the user's own settings.
  579. if (empty($this->email) || !$this->emailnotifyattn) {
  580. return false;
  581. }
  582. // Don't send notifications to a user who is sandboxed or silenced
  583. if ($this->isSandboxed() || $this->isSilenced()) {
  584. return false;
  585. }
  586. return true;
  587. }
  588. public function repeatedByMe($offset = 0, $limit = 20, $since_id = null, $max_id = null)
  589. {
  590. // FIXME: Use another way to get Profile::current() since we
  591. // want to avoid confusion between session user and queue processing.
  592. $stream = new RepeatedByMeNoticeStream($this->getProfile(), Profile::current());
  593. return $stream->getNotices($offset, $limit, $since_id, $max_id);
  594. }
  595. public function repeatsOfMe($offset = 0, $limit = 20, $since_id = null, $max_id = null)
  596. {
  597. // FIXME: Use another way to get Profile::current() since we
  598. // want to avoid confusion between session user and queue processing.
  599. $stream = new RepeatsOfMeNoticeStream($this->getProfile(), Profile::current());
  600. return $stream->getNotices($offset, $limit, $since_id, $max_id);
  601. }
  602. public function repeatedToMe($offset = 0, $limit = 20, $since_id = null, $max_id = null)
  603. {
  604. return $this->getProfile()->repeatedToMe($offset, $limit, $since_id, $max_id);
  605. }
  606. public static function siteOwner()
  607. {
  608. $owner = self::cacheGet('user:site_owner');
  609. if ($owner === false) { // cache miss
  610. $pr = new Profile_role();
  611. $pr->role = Profile_role::OWNER;
  612. $pr->orderBy('created');
  613. $pr->limit(1);
  614. if (!$pr->find(true)) {
  615. throw new NoResultException($pr);
  616. }
  617. $owner = User::getKV('id', $pr->profile_id);
  618. self::cacheSet('user:site_owner', $owner);
  619. }
  620. if ($owner instanceof User) {
  621. return $owner;
  622. }
  623. throw new ServerException(_('No site owner configured.'));
  624. }
  625. /**
  626. * Pull the primary site account to use in single-user mode.
  627. * If a valid user nickname is listed in 'singleuser':'nickname'
  628. * in the config, this will be used; otherwise the site owner
  629. * account is taken by default.
  630. *
  631. * @return User
  632. * @throws ServerException if no valid single user account is present
  633. * @throws ServerException if called when not in single-user mode
  634. */
  635. public static function singleUser()
  636. {
  637. if (!common_config('singleuser', 'enabled')) {
  638. // TRANS: Server exception.
  639. throw new ServerException(_('Single-user mode code called when not enabled.'));
  640. }
  641. if ($nickname = common_config('singleuser', 'nickname')) {
  642. $user = User::getKV('nickname', $nickname);
  643. if ($user instanceof User) {
  644. return $user;
  645. }
  646. }
  647. // If there was no nickname or no user by that nickname,
  648. // try the site owner. Throws exception if not configured.
  649. return User::siteOwner();
  650. }
  651. /**
  652. * This is kind of a hack for using external setup code that's trying to
  653. * build single-user sites.
  654. *
  655. * Will still return a username if the config singleuser/nickname is set
  656. * even if the account doesn't exist, which normally indicates that the
  657. * site is horribly misconfigured.
  658. *
  659. * At the moment, we need to let it through so that router setup can
  660. * complete, otherwise we won't be able to create the account.
  661. *
  662. * This will be easier when we can more easily create the account and
  663. * *then* switch the site to 1user mode without jumping through hoops.
  664. *
  665. * @return string
  666. * @throws ServerException if no valid single user account is present
  667. * @throws ServerException if called when not in single-user mode
  668. */
  669. public static function singleUserNickname()
  670. {
  671. try {
  672. $user = User::singleUser();
  673. return $user->nickname;
  674. } catch (Exception $e) {
  675. if (common_config('singleuser', 'enabled') && common_config('singleuser', 'nickname')) {
  676. common_log(LOG_WARNING, "Warning: code attempting to pull single-user nickname when the account does not exist. If this is not setup time, this is probably a bug.");
  677. return common_config('singleuser', 'nickname');
  678. }
  679. throw $e;
  680. }
  681. }
  682. /**
  683. * Find and shorten links in the given text using this user's URL shortening
  684. * settings.
  685. *
  686. * By default, links will be left untouched if the text is shorter than the
  687. * configured maximum notice length. Pass true for the $always parameter
  688. * to force all links to be shortened regardless.
  689. *
  690. * Side effects: may save file and file_redirection records for referenced URLs.
  691. *
  692. * @param string $text
  693. * @param boolean $always
  694. * @return string
  695. */
  696. public function shortenLinks($text, $always=false)
  697. {
  698. return common_shorten_links($text, $always, $this);
  699. }
  700. /*
  701. * Get a list of OAuth client applications that have access to this
  702. * user's account.
  703. */
  704. public function getConnectedApps($offset = 0, $limit = null)
  705. {
  706. $qry =
  707. 'SELECT u.* ' .
  708. 'FROM oauth_application_user u, oauth_application a ' .
  709. 'WHERE u.profile_id = %d ' .
  710. 'AND a.id = u.application_id ' .
  711. 'AND u.access_type > 0 ' .
  712. 'ORDER BY u.created DESC ';
  713. if ($offset > 0) {
  714. $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
  715. }
  716. $apps = new Oauth_application_user();
  717. $cnt = $apps->query(sprintf($qry, $this->id));
  718. return $apps;
  719. }
  720. /**
  721. * Magic function called at serialize() time.
  722. *
  723. * We use this to drop a couple process-specific references
  724. * from DB_DataObject which can cause trouble in future
  725. * processes.
  726. *
  727. * @return array of variable names to include in serialization.
  728. */
  729. public function __sleep()
  730. {
  731. $vars = parent::__sleep();
  732. $skip = array('_profile');
  733. return array_diff($vars, $skip);
  734. }
  735. public static function recoverPassword($nore)
  736. {
  737. require_once INSTALLDIR . '/lib/util/mail.php';
  738. // $confirm_email will be used as a fallback if our user doesn't have a confirmed email
  739. $confirm_email = null;
  740. if (common_is_email($nore)) {
  741. $user = User::getKV('email', common_canonical_email($nore));
  742. // See if it's an unconfirmed email address
  743. if (!$user instanceof User) {
  744. // Warning: it may actually be legit to have multiple folks
  745. // who have claimed, but not yet confirmed, the same address.
  746. // We'll only send to the first one that comes up.
  747. $confirm_email = new Confirm_address();
  748. $confirm_email->address = common_canonical_email($nore);
  749. $confirm_email->address_type = 'email';
  750. if ($confirm_email->find(true)) {
  751. $user = User::getKV('id', $confirm_email->user_id);
  752. }
  753. }
  754. // No luck finding anyone by that email address.
  755. if (!$user instanceof User) {
  756. if (common_config('site', 'fakeaddressrecovery')) {
  757. // Return without actually doing anything! We fake address recovery
  758. // to avoid revealing which email addresses are registered with the site.
  759. return;
  760. }
  761. // TRANS: Information on password recovery form if no known e-mail address was specified.
  762. throw new ClientException(_('No user with that email address exists here.'));
  763. }
  764. } else {
  765. // This might throw a NicknameException on bad nicknames
  766. $user = User::getKV('nickname', common_canonical_nickname($nore));
  767. if (!$user instanceof User) {
  768. // TRANS: Information on password recovery form if no known username was specified.
  769. throw new ClientException(_('No user with that nickname exists here.'));
  770. }
  771. }
  772. // Try to get an unconfirmed email address if they used a user name
  773. if (empty($user->email) && $confirm_email === null) {
  774. $confirm_email = new Confirm_address();
  775. $confirm_email->user_id = $user->id;
  776. $confirm_email->address_type = 'email';
  777. $confirm_email->find();
  778. if (!$confirm_email->fetch()) {
  779. // Nothing found, so let's reset it to null
  780. $confirm_email = null;
  781. }
  782. }
  783. if (empty($user->email) && !$confirm_email instanceof Confirm_address) {
  784. // TRANS: Client error displayed on password recovery form if a user does not have a registered e-mail address.
  785. throw new ClientException(_('No registered email address for that user.'));
  786. }
  787. // Success! We have a valid user and a confirmed or unconfirmed email address
  788. $confirm = new Confirm_address();
  789. $confirm->code = common_confirmation_code(128);
  790. $confirm->address_type = 'recover';
  791. $confirm->user_id = $user->id;
  792. $confirm->address = $user->email ?: $confirm_email->address;
  793. if (!$confirm->insert()) {
  794. common_log_db_error($confirm, 'INSERT', __FILE__);
  795. // TRANS: Server error displayed if e-mail address confirmation fails in the database on the password recovery form.
  796. throw new ServerException(_('Error saving address confirmation.'));
  797. }
  798. // @todo FIXME: needs i18n.
  799. $body = "Hey, $user->nickname.";
  800. $body .= "\n\n";
  801. $body .= 'Someone just asked for a new password ' .
  802. 'for this account on ' . common_config('site', 'name') . '.';
  803. $body .= "\n\n";
  804. $body .= 'If it was you, and you want to confirm, use the URL below:';
  805. $body .= "\n\n";
  806. $body .= "\t" . common_local_url(
  807. 'recoverpassword',
  808. ['code' => $confirm->code]
  809. );
  810. $body .= "\n\n";
  811. $body .= 'If not, just ignore this message.';
  812. $body .= "\n\n";
  813. $body .= 'Thanks for your time, ';
  814. $body .= "\n";
  815. $body .= common_config('site', 'name');
  816. $body .= "\n";
  817. $headers = _mail_prepare_headers('recoverpassword', $user->nickname, $user->nickname);
  818. // TRANS: Subject for password recovery e-mail.
  819. mail_to_user($user, _('Password recovery requested'), $body, $headers, $confirm->address);
  820. }
  821. public function streamModeOnly()
  822. {
  823. if (common_config('oldschool', 'enabled')) {
  824. $osp = Old_school_prefs::getKV('user_id', $this->id);
  825. if (!empty($osp)) {
  826. return $osp->stream_mode_only;
  827. }
  828. }
  829. return false;
  830. }
  831. public function streamNicknames()
  832. {
  833. if (common_config('oldschool', 'enabled')) {
  834. $osp = Old_school_prefs::getKV('user_id', $this->id);
  835. if (!empty($osp)) {
  836. return $osp->stream_nicknames;
  837. }
  838. }
  839. return false;
  840. }
  841. public function registrationActivity()
  842. {
  843. $profile = $this->getProfile();
  844. $service = new ActivityObject();
  845. $service->type = ActivityObject::SERVICE;
  846. $service->title = common_config('site', 'name');
  847. $service->link = common_root_url();
  848. $service->id = $service->link;
  849. $act = new Activity();
  850. $act->actor = $profile->asActivityObject();
  851. $act->verb = ActivityVerb::JOIN;
  852. $act->objects[] = $service;
  853. $act->id = TagURI::mint(
  854. 'user:register:%d',
  855. $this->id
  856. );
  857. $act->time = strtotime($this->created);
  858. $act->title = _("Register");
  859. $act->content = sprintf(
  860. _('%1$s joined %2$s.'),
  861. $profile->getBestName(),
  862. $service->title
  863. );
  864. return $act;
  865. }
  866. public function isPrivateStream()
  867. {
  868. return $this->getProfile()->isPrivateStream();
  869. }
  870. public function hasPassword()
  871. {
  872. return !empty($this->password);
  873. }
  874. public function setPassword($password)
  875. {
  876. $orig = clone($this);
  877. $this->password = common_munge_password($password, $this->getProfile());
  878. if ($this->validate() !== true) {
  879. // TRANS: Form validation error on page where to change password.
  880. throw new ServerException(_('Error saving user; invalid.'));
  881. }
  882. if (!$this->update($orig)) {
  883. common_log_db_error($this, 'UPDATE', __FILE__);
  884. // TRANS: Server error displayed on page where to change password when password change
  885. // TRANS: could not be made because of a server error.
  886. throw new ServerException(_('Cannot save new password.'));
  887. }
  888. }
  889. public function delPref($namespace, $topic)
  890. {
  891. return $this->getProfile()->delPref($namespace, $topic);
  892. }
  893. public function getPref($namespace, $topic, $default=null)
  894. {
  895. return $this->getProfile()->getPref($namespace, $topic, $default);
  896. }
  897. public function getConfigPref($namespace, $topic)
  898. {
  899. return $this->getProfile()->getConfigPref($namespace, $topic);
  900. }
  901. public function setPref($namespace, $topic, $data)
  902. {
  903. return $this->getProfile()->setPref($namespace, $topic, $data);
  904. }
  905. }