user.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. <?php
  2. class imUser extends model
  3. {
  4. /**
  5. * Extends identify a user with plain password function,
  6. * creates an auth token for user when authenticating with password.
  7. *
  8. * @param string $account the account
  9. * @param string $password md5 hash of the password, or a 64 byte string of padded token.
  10. * @access public
  11. * @return object|bool|string if is valid user, return the user object.
  12. * if no valid user, return false.
  13. * if user is locked, return locked status as string.
  14. */
  15. public function identify($account, $password)
  16. {
  17. if(strlen($password) == 64) return $this->identifyWithToken($account, $password);
  18. $originPassword = $password;
  19. /* Calculate hash of $password$account if not using email as username. */
  20. $user = $this->loadModel('user')->identify($account, $password);
  21. if(empty($user))
  22. {
  23. if($this->loadModel('ldap') !== false && method_exists($this->ldap, 'getConfiguration'))
  24. {
  25. $ldap = $this->ldap->getConfiguration();
  26. if(isset($ldap->enabled) && $ldap->enabled) $user = $this->identifyWithLDAP($account, $originPassword);
  27. }
  28. }
  29. if(is_object($user))
  30. {
  31. /* User was logon with password, generate auth token. */
  32. $token = $this->getAuthToken($user->id, $this->app->input['device']);
  33. $user->token = $token->token;
  34. }
  35. return $user;
  36. }
  37. /**
  38. * Auth with username and token. Token is valid for around 30 seconds.
  39. *
  40. * @param string $account
  41. * @param string $token
  42. * @param string $device
  43. * @access public
  44. * @return object|bool|string
  45. */
  46. public function identifyWithToken($account, $token, $device = '')
  47. {
  48. $tokenAuthWindow = (int)zget($this->config->xuanxuan, 'tokenAuthWindow', 20);
  49. $now = (int)round(time() / $tokenAuthWindow);
  50. $token = substr($token, 0, 32); // Use first 32 chars of token.
  51. $userTokens = $this->dao->select('t1.*, t2.device, t2.token, t2.validUntil')->from(TABLE_USER)->alias('t1')
  52. ->leftJoin(TABLE_IM_USERDEVICE)->alias('t2')->on('t1.id = t2.user')
  53. ->where('t1.account')->eq($account)
  54. ->andWhere('t1.deleted')->eq('0')
  55. ->beginIF(!empty($device))->andWhere('t2.device')->eq($device)->fi()
  56. ->andWhere('t2.validUntil', true)->gt(helper::now())
  57. ->orWhere("(t2.validUntil is null or t2.validUntil = null)")->markRight(1)
  58. ->fetchAll();
  59. if(empty($userTokens)) return 'invalid_token';
  60. foreach($userTokens as $userToken)
  61. {
  62. $authTokens = array();
  63. $authTokens[] = md5($userToken->account . $userToken->token . $now);
  64. $authTokens[] = md5($userToken->account . $userToken->token . ($now - 1));
  65. $authTokens[] = md5($userToken->account . $userToken->token . ($now + 1));
  66. if(in_array($token, $authTokens))
  67. {
  68. if(!empty($userToken->locked) && $userToken->locked != null)
  69. {
  70. $dateDiff = (strtotime($userToken->locked) - time()) / 60;
  71. if($dateDiff > 0) return 'locked';
  72. }
  73. $tokenLifetime = zget($this->config->xuanxuan, 'tokenLifetime', 30);
  74. $tokenLifetime *= 24 * 60 * 60;
  75. if(strtotime($userToken->validUntil) - time() < $tokenLifetime / 3) $userToken->tokenNeedRenew = true;
  76. /* Update user data. */
  77. $updateUser=new stdclass();
  78. $updateUser->ip = helper::getRemoteIp();
  79. $updateUser->last = helper::now();
  80. $updateUser->fails = 0;
  81. $updateUser->visits = ++ $userToken->visits;
  82. /* Update password when create password by oldCreatePassword function. */
  83. $this->dao->update(TABLE_USER)->data($updateUser)->where('account')->eq($account)->exec();
  84. unset($userToken->password);
  85. unset($userToken->device);
  86. unset($userToken->token);
  87. unset($userToken->validUntil);
  88. return $userToken;
  89. }
  90. }
  91. return 'invalid_token';
  92. }
  93. /**
  94. * Auth with username and ldap password.
  95. * @param string $account
  96. * @param string $password
  97. * @return object|bool
  98. */
  99. public function identifyWithLDAP($account, $password)
  100. {
  101. $ldapConfig = $this->loadModel('ldap')->getConfiguration();
  102. if(empty($ldapConfig) || empty($ldapConfig->enabled)) return false;
  103. $ldapConn = ldap_connect($ldapConfig->host, $ldapConfig->port);
  104. ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, isset($ldapConfig->version) ? $ldapConfig->version : 3);
  105. ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);
  106. $ldapBind = @ldap_bind($ldapConn, $ldapConfig->admin, $ldapConfig->password);
  107. if(!$ldapBind)
  108. {
  109. $this->unbindLDAP($ldapConn, false);
  110. return false;
  111. }
  112. $searchList = ldap_search($ldapConn, $ldapConfig->baseDN, "({$ldapConfig->account}=$account)");
  113. $infos = ldap_get_entries($ldapConn, $searchList);
  114. if(!isset($infos[0]))
  115. {
  116. $this->unbindLDAP($ldapConn, $searchList);
  117. return false;
  118. }
  119. $info = $infos[0];
  120. if(empty($info['dn']))
  121. {
  122. $this->unbindLDAP($ldapConn, $searchList);
  123. return false;
  124. }
  125. $ldapBind = @ldap_bind($ldapConn, $info['dn'], $password);
  126. if(!$ldapBind)
  127. {
  128. $this->unbindLDAP($ldapConn, $searchList);
  129. return false;
  130. }
  131. $user = $this->loadModel('user')->getByAccount($account);
  132. if(empty($user))
  133. {
  134. if(!empty($ldapConfig->autoCreate))
  135. {
  136. $user = new stdClass();
  137. $user->account = $account;
  138. $user->password = $password;
  139. if(isset($info['mail'][0])) $user->email = $info['mail'][0];
  140. if(isset($info['mobile'][0])) $user->mobile = $info['mobile'][0];
  141. if(isset($info[$ldapConfig->displayName][0])) $user->realname = $info[$ldapConfig->displayName][0];
  142. if(isset($info['postalcode'][0])) $user->zipcode = $info['postalcode'][0];
  143. $result = $this->user->apiCreate($user, false);
  144. $this->unbindLDAP($ldapConn, $searchList);
  145. if($result) return $this->loadModel('user')->getByAccount($account);
  146. return false;
  147. }
  148. $this->unbindLDAP($ldapConn, $searchList);
  149. return false;
  150. }
  151. $this->unbindLDAP($ldapConn, $searchList);
  152. if($user->deleted == '0') return $user;
  153. return false;
  154. }
  155. /**
  156. * Free search result and unbind ldap.
  157. * @param \LDAP\Connection $ldapConn
  158. * @param \LDAP\Result|array|false $searchList
  159. * @return void
  160. */
  161. public function unbindLDAP($ldapConn, $searchList)
  162. {
  163. if(!empty($searchList)) ldap_free_result($searchList);
  164. if(!empty($ldapConn)) ldap_unbind($ldapConn);
  165. }
  166. /**
  167. * Get a user.
  168. *
  169. * @param int $id
  170. * @access public
  171. * @return object
  172. */
  173. public function getByID($id = 0)
  174. {
  175. $user = $this->dao->select('id, account, realname, avatar, role, dept, clientStatus, gender, email, mobile, phone, qq, deleted, address, weixin')
  176. ->from(TABLE_USER)
  177. ->where('id')->eq($id)
  178. ->fetch();
  179. if(!$user) return (object)array();
  180. return $this->format($user);
  181. }
  182. /**
  183. * Get user list by id or account.
  184. *
  185. * @param string $status
  186. * @param array $characters can be an array of uids or accounts, single type only.
  187. * @param bool $idAsKey
  188. * @access public
  189. * @return array
  190. */
  191. public function getList($status = '', $characters = array(), $idAsKey = true)
  192. {
  193. $dao = $this->dao->select('id, account, realname, avatar, role, dept, clientStatus, gender, email, mobile, phone, qq, deleted, address, weixin')
  194. ->from(TABLE_USER)
  195. ->where(1)
  196. ->beginIF(empty($characters))
  197. ->andWhere('deleted')->eq('0')
  198. ->fi()
  199. ->beginIF($status && $status == 'online')->andWhere('clientStatus')->ne('offline')->fi()
  200. ->beginIF($status && $status != 'online')->andWhere('clientStatus')->eq($status)->fi()
  201. ->beginIF($characters && is_numeric(current($characters)))->andWhere('id')->in($characters)->fi()
  202. ->beginIF($characters && !is_numeric(current($characters)))->andWhere('account')->in($characters)->fi();
  203. $users = $idAsKey ? $dao->fetchAll('id') : $dao->fetchAll();
  204. return $this->format($users);
  205. }
  206. /**
  207. * Get user id list by dept with pager and sort rule
  208. *
  209. * @param string $deptID
  210. * @param array $exclude
  211. * @param object $pager
  212. * @param string $orderBy
  213. * @param boolean $onlySelf if true, exclude subdepts' members from result
  214. * @access public
  215. * @return array
  216. */
  217. public function getIDListByDept($deptID = 0, $exclude = array(), $pager = null, $orderBy = '', $onlySelf = false)
  218. {
  219. $depts = $deptID ? ($onlySelf ? array($deptID) : $this->loadModel('tree')->getFamily($deptID, 'dept')) : 0;
  220. return $this->dao->select('id')
  221. ->from(TABLE_USER)
  222. ->where('deleted')->eq('0')
  223. ->beginIF($deptID)->andWhere('dept')->in($depts)->fi() // Fetch all members if $deptID is 0.
  224. ->beginIF(!$deptID && $onlySelf)->andWhere('dept')->eq(0)->fi()
  225. ->beginIF(!empty($exclude))->andWhere('id')->notin($exclude)->fi()
  226. ->orderBy($orderBy)
  227. ->beginIF($pager)->page($pager)->fi()
  228. ->fetchPairs('id');
  229. }
  230. /**
  231. * Get user count.
  232. *
  233. * @access public
  234. * @return int
  235. */
  236. public function getCount()
  237. {
  238. return $this->dao->select('COUNT(*)')->from(TABLE_USER)->where('deleted')->eq('0')->fetch('COUNT(*)');
  239. }
  240. /**
  241. * Update a user.
  242. *
  243. * @param object $user
  244. * @access public
  245. * @return object
  246. */
  247. public function update($user = null)
  248. {
  249. if(empty($user->id)) return null;
  250. $currentClientStatus = $this->dao->select('clientStatus')->from(TABLE_USER)->where('id')->eq($user->id)->fetch('clientStatus');
  251. if($currentClientStatus == 'offline' && !in_array(strtolower($this->app->getMethodName()), array('userlogin')))
  252. {
  253. dao::$errors['clientStatus'][] = 'User is offline.';
  254. return null;
  255. }
  256. $data = array();
  257. /* Updates status. */
  258. if(isset($user->clientStatus) && !empty($user->clientStatus)) $data['clientStatus'] = $user->clientStatus;
  259. /* Changes password. */
  260. if(!empty($user->account) && !empty($user->password)) $data['password'] = $user->password;
  261. /* Updates contact info. */
  262. if(empty($data))
  263. {
  264. foreach($this->config->im->user->canEditFields as $field)
  265. {
  266. if(isset($user->$field)) $data[$field] = $user->$field;
  267. }
  268. }
  269. if(empty($data)) return null;
  270. $data['clientLang'] = $this->session->clientLang;
  271. $this->dao->update(TABLE_USER)->data($data)->where('id')->eq($user->id)->exec();
  272. /* Revoke user's tokens on password change. */
  273. if(!dao::isError() && !empty($user->password)) $this->revokeAuthToken($user->id);
  274. return $this->getByID($user->id);
  275. }
  276. /**
  277. * Format users.
  278. *
  279. * @param mixed $users object | array
  280. * @access public
  281. * @return object | array
  282. */
  283. public function format($users)
  284. {
  285. $isObject = false;
  286. if(is_object($users))
  287. {
  288. $isObject = true;
  289. $users = array($users);
  290. }
  291. $admins = $this->dao->select('admins')->from(TABLE_COMPANY)->where('id')->eq($this->app->company->id)->fetch('admins');$adminArray = explode(',', $admins);
  292. foreach($users as $user)
  293. {
  294. $user->id = (int)$user->id;
  295. $user->dept = (int)$user->dept;
  296. $user->deleted = isset($user->deleted) ? ((bool)$user->deleted ? 1 : 0) : 0;
  297. $user->status = isset($user->clientStatus) ? $user->clientStatus : 0;
  298. if(isset($user->avatar)) $user->avatar = (!empty($user->avatar) && substr($user->avatar, 0, 7) !== 'http://' && substr($user->avatar, 0, 8) !== 'https://') ? $this->loadModel('im')->getServer() . $user->avatar : $user->avatar;
  299. if(!isset($user->signed)) $user->signed = 0;
  300. $user->admin = in_array($user->account, $adminArray) ? 'super' : '';
  301. }
  302. if($isObject) return reset($users);
  303. return $users;
  304. }
  305. /**
  306. * Reset user status.
  307. *
  308. * @param string $status
  309. * @access public
  310. * @return bool
  311. */
  312. public function resetStatus($status = 'offline')
  313. {
  314. $this->dao->update(TABLE_USER)->set('clientStatus')->eq($status)->exec();
  315. return !dao::isError();
  316. }
  317. /**
  318. * Set user status to offline.
  319. *
  320. * @param array $users
  321. * @access public
  322. * @return bool
  323. */
  324. public function setOffline($users = array())
  325. {
  326. if(empty($users)) return true;
  327. $this->dao->update(TABLE_USER)->set('clientStatus')->eq('offline')->where('id')->in($users)->exec();
  328. return !dao::isError();
  329. }
  330. /**
  331. * Get list of users / depts that were created / edited / deleted in the last polling interval.
  332. *
  333. * @param string $type user | dept
  334. * @access public
  335. * @return array
  336. */
  337. public function hasChanges($type = 'user')
  338. {
  339. $timeStr = isset($this->config->xuanxuan->pollingInterval)
  340. ? "- {$this->config->xuanxuan->pollingInterval} seconds"
  341. : '- 60 seconds';
  342. return $this->dao->select('objectID')->from(TABLE_ACTION)
  343. ->where('objectType')->eq($type == 'dept' ? 'deptCategory' : 'user')
  344. ->andWhere('action')->in('create,edit,delete')
  345. ->andWhere('date')->gt(date(DT_DATETIME1, strtotime($timeStr)))
  346. ->fetchPairs();
  347. }
  348. /**
  349. * Update login or logout time of a user-device pair.
  350. *
  351. * @param int $user userID
  352. * @param string $device device token
  353. * @param string $type login | logout
  354. * @access public
  355. * @return bool
  356. */
  357. public function updateDevice($user, $device, $type = 'logout', $version = '')
  358. {
  359. $data = new stdclass();
  360. $data->user = $user;
  361. $data->device = $device;
  362. $data->{"last$type"} = helper::now();
  363. $data->online = $type == 'login' ? 1 : 0;
  364. $data->version = $version;
  365. $stmt = $this->dao->insert(TABLE_IM_USERDEVICE)->data($data)->get();
  366. $stmt .= " ON DUPLICATE KEY UPDATE `last$type` = '{$data->{"last$type"}}',`online` = '{$data->online}'";
  367. if($type == 'login') $stmt .= ",`version` = '{$data->version}'";
  368. $this->dao->exec($stmt);
  369. return !dao::isError();
  370. }
  371. /**
  372. * Get last logout time of user (on device).
  373. *
  374. * @param int $user
  375. * @param string $device
  376. * @access public
  377. * @return string
  378. */
  379. public function getLastLogout($user, $device = '')
  380. {
  381. return $this->dao->select('MAX(lastLogout)')->from(TABLE_IM_USERDEVICE)
  382. ->where('user')->eq($user)
  383. ->beginIF(!empty($device))->andWhere('device')->eq($device)->fi()
  384. ->fetch('MAX(lastLogout)');
  385. }
  386. /**
  387. * Regenerate pinyin for users.
  388. *
  389. * @param array $users
  390. * @access public
  391. * @return bool
  392. */
  393. public function reindexPinyin($users = array())
  394. {
  395. $realnames = $this->dao->select('id,realname')->from(TABLE_USER)
  396. ->beginIF(!empty($users))->where('id')->in($users)->fi()
  397. ->fetchPairs();
  398. $converted = commonModel::convert2Pinyin($realnames);
  399. $pinyinData = array();
  400. foreach($realnames as $id => $realname) $pinyinData[] = "($id,'{$converted[$realname]}')";
  401. $query = "INSERT INTO " . TABLE_USER . "(`id`,`pinyin`) VALUES " . join(',', $pinyinData) . " ON DUPLICATE KEY UPDATE `pinyin`=VALUES(`pinyin`)";
  402. $this->dao->query($query); $this->dao->setCache(trim(TABLE_USER, "`"));
  403. return !dao::isError();
  404. }
  405. /**
  406. * Search for user with account / realname / pinyin in group / dept.
  407. *
  408. * @param string $search
  409. * @param object $options (object)array('chat' => '', 'dept' => '', 'limit' => 51, 'exclude' => [3, 5, 7])
  410. * @param boolean $returnID
  411. * @access public
  412. * @return void
  413. */
  414. public function search($search, $options = array(), $returnID = false, $pager = null)
  415. {
  416. $depts = array();
  417. $chatMembers = array();
  418. $exclude = array();
  419. if(!is_object($options) && is_array($options)) $options = (object)$options;
  420. if(property_exists($options, 'dept') && $options->dept != 0)
  421. {
  422. $depts = $this->loadModel('tree')->getFamily($options->dept, 'dept');
  423. }
  424. if(property_exists($options, 'chat'))
  425. {
  426. $chat = $this->loadModel('im')->chatGetByGid($options->chat, true);
  427. $chatMembers = $chat->members;
  428. }
  429. if(property_exists($options, 'exclude')) $exclude = $options->exclude;
  430. $result = $this->dao->select($returnID ? 'id' : 'id, account, realname, avatar, role, dept, clientStatus, gender, email, mobile, phone, qq, deleted, address, weixin')->from(TABLE_USER)
  431. ->where('1=1')
  432. ->beginIF(!empty($depts))->andWhere('dept')->in($depts)->fi()
  433. ->beginIF(!empty($chatMembers))->andWhere('id')->in($chatMembers)->fi()
  434. ->beginIF(!empty($exclude))->andWhere('id')->notin($exclude)->fi()
  435. ->andWhere('account', $markLeft = true)->like("%$search%")
  436. ->orWhere('pinyin')->like("%$search%")
  437. ->orWhere('realname')->like("%$search%")->markRight(1)
  438. ->beginIF($pager)->page($pager)->fi()
  439. ->beginIF(!$pager)->limit(isset($options->limit) ? $options->limit : 51)->fi()
  440. ->fetchAll();
  441. return $returnID ? array_map(function($obj){return (int)$obj->id;}, $result) : $this->format($result);
  442. }
  443. /**
  444. * Generate a 64 byte hex string as auth token using phpaes.
  445. *
  446. * @access private
  447. * @return string
  448. */
  449. function generateAuthToken()
  450. {
  451. $random = $this->app->loadClass('phpaes')->randomString(32);
  452. return bin2hex($random);
  453. }
  454. /**
  455. * Get or create an auth token for user's device, binds unused token automatically if there is one.
  456. *
  457. * @param int $userID
  458. * @param string $deviceType
  459. * @param string $deviceID
  460. * @access public
  461. * @return object|bool
  462. */
  463. public function getAuthToken($userID, $deviceType = '', $deviceID = '')
  464. {
  465. if(!empty($deviceType))
  466. {
  467. /* Try to fetch unused token and bond token of the device. */
  468. $userTokens = $this->dao->select('*')->from(TABLE_IM_USERDEVICE)
  469. ->where('user')->eq($userID)
  470. ->fetchAll();
  471. $userTokens = array_filter(
  472. $userTokens,
  473. function($userToken) use ($deviceType, $deviceID)
  474. {
  475. return ($userToken->device == '' && $userToken->deviceID == '')
  476. || !($userToken->device != $deviceType || !empty($deviceID) && $userToken->deviceID != $deviceID);
  477. }
  478. );
  479. if(!empty($userTokens))
  480. {
  481. /* If got both an unused token and a device auth token: */
  482. if(count($userTokens) > 1)
  483. {
  484. $bondTokens = array_filter(
  485. $userTokens,
  486. function($userToken) use ($deviceType, $deviceID)
  487. {
  488. return $userToken->device == $deviceType
  489. && (empty($deviceID) || !empty($deviceID) && $userToken->deviceID == $deviceID);
  490. }
  491. );
  492. $bondToken = current($bondTokens);
  493. if(strtotime($bondToken->validUntil) <= time()) $bondToken = $this->renewAuthToken($userID, $deviceType, $deviceID);
  494. return $bondToken;
  495. }
  496. $userToken = current($userTokens);
  497. if(empty($userToken->token) || (!empty($userToken->validUntil) && strtotime($userToken->validUntil) <= time()))
  498. {
  499. return $this->renewAuthToken($userID, $deviceType, $deviceID);
  500. }
  501. if($userToken->device != '') return $userToken;
  502. $this->dao->update(TABLE_IM_USERDEVICE)
  503. ->set('device')->eq($deviceType)
  504. ->set('deviceID')->eq($deviceID)
  505. ->where('id')->eq($userToken->id)
  506. ->exec();
  507. $userToken->device = $deviceType;
  508. $userToken->deviceID = $deviceID;
  509. return $userToken;
  510. }
  511. }
  512. return $this->renewAuthToken($userID, $deviceType, $deviceID);
  513. }
  514. /**
  515. * Renew or just generate auth token for user's device.
  516. *
  517. * @param int $userID
  518. * @param string $deviceType
  519. * @param string $deviceID
  520. * @access public
  521. * @return object|bool
  522. */
  523. public function renewAuthToken($userID, $deviceType = '', $deviceID = '')
  524. {
  525. /* 安全修复:验证 deviceID 格式,防止 SQL 注入 */
  526. /* deviceID 应该是 16 位的 MD5 码,只允许 a-f、A-F、0-9 */
  527. if(!empty($deviceID) && !preg_match('/^[a-fA-F0-9]{16}$/', $deviceID))
  528. {
  529. dao::$errors[] = 'Invalid deviceID format';
  530. return false;
  531. }
  532. $authToken = $this->generateAuthToken();
  533. $tokenLifetime = zget($this->config->xuanxuan, 'tokenLifetime', 30);
  534. $userDevice = new stdclass();
  535. $userDevice->device = $deviceType;
  536. $userDevice->deviceID = $deviceID;
  537. $userDevice->user = $userID;
  538. $userDevice->token = $authToken;
  539. $userDevice->validUntil = date('Y-m-d H:i:s', strtotime("+ $tokenLifetime days"));
  540. $stmt = $this->dao->insert(TABLE_IM_USERDEVICE)->data($userDevice)->get();
  541. $stmt .= " ON DUPLICATE KEY UPDATE `deviceID` = '{$userDevice->deviceID}', `token` = '{$userDevice->token}', `validUntil` = '{$userDevice->validUntil}';"; // TODO: make deviceID persistent.
  542. $this->dao->exec($stmt);
  543. if(dao::isError()) return false;
  544. return $userDevice;
  545. }
  546. /**
  547. * Revoke user's auth token for specific device.
  548. *
  549. * @param int $userID
  550. * @param string $deviceType
  551. * @param string $deviceID
  552. * @access public
  553. * @return bool
  554. */
  555. public function revokeAuthToken($userID, $deviceType = '', $deviceID = '')
  556. {
  557. $this->dao->update(TABLE_IM_USERDEVICE)
  558. ->set('validUntil')->eq(helper::now())
  559. ->where('user')->eq($userID)
  560. ->beginIF(!empty($deviceType))->andWhere('device')->eq($deviceType)->fi()
  561. ->beginIF(!empty($deviceID))->andWhere('deviceID')->eq($deviceID)->fi()
  562. ->exec();
  563. return !dao::isError();
  564. }
  565. /**
  566. * Get list of users who changed password but did not re-login.
  567. *
  568. * @access public
  569. * @return array
  570. */
  571. public function getChangedPassword()
  572. {
  573. $actionObjectIDs = array();
  574. $passwordChangeActions = array();
  575. $loginHistoryActions = array();
  576. $passwordChangeActions = $this->loadModel('action')->getListSinceLastPoll('changepassword');
  577. $loginHistoryActions = $this->loadModel('action')->getListSinceLastPoll('loginxuanxuan');
  578. foreach($passwordChangeActions as $passwordChange)
  579. {
  580. $loginExist = false;
  581. foreach($loginHistoryActions as $login)
  582. {
  583. if ($passwordChange->objectID === $login->objectID)
  584. {
  585. $loginExist = true;
  586. if( strtotime($login->date) < strtotime($passwordChange->date)) $actionObjectIDs[] = (int)$passwordChange->objectID;
  587. }
  588. }
  589. if(!$loginExist) $actionObjectIDs[] = (int)$passwordChange->objectID;
  590. }
  591. return $actionObjectIDs;
  592. }
  593. /**
  594. * Get user ID list of deleted online users.
  595. *
  596. * @access public
  597. * @return array
  598. */
  599. public function getOnlineDeleted()
  600. {
  601. $userIDs = $this->dao->select('id')->from(TABLE_USER)
  602. ->where('deleted')->eq(1)
  603. ->andWhere('clientStatus')->ne('offline')
  604. ->fetchAll('id');
  605. if(dao::isError()) return array();
  606. return array_values(array_map(function($obj){return (int)$obj->id;}, $userIDs));
  607. }
  608. /**
  609. * Get user ID list of forbidden online users.
  610. *
  611. * @access public
  612. * @return array
  613. */
  614. public function getOnlineForbidden()
  615. {
  616. $userIDs = $this->dao->select('id')->from(TABLE_USER)
  617. ->where('locked')->ge(helper::now())
  618. ->andWhere('clientStatus')->ne('offline')
  619. ->fetchAll('id');
  620. if(dao::isError()) return array();
  621. return array_values(array_map(function($obj){return (int)$obj->id;}, $userIDs));
  622. }
  623. /**
  624. * Add user action.
  625. *
  626. * @param int|string $user userID or user's account.
  627. * @param string $actionType
  628. * @param string $result
  629. * @param string $comment
  630. * @param bool $common
  631. * @access public
  632. * @return void
  633. */
  634. public function addAction($user, $actionType, $result, $comment = '', $common = false)
  635. {
  636. if(!zget($this->config->xuanxuan, 'logLevel', 1) && !$common) return;
  637. $account = '';
  638. $userID = 0;
  639. if(is_int($user))
  640. {
  641. $account = $this->dao->select('account')->from(TABLE_USER)->where('id')->eq($user)->fetch('account');
  642. $userID = $user;
  643. }
  644. if(is_string($user))
  645. {
  646. $userID = $this->dao->select('id')->from(TABLE_USER)->where('account')->eq($user)->fetch('id');
  647. $account = $user;
  648. }
  649. $actor = !empty($account) ? $account : '';
  650. $extra = json_encode(array('actorId' => $userID));
  651. $this->loadModel('action')->create('user', $userID, $actionType, $result, $comment, $extra, $actor);
  652. }
  653. /**
  654. * Compare a user online device version with a given version.
  655. *
  656. * @param int $userID
  657. * @param string $compareVersion
  658. * @param string $deviceType
  659. * @access public
  660. * @return bool
  661. */
  662. public function isDeviceVersionGe($userID, $compareVersion, $deviceType = 'desktop')
  663. {
  664. $version = $this->dao->select('version')->from(TABLE_IM_USERDEVICE)
  665. ->where('user')->eq($userID)
  666. ->andWhere('device')->eq($deviceType)
  667. ->fetch('version');
  668. if(dao::isError() || empty($version)) return false;
  669. return version_compare($version, $compareVersion, '>=');
  670. }
  671. }