PageRenderTime 68ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Lib/Base/Service/User.php

https://github.com/ilyich/iqyou
PHP | 3618 lines | 2415 code | 501 blank | 702 comment | 469 complexity | 4367631cc482a0023f323c15ae1d1730 MD5 | raw file
  1. <?php
  2. class Base_Service_User
  3. {
  4. const BAN_COOKIE_NAME = 'f_ubx';
  5. const COOKIE_CHECKSUM = 27;
  6. const USER_GENDER_FEMALE = 0;
  7. const USER_GENDER_MALE = 1;
  8. const USER_GENDER_UNKNOWN = 2;
  9. const SALT_TYPE_COOKIE = 'cookie';
  10. const SALT_TYPE_COOKIE_OLD = 'cookie_old';
  11. const SALT_TYPE_EMAIL = 'email';
  12. const SALT_TYPE_PASSWORD = 'password';
  13. /* Bitwise online/offline statuses */
  14. const SOURCE_OFFLINE = 0;
  15. // Online on site
  16. const SOURCE_FS_ONLINE = 1;
  17. const SOURCE_FS_AWAY = 2;
  18. // Online in messenger
  19. const SOURCE_FSM_ONLINE = 4;
  20. const SOURCE_FSM_AWAY = 8;
  21. // Online from mibile version of FS
  22. const SOURCE_MOBILE_ONLINE = 16;
  23. const SOURCE_MOBILE_AWAY = 32;
  24. // Online from iPhone
  25. const SOURCE_IPHONE_ONLINE = 64;
  26. const SOURCE_IPHONE_AWAY = 128;
  27. // Тут внимание! Добвлять новые сурсы можно но мы ограничены 16 битами (8 из которых уже заняты)
  28. const ONLINE_BIT_SHIFT = 16; // x bit shift
  29. const ONLINE_MAX_ONLINE_TIME = 86400; //24 * 60 * 60 - максимальное время онлайна
  30. const ONLINE_TIMEOUT_UPDATE = 180; // 3 * 60 - тамймаут, когда записывать юзера в up
  31. /*Sources*/
  32. const ONLINE_SOURCE_FS = 1;
  33. const ONLINE_SOURCE_FSM = 2;
  34. const ONLINE_SOURCE_MOBILE = 3;
  35. const ONLINE_SOURCE_IPHONE = 4;
  36. /*statuses*/
  37. const ONLINE_STATUS_ONLINE = 1;
  38. const ONLINE_STATUS_UPDATE = 2;
  39. const ONLINE_STATUS_OFFLINE = 3;
  40. const ACTIVITY_GROUP_ERROR = 5;
  41. const ACTIVITY_GROUP_NEWUSER = 1;
  42. const ACTIVITY_GROUP_ACTIVE = 2;
  43. const ACTIVITY_GROUP_INACTIVE = 3;
  44. const ACTIVITY_GROUP_LOST = 4;
  45. const M_GROUP_AGE_KID = 1;
  46. const M_GROUP_AGE_ADULT = 4;
  47. const M_GROUP_AGE_NO_AGE = 6;
  48. // const M_GROUP_AGE_TEEN = 2;
  49. // const M_GROUP_AGE_YOUNG = 3;
  50. // const M_GROUP_AGE_AGED = 5;
  51. const M_GROUP_MALE = 1;
  52. const M_GROUP_FEMALE = 2;
  53. const F_ACT_GROUP = 1;
  54. const F_AGE_GROUP = 2;
  55. const F_SEX_GROUP = 3;
  56. const ZODIAC_ARIES = 1; // Овен
  57. const ZODIAC_TAURUS = 2; // Телец
  58. const ZODIAC_GEMINI = 3; // Близнецы
  59. const ZODIAC_CANCER = 4; // Рак
  60. const ZODIAC_LEO = 5; // Лев
  61. const ZODIAC_VIRGO = 6; // Дева
  62. const ZODIAC_LIBRA = 7; // Весы
  63. const ZODIAC_SCORPIO = 8; // Скорпион
  64. const ZODIAC_SAGITTARIUS = 9; // Стрелец
  65. const ZODIAC_CAPRICORN = 10; // Козерог
  66. const ZODIAC_AQUARIUS = 11; // Водолей
  67. const ZODIAC_PISCES = 12; // Рыбы
  68. public static $zodiacs = array(
  69. self::ZODIAC_ARIES => array(
  70. 'name' => 'Овен',
  71. 'range' => array(321, 420),
  72. 'suitable_signs' => array(self::ZODIAC_GEMINI, self::ZODIAC_LEO, self::ZODIAC_CAPRICORN, self::ZODIAC_AQUARIUS, self::ZODIAC_PISCES),
  73. ),
  74. self::ZODIAC_TAURUS => array(
  75. 'name' => 'Телец',
  76. 'range' => array(421, 520),
  77. 'suitable_signs' => array(self::ZODIAC_CANCER, self::ZODIAC_LEO, self::ZODIAC_VIRGO, self::ZODIAC_LIBRA, self::ZODIAC_CAPRICORN, self::ZODIAC_PISCES),
  78. ),
  79. self::ZODIAC_GEMINI => array(
  80. 'name' => 'Близнецы',
  81. 'range' => array(521, 621),
  82. 'suitable_signs' => array(self::ZODIAC_ARIES, self::ZODIAC_LEO, self::ZODIAC_LIBRA, self::ZODIAC_AQUARIUS, self::ZODIAC_PISCES),
  83. ),
  84. self::ZODIAC_CANCER => array(
  85. 'name' => 'Рак',
  86. 'range' => array(622, 722),
  87. 'suitable_signs' => array(self::ZODIAC_ARIES, self::ZODIAC_LEO, self::ZODIAC_LIBRA, self::ZODIAC_AQUARIUS, self::ZODIAC_PISCES),
  88. ),
  89. self::ZODIAC_LEO => array(
  90. 'name' => 'Лев',
  91. 'range' => array(723, 823),
  92. 'suitable_signs' => array(self::ZODIAC_ARIES, self::ZODIAC_TAURUS, self::ZODIAC_GEMINI, self::ZODIAC_CANCER, self::ZODIAC_LEO, self::ZODIAC_LIBRA, self::ZODIAC_SAGITTARIUS),
  93. ),
  94. self::ZODIAC_VIRGO => array(
  95. 'name' => 'Дева',
  96. 'range' => array(824, 923),
  97. 'suitable_signs' => array(self::ZODIAC_TAURUS, self::ZODIAC_CANCER, self::ZODIAC_VIRGO, self::ZODIAC_SCORPIO, self::ZODIAC_CAPRICORN, self::ZODIAC_AQUARIUS),
  98. ),
  99. self::ZODIAC_LIBRA => array(
  100. 'name' => 'Весы',
  101. 'range' => array(924, 1022),
  102. 'suitable_signs' => array(self::ZODIAC_TAURUS, self::ZODIAC_GEMINI, self::ZODIAC_LEO, self::ZODIAC_LIBRA, self::ZODIAC_SAGITTARIUS, self::ZODIAC_AQUARIUS),
  103. ),
  104. self::ZODIAC_SCORPIO => array(
  105. 'name' => 'Скорпион',
  106. 'range' => array(1023, 1122),
  107. 'suitable_signs' => array(self::ZODIAC_CANCER, self::ZODIAC_VIRGO, self::ZODIAC_CAPRICORN, self::ZODIAC_PISCES),
  108. ),
  109. self::ZODIAC_SAGITTARIUS => array(
  110. 'name' => 'Стрелец',
  111. 'range' => array(1123, 1221),
  112. 'suitable_signs' => array(self::ZODIAC_LEO, self::ZODIAC_LIBRA, self::ZODIAC_AQUARIUS),
  113. ),
  114. self::ZODIAC_CAPRICORN => array(
  115. 'name' => 'Козерог',
  116. 'range' => array(1222, 120),
  117. 'suitable_signs' => array(self::ZODIAC_ARIES, self::ZODIAC_TAURUS, self::ZODIAC_VIRGO, self::ZODIAC_SCORPIO, self::ZODIAC_CAPRICORN, self::ZODIAC_PISCES),
  118. ),
  119. self::ZODIAC_AQUARIUS => array(
  120. 'name' => 'Водолей',
  121. 'range' => array(121, 219),
  122. 'suitable_signs' => array(self::ZODIAC_ARIES, self::ZODIAC_GEMINI, self::ZODIAC_VIRGO, self::ZODIAC_LIBRA, self::ZODIAC_SAGITTARIUS, self::ZODIAC_AQUARIUS),
  123. ),
  124. self::ZODIAC_PISCES => array(
  125. 'name' => 'Рыбы',
  126. 'range' => array(220, 320),
  127. 'suitable_signs' => array(self::ZODIAC_ARIES, self::ZODIAC_TAURUS, self::ZODIAC_GEMINI, self::ZODIAC_CANCER, self::ZODIAC_SCORPIO, self::ZODIAC_CAPRICORN),
  128. ),
  129. );
  130. /* Time outs by source */
  131. private static $onlineAwayTimeout = array(
  132. // online type => timeout in seconds
  133. self::SOURCE_FS_ONLINE => 900, // 60 * 15
  134. self::SOURCE_FSM_ONLINE => 900, // 60 * 15
  135. self::SOURCE_MOBILE_ONLINE => 900, // 60 * 15
  136. self::SOURCE_IPHONE_ONLINE => 900, // 60 * 15
  137. );
  138. private static $_systemUsersIds = array(
  139. 1, // Фотострана
  140. 2, // Служба поддержки
  141. 3, // Служба модерации
  142. 57267704, // Служба Поддержки ФотоЧата
  143. 63760510, // Тематические новости в ньюсфиде
  144. 63760585, // Тематические новости в ньюсфиде
  145. 63760651, // Тематические новости в ньюсфиде
  146. 63760698, // Тематические новости в ньюсфиде
  147. 63760742, // Тематические новости в ньюсфиде
  148. 63760791, // Тематические новости в ньюсфиде
  149. 63760836, // Тематические новости в ньюсфиде
  150. 63760932, // Тематические новости в ньюсфиде
  151. 65396826, // Социальные таргетинговые объявления
  152. 70388414, // Бот Новости Фотостраны
  153. );
  154. private static function getSalt($type)
  155. {
  156. $saltConfig = Base_Application::getInstance()->config['passwd']['salt'];
  157. return !empty($saltConfig[$type]) ? $saltConfig[$type] : '';
  158. }
  159. public static function getUserProfileUrl($user, $native = false)
  160. {
  161. if ($native) {
  162. $project = $user->getNativeProject();
  163. } else {
  164. $project = Base_Project_Manager::getProject();
  165. }
  166. $domain = $project->getDomain();
  167. if (Base_Service_Common::isStage(false)) {
  168. $domain = 'stage.' . $domain;
  169. }
  170. $isRambler = Base_Project_Manager::getProject()->isRamblerWL() || $project->isRamblerWl();
  171. // костыль для letitbit и pet.rambler.ru
  172. if (!Base_Project_Manager::isOurDomainId($project->getDomainModel()->getId()) ||
  173. $isRambler) {
  174. return 'http://' . $domain . '/user/' . $user['user_id'] . '/';
  175. }
  176. if (!isset($user['user_id']) || !$user['user_id']) {
  177. return 'http://' . $domain . '/user/deleteduser/';
  178. }
  179. if (PetApp_Base_Service_Project::isPetAppProject($project->getType())) {
  180. return $user['user_pet_id'] ? 'http://' . $domain . '/pet/' . $user['user_pet_id'] . '/' :
  181. 'http://' . $domain . '/user/'. $user['user_id'] .'/';
  182. }
  183. if (isset($user['user_pagename']) && $user['user_pagename']) {
  184. if (Base_Project_Fotostrana::fotostrana2Enabled()) {
  185. // для определения ссылок на пользователей в фс 2.0 добавляем /u/. для аякс-переходов
  186. // @todo найти нормальное решение, не требующее изменения урлов.
  187. return 'http://' . $domain . '/u/' . $user['user_pagename'] . '/';
  188. }
  189. return 'http://' . $domain . '/' . $user['user_pagename'] . '/';
  190. } else {
  191. return 'http://' . $domain . '/user/' . $user['user_id'] . '/';
  192. }
  193. }
  194. public static function getInterestPeople($user)
  195. {
  196. $userId = $user->getId();
  197. $cachedId = Base_Service_Memcache::get(Base_Dao_User::MC_USER_INTERESTPEOPLE.$userId, __METHOD__);
  198. if($cachedId!==false) {
  199. $result = Base_Dao_User::getUsersByIds($cachedId);
  200. } else {
  201. if(!($user instanceof Base_Model_User) || !$user) {
  202. return array();
  203. }
  204. $periods = array(Base_Service_Interest_Tracker::USERS_PERIOD_DAY, //0
  205. Base_Service_Interest_Tracker::USERS_PERIOD_WEEK, //1
  206. Base_Service_Interest_Tracker::USERS_PERIOD_MONTH); //2
  207. $result = array();
  208. $userFriendsIds = Friends_Service_New::getFriends($userId, 50, 0, true);
  209. $userFavIds = Usercontact_Dao_Favorite::getRight($userId, 10);
  210. $t = Base_Dao_User::getUsersByIds(array_unique($userFavIds[2]));
  211. $userFavs = array();
  212. foreach($t as $u){
  213. if ($u->isPetAppUser()) {
  214. Base_Service_Counter_Social::getInstance()->increment($u, 'test_stat_pet_only_in_favorite_left', 1);
  215. Base_Service_Counter_Social::getInstance()->increment($user, 'test_stat_pet_only_in_favorite_right', 1);
  216. }
  217. if($u->isOnline()) {
  218. $userFavs[] = $u;
  219. }
  220. }
  221. if(count($userFavs)==0 || count($userFriendsIds)==0) {
  222. $interests = array(15,10,5);
  223. } else {
  224. $interests = array(15,5,5);
  225. }
  226. $userInterestIds = array();
  227. foreach($periods as $p) {
  228. $t = Base_Service_Interest_Tracker::getUserContactsPopular($userId, $p, $interests[$p]);
  229. if(is_array($t)) {
  230. foreach($t as $u=>$c) {
  231. if($c>2 && $u!=0) {
  232. $userInterestIds[] = $u;
  233. }
  234. }
  235. }
  236. }
  237. $userInterest = Base_Dao_User::getUsersByIds(array_unique($userInterestIds));
  238. $userFriendsIds = array_diff($userFriendsIds, $userInterestIds, $userFavIds);
  239. $userFriends = Base_Dao_User::getUsersByIds($userFriendsIds);
  240. $users = array_merge($userInterest, $userFavs, $userFriends);
  241. //$usersIds = array_merge($userInterestIds, $userFavIds[2], $userFriendsIds);
  242. $resIds = array();
  243. foreach($users as $u) {
  244. if($u->hasMainPhoto() && $u->getId()>1 && !in_array($u, $result)) {
  245. $result[] = $u;
  246. $resIds[] = $u->getId();
  247. }
  248. if(count($result)>=20) break;
  249. }
  250. Base_Service_Memcache::set(Base_Dao_User::MC_USER_INTERESTPEOPLE . $userId, $resIds);
  251. }
  252. return $result;
  253. }
  254. public static function generateUserEmailHash($userOrEmail)
  255. {
  256. $salt = self::getSalt('email');
  257. if ($userOrEmail instanceof Base_Model_User) {
  258. return md5($userOrEmail->getEmail() . $salt);
  259. } else {
  260. return md5($userOrEmail . $salt);
  261. }
  262. }
  263. /**
  264. * convert user birthday to age
  265. *
  266. * @param $userBirthday
  267. * @param bool $asString
  268. * @param bool $baseTime
  269. *
  270. * @return bool|int|string
  271. */
  272. public static function getUserAge($userBirthday, $asString = false, $baseTime = false)
  273. {
  274. if (!$userBirthday || $userBirthday == '0000-00-00') {
  275. return false;
  276. }
  277. $baseTime = ($baseTime === false) ? TIME : $baseTime;
  278. list($year, $month, $day) = explode('-', $userBirthday);
  279. $yearDiff = date('Y', ($baseTime)) - $year;
  280. $monthDiff = date('m', ($baseTime)) - $month;
  281. $dayDiff = date('d', ($baseTime)) - $day;
  282. if ($monthDiff < 0) {
  283. $yearDiff--;
  284. } elseif (($monthDiff == 0) && ($dayDiff < 0)) {
  285. $yearDiff--;
  286. }
  287. return $asString ? _f('{plural|%d год|%d года|%d лет|%d лет}', $yearDiff) : $yearDiff;
  288. }
  289. /**
  290. * Получть хеш пароля из пароля. Он хранится в бд
  291. */
  292. public static function getPasswordHash($password)
  293. {
  294. return md5($password . self::getSalt(self::SALT_TYPE_PASSWORD));
  295. }
  296. public static function getPasswordCrypt($password)
  297. {
  298. $secretKey = Base_Application::getInstance()->config['passwd']['user']['crypt_key'];
  299. return Base_Service_Crypt::crypt($password, true, $secretKey);
  300. }
  301. public static function getPasswordDecrypt($password)
  302. {
  303. if(Base_Mailer_Service_Config::isPreviewMode()) {
  304. return 'password_hidden';
  305. }
  306. $secretKey = Base_Application::getInstance()->config['passwd']['user']['crypt_key'];
  307. return Base_Service_Crypt::decrypt($password, true, $secretKey);
  308. }
  309. /**
  310. * Получить хеш для авторизации
  311. *
  312. * @param $userId ID пользователя
  313. * @param $userEmail e-mail пользователя
  314. * @param $userPasswordHash хеш пароля пользователя
  315. * @param bool $includeASCode включать ли в хеш провайдера
  316. * @param bool $oldSalt использовать ли старую соль (для переходного периода)
  317. *
  318. * @return string
  319. */
  320. public static function getAuthHash($userId, $userEmail, $userPasswordHash, $includeASCode = false, $oldSalt = false)
  321. {
  322. $salt = self::getSalt($oldSalt ? self::SALT_TYPE_COOKIE_OLD : self::SALT_TYPE_COOKIE);
  323. $provider = '';
  324. if ($includeASCode) {
  325. $ip = Base_Service_Common::getRealIp();
  326. $location = Base_Dao_Geo::getLocationByIp($ip);
  327. if (!empty($location['provider'])) {
  328. $provider = crc32(Utf::trim($location['provider']));
  329. }
  330. }
  331. return md5($userEmail . $userPasswordHash . $provider . $salt) . $userId % self::COOKIE_CHECKSUM;
  332. }
  333. /**
  334. * @deprecated use Base_Service_User::getAuthHash
  335. */
  336. public static function getUserPasswordSalt($userId, $userEmail, $userPassword, $newEmail = '')
  337. {
  338. return md5($userEmail . $userPassword . $newEmail . self::getSalt(self::SALT_TYPE_COOKIE_OLD)) . $userId % self::COOKIE_CHECKSUM;
  339. }
  340. /**
  341. * Упрощенная авторизация. Используется там, где не нужна повышенная безопасность
  342. *
  343. * @param $userId
  344. * @param $cookieSecretWord
  345. *
  346. * @return bool
  347. */
  348. public static function checkPasswordHash($userId, $cookieSecretWord)
  349. {
  350. $uidHash = $userId % Base_Service_User::COOKIE_CHECKSUM;
  351. return ($uidHash == Utf::substr($cookieSecretWord, -Utf::strlen($uidHash), Utf::strlen($uidHash)));
  352. }
  353. /**
  354. * Авторизует пользователя на сайте
  355. *
  356. * @param Base_Model_User $user
  357. *
  358. * @return bool
  359. */
  360. public static function logIn($user)
  361. {
  362. if (!$user) {
  363. return false;
  364. }
  365. Service_Base::setCookie(
  366. 'uid',
  367. $user->getId(),
  368. 100, '/', true, 2
  369. );
  370. Service_Base::setCookie(
  371. 'hw',
  372. Base_Service_User::getAuthHash($user->getId(), $user->getEmail(), $user->getPasswordHash(), false),
  373. 100, '/', true, 2
  374. );
  375. Antispam_Service_Token::setToken();
  376. $trace = Base_Service_Log::getTrace(3);
  377. Pet_Dao_Trace::log($user->getId(), 'lgn: ' . implode(',', $trace));
  378. return true;
  379. }
  380. public static function logout()
  381. {
  382. Service_Base::setCookie('hw', '', -1, '/', true, 2);
  383. Service_Base::setCookie('uid', '', -1, '/', true, 2);
  384. }
  385. /**
  386. * @param Base_Model_User $user
  387. *
  388. * @return boolean
  389. */
  390. public static function checkUserNeedsActivation($user)
  391. {
  392. if (!$user) {
  393. return false;
  394. }
  395. if (!$user->getRefId() && !$user->isEmailApproved() && $user->isFsUser()) {
  396. return true;
  397. }
  398. return false;
  399. }
  400. /**
  401. * Check User for ban
  402. *
  403. * @param Base_Model_User $user
  404. *
  405. * @return bool
  406. */
  407. public static function checkUserBanCookie($user)
  408. {
  409. return;
  410. // if (!$user->isBanned() && isset($_COOKIE[self::BAN_COOKIE_NAME])) {
  411. // Service_Base::setCookie(self::BAN_COOKIE_NAME, '');
  412. // } elseif ($user->isBanned() && $user->isBanned() != Db_Moders::BAN_REASON_NEED_APPROVE_EMAIL && !isset($_COOKIE[self::BAN_COOKIE_NAME])) {
  413. // Service_Base::setCookie(self::BAN_COOKIE_NAME, 1, 1);
  414. // }
  415. }
  416. /**
  417. * Проверям, может ли юзер стать претендентом в эксперты
  418. *
  419. * @param Base_Model_User $user пользователь
  420. * @param $type (expert, moder, petgid, guide)
  421. * @param $reason Возвращает причину отказа (в виде кода)
  422. *
  423. * @return bool
  424. */
  425. public static function canBeExpert($user, $type, &$reason = '')
  426. {
  427. $reason = '';
  428. assert(in_array($type, array('expert', 'moder', 'petgid', 'guide')));
  429. if (!($user instanceof Base_Model_User)) {
  430. trigger_error("Please pass Base_Model_User, not an array", E_USER_WARNING);
  431. $user = Base_Dao_User::getUserById($user['user_id']);
  432. }
  433. /*if(!$user['user_identity_approved']) {
  434. if($type != 'petgid') {
  435. $reason = 'need_identity_approved';
  436. return false;
  437. }
  438. }*/
  439. if ($type == 'petgid' && $user->hasProfession()) {
  440. $reason = 'has_profession';
  441. return false;
  442. }
  443. // Требуем наличия подтвержденного телефона
  444. if ($user->getUserClass() !== Db_User::USER_CLASS_CITIZEN) {
  445. $reason = 'must_be_citizen';
  446. return false;
  447. }
  448. if(self::getUserAge($user['user_birthday'])<18 && $type!='petgid'){
  449. $reason = 'must_be_18';
  450. return false;
  451. }
  452. if ($type=='petgid') {
  453. if (self::getUserAge($user['user_birthday'])<16) {
  454. $reason = 'must_be_16';
  455. return false;
  456. }
  457. $pet = $user->getPet();
  458. if (!$pet) {
  459. $reason = 'no_pet';
  460. return false;
  461. }
  462. if ($pet->getDaltLevel() < 10) {
  463. $reason = 'pet_dalt_10';
  464. return false;
  465. }
  466. }
  467. $dbModers = new Admin_Dao_User();
  468. $bans = $dbModers->getUsersViolations(array($user['user_id']),'adminban',true);
  469. if(!empty($bans)) {
  470. $reason = 'has_bans';
  471. return false;
  472. }
  473. if(Support_Dao_Base::getExpertRequestByUserId($user['user_id'],$type)) {
  474. $reason = 'already_applied';
  475. return false;
  476. }
  477. return true;
  478. }
  479. /**
  480. * Возвращает HTML-текст с сообщением об ошибке для данного кода ошибки.
  481. */
  482. public static function getErrorText($errorCode)
  483. {
  484. $simpleCodes = array(
  485. 'need_identity_approved' => _g('Чтобы получить профессию, надо сначала подтвердить свою личность.'),
  486. 'need_more_reputation' => _g('У тебя пока недостаточно репутации, чтобы получить профессию.'), // deprecated
  487. 'must_be_18' => _g('Тебе должно быть не меньше 18 лет!'),
  488. 'must_be_16' => _g('Тебе должно быть не меньше 16 лет!'),
  489. 'has_profession' => _g('У тебя уже есть профессия на сайте!'),
  490. 'no_pet' => _g('У тебя нет питомца.'),
  491. 'pet_dalt_10' => _g('У твоего питомца недостаточно способностей.'),
  492. 'has_bans' => _g('Ты был наказан за нарушения.'),
  493. 'already_applied' => _g('Ты уже подавал заявку на получение профессии. Дождись ее рассмотрения.'),
  494. 'must_be_citizen' => _g('Тебе необходимо подтвердить свой номер телефона.')
  495. );
  496. if ($errorCode == '') {
  497. // No error
  498. return '';
  499. }
  500. if (isset($simpleCodes[$errorCode])) {
  501. return $simpleCodes[$errorCode];
  502. }
  503. throw new Base_Exception("Invalid error code: '$errorCode'");
  504. }
  505. public static function canBeWriter($user)
  506. {
  507. return false; // профессия выключена для всех
  508. if (Event_Service_Editor::userInBan($user['user_id'])) {
  509. return false;
  510. }
  511. return
  512. !$user['user_is_volunteer']
  513. && $user['user_class'] == Db_User::USER_CLASS_CITIZEN;
  514. }
  515. public static function getDeletedUserInto(&$user)
  516. {
  517. $user['is_deleted'] = true;
  518. $user['user_name'] = _g('Удаленный житель');
  519. $user['user_pagename'] = null;
  520. $user['user_phone'] = null;
  521. $user['user_phone_approved'] = '0';
  522. $user['user_sex'] = 'm';
  523. $user['user_inserted'] = '2009-01-01 00:00:00';
  524. $user['user_updated'] = '2009-01-01 00:00:00';
  525. $user['user_email'] = 'deleted@fotostrana.ru';
  526. $user['password_hash'] = '';
  527. $user['user_email_approved'] = null;
  528. $user['user_ip'] = null;
  529. $user['user_id_from'] = null;
  530. $user['user_about'] = null;
  531. $user['user_city'] = null;
  532. $user['user_last_mail'] = '2009-01-01 00:00:00';
  533. $user['user_is_hidden'] = null;
  534. $user['user_is_volunteer'] = null;
  535. $user['user_source'] = null;
  536. $user['user_source_result'] = null;
  537. $user['user_not_activated'] = null;
  538. $user['user_country_id'] = 0;
  539. $user['user_city_id'] = 0;
  540. $user['user_region_id'] = 0;
  541. $user['user_birthday'] = null;
  542. $user['user_ref_id'] = null;
  543. $user['user_ref_pa'] = '0';
  544. $user['user_deviz'] = null;
  545. $user['user_why'] = null;
  546. $user['user_galleries'] = null;
  547. $user['user_status'] = null;
  548. $user['user_bans'] = null;
  549. $user['user_warn'] = null;
  550. $user['user_cash'] = '0.00';
  551. $user['user_hold'] = '0.00';
  552. $user['user_class'] = '0';
  553. $user['terms_agree'] = '1';
  554. $user['time_in'] = 1;
  555. $user['time_out'] = 1;
  556. $user['user_pet_id'] = 0;
  557. $user['user_photo_id'] = -1;
  558. return $user;
  559. }
  560. public static function getDeletedUser($userId)
  561. {
  562. $user = array();
  563. $user['user_id'] = $userId;
  564. self::getDeletedUserInto($user);
  565. return new Base_Model_User($user);
  566. }
  567. public static function validateEmail($email)
  568. {
  569. return Utf::preg_match('/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/', $email);
  570. }
  571. /**
  572. * исправить ошибки в написании пароля:
  573. * убирает пробелы на краях и заменяет русские символы транслитом
  574. * (если забыл сменить язык)
  575. */
  576. public static function correctPassword($pswd)
  577. {
  578. $pswd = trim($pswd);
  579. // strtr получается в 4е раза быстрее чем str_replace
  580. return strtr($pswd, 'ёйцукенгшщзфывапролдячсмитьЁЙЦУКЕНГШЩЗФЫВАПРОЛДЯЧСМИТЬ', '`qwertyuiopasdfghjklzxcvbnm~QWERTYUIOPASDFGHJKLZXCVBNM');
  581. }
  582. /**
  583. * Валидатор для имени пользователя
  584. *
  585. * @param $name
  586. * @param string $error
  587. * @return bool
  588. */
  589. public static function validateUsername($name, &$error = '', &$debug = '')
  590. {
  591. // если нужно заблочить смешанное имя (кириллица + латиница),
  592. // можно добавлять в любой массив
  593. // разделение массивов сделано для увеличения производительности в
  594. // ф-циях preg_match
  595. if (Base_Service_Common::isOurIp()) {
  596. return true;
  597. }
  598. $name = str_replace(chr(173), '', $name);
  599. // stop words in Antifraud_Service_Badwords
  600. $cyrStopWords = Antifraud_Service_Badwords::getWords(Antifraud_Service_Badwords::CYR_NAMES);
  601. $latStopWords = Antifraud_Service_Badwords::getWords(Antifraud_Service_Badwords::LAT_NAMES);
  602. array_walk($cyrStopWords, function (&$value, $key) {
  603. $value = preg_quote($value);
  604. });
  605. array_walk($latStopWords, function (&$value, $key) {
  606. $value = preg_quote($value);
  607. });
  608. $error = '';
  609. $toCyrName = strtr($name, 'aA6cCeETmHoOpPkKxXBMbryYUunl3gt', 'аАбсСеЕТтНоОрРкКхХВМьгуУИип1Здт'); // переводим имя в кириллицу
  610. $toLatName = strtr($name, 'аАбсСеЕТтНоОрРкКхХВМьгуУИип1Здт', 'aA6cCeETmHoOpPkKxXBMbryYUunl3gt'); // переводим имя в латиницу
  611. $name = Utf::trim(mb_strtolower($name));
  612. $toCyrName = Utf::trim(mb_strtolower($toCyrName));
  613. $toLatName = Utf::trim(mb_strtolower($toLatName));
  614. // первый этап проверки, если юзер меняет на что-то вроде админ или admin (т.е. не использует замены символов на похожие)
  615. if (!$error && Utf::preg_match('/(' . implode('|', array_merge($cyrStopWords, $latStopWords)) . ')/', $name)) {
  616. $error = 'stop_word';
  617. $debug = 'lat_or_cyr_stop_word';
  618. }
  619. // второй этап, если юзер ввел имя, используя замену кириллических символов на похожие латинские
  620. if (!$error && Utf::preg_match('/(' . implode('|', $cyrStopWords) . ')/', $toCyrName)) {
  621. $error = 'stop_word';
  622. $debug = 'cyr_to_lat_change';
  623. }
  624. // третий этап, если юзер ввел имя, используя замену латинских символов на похожие кириллические
  625. if (!$error && Utf::preg_match('/(' . implode('|', $latStopWords) . ')/', $toLatName)) {
  626. $error = 'stop_word';
  627. $debug = 'lat_to_cyr_change';
  628. }
  629. // normalize urls
  630. $toLatName = str_replace(' ', '', $toLatName);
  631. $toLatName = preg_replace('#[.]+#', '.', $toLatName);
  632. if (!$error && Utf::preg_match(Base_Util_String::getUrlPreg(), $toLatName) > 0) {
  633. $error = 'stop_word';
  634. $debug = 'url_preg';
  635. }
  636. $statsClient = new Base_Service_Counter_Main();
  637. $defaultUser = Base_Dao_User::getUserById(1);
  638. $statsClient->increment($defaultUser, 'signup_time_name_check', 1);
  639. if ($error) {
  640. $statsClient->increment($defaultUser, 'signup_time_name_block', 1);
  641. return false;
  642. }
  643. return true;
  644. }
  645. public static function getRemainingDeleteRequestTime($userId)
  646. {
  647. $requestTime = Base_Dao_User::getDeleteRequestTime($userId);
  648. if (!$requestTime) {
  649. return false;
  650. }
  651. $requestTimeEnd = $requestTime + 60*60*24;
  652. $timeRemaining = $requestTimeEnd - time();
  653. // Пользователь за 5 дней не подтвердил желание удалиться, удаляем заявку
  654. $user = Base_Dao_User::getUserById($userId);
  655. if($timeRemaining < -60*60*24*5 && $user['user_is_hidden'] != Db_Moders::DELAYED_DELETED){
  656. Base_Dao_User::cancelDeleteRequest($userId);
  657. return false;
  658. }
  659. if ($timeRemaining < 0) {
  660. return -1;
  661. }
  662. $hours = (int)($timeRemaining / (60*60));
  663. $minutes = (int)($timeRemaining % (60*60) /60);
  664. $hoursStr = _f('{plural|%d час|%d часа|%d часов}', $hours);
  665. $minutesStr = _f('{plural|%d минуту|%d минуты|%d минут}', $minutes);
  666. return $hoursStr . ' ' . $minutesStr;
  667. }
  668. public static function getRemainingDeleteTime($userId)
  669. {
  670. $requestTime = Base_Dao_User::getDeleteRequestTime($userId);
  671. if (!$requestTime) {
  672. return false;
  673. }
  674. $deleteTime = $requestTime + 60*60*24*30;
  675. $timeRemaining = $deleteTime - time();
  676. if ($timeRemaining < 0) {
  677. return _g('еще одного дня');
  678. }
  679. $days = (int)($timeRemaining / (60*60*24));
  680. if ($days == 0) {
  681. $days = 1;
  682. }
  683. return _f('{plural|%d дня|%d дней|%d дней}', $days);
  684. }
  685. public static function getDeleteConfirmHash($user, $oldSalt = false)
  686. {
  687. $salt = self::getSalt($oldSalt ? self::SALT_TYPE_COOKIE_OLD : self::SALT_TYPE_COOKIE);
  688. return md5($user['user_inserted'] . $salt);
  689. }
  690. public static function processDelayedDelete()
  691. {
  692. $db = Base_Context::getInstance()->getDbConnection();
  693. $dbUser = new Db_User();
  694. $query = '
  695. SELECT user_id, reason
  696. FROM
  697. user_delete_request
  698. WHERE
  699. DATE_SUB(NOW(), INTERVAL 30 DAY) > request_time
  700. LIMIT 200';
  701. $usersData = $db->fetchAssoc($query, __METHOD__);
  702. if ($usersData) {
  703. $userIds = Base_Util_Array::extract($usersData, 'user_id');
  704. $usersToDelete = Base_Dao_User::getUsersByIds($userIds);
  705. foreach ($usersToDelete as $userToDelete) {
  706. if ($userToDelete->isHidden() != Db_Moders::DELAYED_DELETED) {
  707. continue;
  708. }
  709. $reason = isset($usersData[$userToDelete->getId()]) ? $usersData[$userToDelete->getId()]['reason'] : '';
  710. $dbUser->deleteUser($userToDelete, $reason);
  711. $userToDelete->getNativeProject()->getStatisticClient()->increment($userToDelete, 'delete_user', 1);
  712. // @analytics stats
  713. $analytics = new Base_Service_Counter_Analytics();
  714. $analytics->increment($userToDelete, 'delete_user', 1);
  715. sleep(1);
  716. }
  717. $db->delete('user_delete_request', $db->qq('user_id IN ('.join(',',$userIds).')'), __METHOD__);
  718. }
  719. // предупреждаем тех, кому осталось 3 дня до удаления
  720. $query = '
  721. SELECT user_id
  722. FROM
  723. user_delete_request
  724. WHERE
  725. DATE_SUB(NOW(), INTERVAL 60*24*27-10 MINUTE) > request_time
  726. AND DATE_SUB(NOW(), INTERVAL 60*24*27 MINUTE) <= request_time';
  727. $usersToNotifyIds = $db->fetchCol($query, __METHOD__);
  728. if ($usersToNotifyIds) {
  729. $usersToNotify = Base_Dao_User::getUsersByIds($usersToNotifyIds);
  730. foreach ($usersToNotify as $userToNotify) {
  731. if ($userToNotify->isHidden() != Db_Moders::DELAYED_DELETED || !$userToNotify->isEmailSet() ) {
  732. continue;
  733. }
  734. /* @var $userToNotify Base_Model_User */
  735. Base_Mailer_Base::addImmediateMail($userToNotify, Base_Mailer_NewTypes::TYPE_DELETE_NOTIFY, __METHOD__, array('subject' => _f('Удаление с {string}', $userToNotify->getNativeProject()->getTitle(2))));
  736. }
  737. }
  738. }
  739. /**
  740. * @param Base_Model_User $user
  741. * @param string $wLetter
  742. * @param string $mLetter
  743. *
  744. * @return string
  745. */
  746. public static function getSexLetter($user, $wLetter = 'а', $mLetter = '')
  747. {
  748. return $user->isFemale() ? $wLetter : $mLetter;
  749. }
  750. /**
  751. * Проверяет, кто из списка айдишников онлайн
  752. *
  753. * @param array $ids массив айдишников пользователй для проверки
  754. * @param bool $keepOffline
  755. *
  756. * @return array|bool массив с ключами $ids и значениями 1|0 (онлайн/офлайн)
  757. */
  758. public static function getAutoOnlineUsers(array $ids, $keepOffline = true)
  759. {
  760. if(!is_array($ids)){
  761. return false;
  762. }
  763. $db = Base_Context::getInstance()->getDbConnection();
  764. $select = $db->select()->from('auto_online','user_id')->where('user_id IN (?)', $ids);
  765. $result = $db->fetchCol($select, __METHOD__);
  766. $online = $keepOffline ? array_fill_keys($ids, 0) : array();
  767. foreach($result as $id){
  768. $online[$id]=1;
  769. }
  770. return $online;
  771. }
  772. /**
  773. * По прошествию 4-х дней переносятся все пользователи кроме:
  774. * 1) забанен и не имеет фин активности
  775. * 2) не имеет подтвержденного е-мела
  776. * в таблицу user. Остальные пользователи удаляются.
  777. */
  778. public static function moveNewUsers()
  779. {
  780. $date4Day = date('Y-m-d H:i:s', time() - 86400 * 4);
  781. /**
  782. * Если мы в окружении для тестирования, необходимо
  783. * скопировать пользователей которые были добавлены через modelFactory
  784. */
  785. if (defined('TESTING') && constant('TESTING') === true) {
  786. // $whereNotActive = "user_email = '".Db_User::DEFAULT_EMAIL."'";
  787. // $whereActive = "user_email != '".Db_User::DEFAULT_EMAIL."'";
  788. $whereActive = "1=1";
  789. } else {
  790. // $whereNotActive = "user_inserted < '$date4Day' AND user_email = '".Db_User::DEFAULT_EMAIL."'";
  791. // $whereActive = "user_inserted < '$date4Day' AND user_email != '".Db_User::DEFAULT_EMAIL."'";
  792. $whereActive = "user_inserted < '$date4Day'";
  793. }
  794. /* ---------------------------------------------------------------------------------------------------------- */
  795. $db = Base_Context::getInstance()->getDbConnection();
  796. /* $countNew = (int) $db->fetchOne("SELECT COUNT(*) FROM `user_new` WHERE $whereActive", __METHOD__);
  797. $countBefore = (int) $db->fetchOne("SELECT COUNT(*) FROM `user`", __METHOD__);
  798. $query = "INSERT INTO `user` (SELECT * FROM `user_new` WHERE $whereActive) ON DUPLICATE KEY UPDATE user.user_email = user_new.user_email, user.user_is_hidden = user_new.user_is_hidden, user.user_not_activated = user_new.user_not_activated";
  799. $db->writequery('user', $query, __METHOD__);
  800. $countAfter = $db->fetchOneMaster("SELECT COUNT(*) FROM `user`", __METHOD__);
  801. if ($countAfter >= $countBefore + $countNew) {
  802. $queryDelete = "DELETE FROM `user_new` WHERE $whereActive";
  803. Driver_Db::writequery('user_new', $queryDelete, __METHOD__);
  804. }*/
  805. // получим активных пользователей, для переноса
  806. $activeIds = $db->fetchCol("SELECT `user_id` FROM `user_new` WHERE $whereActive", __METHOD__);
  807. $activeTotal = count($activeIds);
  808. foreach (array_chunk($activeIds, 5000) as $idsPack) {
  809. $activeIdsImploded = implode(', ', $idsPack);
  810. // перенесем их в `users` табличку
  811. $query = "
  812. INSERT INTO `user` (SELECT * FROM `user_new` WHERE `user_id` IN($activeIdsImploded))
  813. ON DUPLICATE KEY UPDATE
  814. `user`.`user_email` = `user_new`.`user_email`,
  815. `user`.`user_is_hidden` = `user_new`.`user_is_hidden`,
  816. `user`.`user_not_activated` = `user_new`.`user_not_activated`
  817. ";
  818. $db->writequery('user', $query, __METHOD__);
  819. // уберем перенесенных из `user_new`
  820. $db->writequery('user_new', "DELETE FROM `user_new` WHERE `user_id` IN($activeIdsImploded)", __METHOD__);
  821. }
  822. unset($activeIds, $activeIdsImploded);
  823. /* ---------------------------------------------------------------------------------------------------------- */
  824. // полюбому удаляем, тех, кто были незабанены :)
  825. $dbUser = new Db_User();
  826. $dbUser->getTb(1, true); // обновляем кеш по ид на данном сервере.
  827. $usersToDelete = array(); // $db->fetchAll($db->select()->from('user_new')->where($whereNotActive), __METHOD__);
  828. $phonesToBlacklist = array();
  829. // foreach ($usersToDelete as $user) {
  830. // // забанен, не платил, номер телефона активирован
  831. //// if($user['user_is_hidden'] >= 1 && $user['user_cash'] === null && $user['user_phone_approved'] == 1 && $user['user_phone']){
  832. //// $phonesToBlacklist[] = $user['user_phone'];
  833. //// }
  834. //
  835. // /**
  836. // * Слишком долго выполняется в тестах
  837. // */
  838. // if (!defined('TESTING')) {
  839. // $dbUser->deleteUser($user, "not-active-4day");
  840. // }
  841. // }
  842. //
  843. // // добавляем телефоны в blacklist. Отключено по просьбе Германова Евгения.
  844. //// $blacklistDao = Antifraud_Dao_Blacklist::getInstance();
  845. //// foreach($phonesToBlacklist as $phone){
  846. //// $blacklistDao->addToBlackList(Antifraud_Dao_Blacklist::TYPE_PHONE, $phone, 'not-active-4day-banned');
  847. //// }
  848. //
  849. // $dbUser->getTb(1, true);
  850. return array(
  851. 'transferred' => $activeTotal, //$countNew,
  852. 'deleted' => count($usersToDelete),
  853. 'phonesToBlacklist' => count($phonesToBlacklist)
  854. );
  855. }
  856. /**
  857. * @depracated - оставлю для истории. Сейчас этот крон работает в демоне. Код портировали с небольшими измениями.
  858. *
  859. * Обновления онлайн. Запускается раз в 1 минуту.
  860. *
  861. * Как эта хренота работает:
  862. * - забираем юзеров из SQ, туда записываются из разных мест массивчики вида
  863. array(
  864. 'user_id' => id юзера, который произвёл действие,
  865. 'time' => время, когда это действие было совершено,
  866. 'r_id' => реферрер - откуда пришёл юзер,
  867. 'r_data' => что-то (не вкурсе, присутствовало в старом кроне),
  868. 'src_id' => сурс - с какого типа ФС юзер вышел онлайн (FS, FSm, Mobile version, iPhone app, ...)
  869. );
  870. * - группируем эти действия по user_id и src_id
  871. * - берём время последнего действия для каждого типа
  872. * - достаём все записи из auto_online
  873. * - путём нехитрых манипуляций вычисляем по time_update юзеров, которые проэкспайрились
  874. * - исходя из данных из SQ определяем юзеров которые только что стали онлайн (тоесть отсутствуют в auto_online)
  875. * и тех, кто уже был онлайн на момент запуска крона (присутствуют в auto_online)
  876. * - и затем апдейтим группы этих юзеров
  877. * ... В общем коллективный разум нам подсказал сложную и интересную реализацию онлайнов.
  878. * В табличках user и user_new у нас есть (появилось) поле online_mask, которое содержит в себе побитовую маску
  879. * сурсов, в которых юзер онлайн/оффлайн. Битики лежат в константах этого класса (с префиксом SOURCE_*).
  880. * Масочка может принимать значение 0 - когда юзер вообще оффлайн или иметь установленные битики онлайн/эвэй для каждого их сурсов.
  881. * Под каждый сурс занято 2 бита - бит online и бит away (это когда юзер ещё где-то онлайн, но по данному сурсу уже оффлайн),
  882. * выглядит это как-то так (на момент запуска - 16 бит, определяется константой ONLINE_BIT_SHIFT):
  883. * |0|0|0|0|0|0|0|0|
  884. * | | | | FS: away, online
  885. * | | | FSm: away, online
  886. * | | Mobile: away, online
  887. * | iPhone: away, online
  888. * Если юзер не имеет ни одного установленного бита online - ему вставляется 0.
  889. * Апдейтится эта штука так: создаётся маска, имеющая 32 бит:
  890. * 16 младших бит - это те биты, которые мы хотим сбросить
  891. * 16 старших бит - это те биты, которые мы хотим поставить
  892. * Весь апдейт происходит сбрасыванием полной маски, и установкой со сдвигом маски на 16 бит вправо
  893. * Собственно вот :)
  894. * - и соответственно апдейтим мемкэш для этих юзеров
  895. * - удаляем юзеров из auto_online
  896. * Тут ещё есть немного кода, который был перенесён из старого крона, и коммент соответсвенно для него:
  897. * в) статистика логинов для Gameleads
  898. *
  899. */
  900. public static function updateAutoOnline($qnum)
  901. {
  902. $timeNow = time();
  903. $currDayTS = strtotime(date('Y-m-d 00:00:00'));
  904. $prevDayTS = strtotime(date('Y-m-d 00:00:00', strtotime('-1 day')));
  905. // забираем все из очереди
  906. if (PRODUCTION) {
  907. $queueNum = ($qnum == 1) ? Base_Service_SharedQueue::INDEX_ONLINE : Base_Service_SharedQueue::INDEX_ONLINE_EVEN;
  908. $sqData = Base_Service_SharedQueue::popAll($queueNum);
  909. } else {
  910. if ($qnum != 1) {
  911. return true;
  912. }
  913. $sqData = array_merge(
  914. Base_Service_SharedQueue::popAll(Base_Service_SharedQueue::INDEX_ONLINE),
  915. Base_Service_SharedQueue::popAll(Base_Service_SharedQueue::INDEX_ONLINE_EVEN)
  916. );
  917. }
  918. // берём время последнего действия с группировкой по юзеру и сурсу
  919. $byUserSrc = array();
  920. $usersLocation = array();
  921. /*$loggedOutUsers = array();*/
  922. foreach ($sqData as $k => $item) {
  923. if (!empty($item['from_logout'])) {
  924. /*$loggedOutUsers[$item['user_id']] = 1;*/
  925. continue;
  926. }
  927. // временная затычка, на время перехода со старого крона на новый
  928. if (!isset($item['src_id'])) {
  929. $item['src_id'] = self::SOURCE_FS_ONLINE;
  930. }
  931. /* Example
  932. $item = array(
  933. 'user_id' => $userId,
  934. 'time' => $timeNow,
  935. 'r_id' => $referrerId,
  936. 'r_data' => $returnData,
  937. 'src_id' => $sourceId
  938. );
  939. */
  940. // отсеиваем события старше таймаута для данного сурса (ибо нахх не нужны)
  941. if (($timeNow - $item['time']) > Base_Service_User::$onlineAwayTimeout[$item['src_id']]) {
  942. continue;
  943. }
  944. //Сохраним ip и время для обновления счетчиков логинов
  945. if (isset($item['long_ip'])) {
  946. $usersLocation[$item['user_id']] = $item['long_ip'];
  947. }
  948. if (!isset($byUserSrc[$item['user_id']])) {
  949. $byUserSrc[$item['user_id']] = array();
  950. } elseif (!isset($byUserSrc[$item['user_id']][$item['src_id']])) {
  951. $byUserSrc[$item['user_id']][$item['src_id']] = array();
  952. }
  953. // сделаем ссылочку, для более удобной работы с элементом массива
  954. $link = &$byUserSrc[$item['user_id']][$item['src_id']];
  955. // обновляем время последнего действия
  956. if (!isset($link['time']) || $item['time'] > $link['time']) {
  957. $link['time'] = $item['time'];
  958. }
  959. // Записываем только первый refererId для юзера
  960. if (!empty($item['r_id']) && !isset($link['r_id'])) {
  961. $link['r_id'] = $item['r_id'];
  962. }
  963. if (!empty($item['r_data']) && empty($link['r_data'])) {
  964. $link['r_data'] = $item['r_data'];
  965. }
  966. unset($link);
  967. }
  968. unset($sqData);
  969. $_new = $_up = $_off = array();
  970. // выбираем пользователей которые были онлайн
  971. $autoOnlineByUser = array();
  972. $db = Base_Context::getInstance()->getDbConnection();
  973. $autoOnlineData = $db->selectAll('auto_online', "SELECT user_id, source_id, time_update, time_in FROM auto_online", __METHOD__);
  974. foreach ($autoOnlineData as $k => $row) {
  975. // фильтруем чётные / нечётные
  976. if (PRODUCTION && (($qnum == 1 && $row['user_id'] % 2 == 0) || ($qnum == 2 && $row['user_id'] % 2 != 0))) {
  977. continue;
  978. }
  979. $timeOut = Base_Service_User::$onlineAwayTimeout[$row['source_id']];
  980. if (!isset($autoOnlineByUser[$row['user_id']])) {
  981. $autoOnlineByUser[$row['user_id']] = array();
  982. }
  983. $autoOnlineByUser[$row['user_id']][$row['source_id']] = array(
  984. 'time_update' => $row['time_update'],
  985. 'time_in' => $row['time_in']
  986. );
  987. /* Вынес условия в переменные, иначе код нечитаемый */
  988. // если юзер проэкспайрился по табличке
  989. $dbTimeout = ($timeNow - $row['time_update']) > $timeOut;
  990. // отсутствует в SQ или имеет в SQ проэкспайреное время
  991. $sqTimeout = (!isset($byUserSrc[$row['user_id']][$row['source_id']]) || ($timeNow - $byUserSrc[$row['user_id']][$row['source_id']]['time']) > self::ONLINE_TIMEOUT_UPDATE);
  992. // если юзер олайн больше чем максимальное время онлайна
  993. $maxTimeout = ($timeNow - $row['time_in']) > self::ONLINE_MAX_ONLINE_TIME;
  994. if (($dbTimeout && $sqTimeout) || $maxTimeout/* || isset($loggedOutUsers[$row['user_id']])*/) {
  995. if (!isset($_off[$row['user_id']])) {
  996. $_off[$row['user_id']] = array();
  997. }
  998. /*$_off[$row['user_id']][$row['source_id']] = array();*/
  999. $_off[$row['user_id']][$row['source_id']] = (int) $row['user_id'];
  1000. // удалим из массива, дабы не занести эту пару (юзер-тип) в другую группу (up, new)
  1001. unset($byUserSrc[$row['user_id']][$row['source_id']]);
  1002. if (empty($byUserSrc[$row['user_id']])) {
  1003. unset($byUserSrc[$row['user_id']]);
  1004. }
  1005. }
  1006. }
  1007. unset($autoOnlineData);
  1008. // Ид источника, с которого пришел юзер
  1009. $refererIds = $returnData = array();
  1010. // выбираем юзеров, которые сейчас онлайн
  1011. foreach ($byUserSrc as $userId => $sources) {
  1012. foreach ($sources as $srcId => $data) {
  1013. // юзеры, которые есть в табличке - up'ы
  1014. if (isset($autoOnlineByUser[$userId][$srcId])) {
  1015. // если обновление было больше чем 3 минуты назад
  1016. /* Не трогать этот IF, блеать! */
  1017. if (($timeNow - $autoOnlineByUser[$userId][$srcId]['time_update']) < self::ONLINE_TIMEOUT_UPDATE) {
  1018. continue;
  1019. }
  1020. if (!isset($_up[$userId])) {
  1021. $_up[$userId] = array();
  1022. }
  1023. /*$_up[$userId][$srcId] = array();*/
  1024. $_up[$userId][$srcId] = $userId;
  1025. // юзеры, которых нет в табличке - new
  1026. } else {
  1027. if (!isset($_new[$userId])) {
  1028. $_new[$userId] = array();
  1029. }
  1030. /*$_new[$userId][$srcId] = array();*/
  1031. $_new[$userId][$srcId] = $userId;
  1032. }
  1033. // Взято со старого крона, для совместимости
  1034. if ($srcId == self::SOURCE_FS_ONLINE) {
  1035. // Записываем только первый refererId для юзера
  1036. if (isset($data['r_id'])) {
  1037. $refererIds[$userId] = $data['r_id'];
  1038. }
  1039. if (isset($data['r_data'])) {
  1040. $returnData[$userId] = $data['r_data'];
  1041. }
  1042. }
  1043. }
  1044. }
  1045. unset($byUserSrc);
  1046. // Переназначаем переменные для совместимости с пересчётом старых статистик и прочего и прочего и прочего...
  1047. // при этом учитываем только онлайны с сайта (SOURCE_FS_ONLINE)
  1048. $new = self::extractOnlinersBySrc($_new, self::SOURCE_FS_ONLINE);
  1049. $off = self::extractOnlinersBySrc($_off, self::SOURCE_FS_ONLINE);
  1050. $up = self::extractOnlinersBySrc($_up, self::SOURCE_FS_ONLINE);
  1051. $newMobileUsers = self::extractOnlinersBySrc($_new, self::SOURCE_MOBILE_ONLINE);
  1052. $offMobileUsers = self::extractOnlinersBySrc($_off, self::SOURCE_MOBILE_ONLINE);
  1053. //$upMobileUsers = self::extractOnlinersBySrc($_up, self::SOURCE_MOBILE_ONLINE);
  1054. // достаём модельки юзеров для каждого типа
  1055. $newUsers = empty($new) ? array() : Base_Dao_User::getUsersByIds($new);
  1056. $offUsers = empty($off) ? array() : Base_Dao_User::getUsersByIds($off);
  1057. $upUsers = empty($up) ? array() : Base_Dao_User::getUsersByIds($up);
  1058. //$upMoibleUsers = empty($upMobileUsers) ? array() : Base_Dao_User::getUsersByIds($upMobileUsers);
  1059. // Create memory table
  1060. $oldOnline = false;
  1061. $createTmpTable = !empty($_off) || !empty($_new);
  1062. if ($createTmpTable) {
  1063. self::createTmpTableForMasks();
  1064. }
  1065. $mcMaskOffUpdate = array();
  1066. // обработка полученных массивов
  1067. if (!empty($_off)) {
  1068. self::makeUsersOffline($_off, $mcMaskOffUpdate/*passed by link*/);
  1069. }
  1070. if (!empty($_up)) {
  1071. self::updateOnlineUsers($_up);
  1072. }
  1073. if (!empty($_new)) {
  1074. $oldOnline = self::makeUsersOnline($_new, $mcMaskOffUpdate/*passed by link*/);
  1075. }
  1076. if (!empty($mcMaskOffUpdate)) {
  1077. // Апдейтим мемкэш для офлайнов
  1078. Db_User::updateMemcacheOnlineMask($mcMaskOffUpdate/*, array('time_out' => $timeNow)*/);
  1079. $toTmpInsert = array();
  1080. foreach ($mcMaskOffUpdate as $userId => $bitMask) {
  1081. $toTmpInsert[] = '(' . (int) $userId . ', ' . $bitMask . ')';
  1082. }
  1083. if (!empty($toTmpInsert)) {
  1084. $db->writequery('tmp_online_masks', 'INSERT INTO `tmp_online_masks` (`user_id`, `new_mask`) VALUES ' . implode(', ', $toTmpInsert), __METHOD__);
  1085. }
  1086. }
  1087. // if we've some data in tmp table
  1088. if ($createTmpTable) {
  1089. $fullOnlineMask = self::getAllOnlineMask();
  1090. $bitmaskUpdate = '(`u`.`online_mask` & ~`tom`.`new_mask` | `tom`.`new_mask` >> ' . self::ONLINE_BIT_SHIFT . ')';
  1091. // Апдейтим user
  1092. $query = 'UPDATE `user` AS `u`, `tmp_online_masks` AS `tom` SET
  1093. `u`.`online_mask` = IF(' . $bitmaskUpdate . ' & ' . $fullOnlineMask . ' > 0,
  1094. ' . $bitmaskUpdate . ',
  1095. 0
  1096. ),
  1097. `u`.`time_in` = IF(`u`.`time_out` > 0,
  1098. ' . $timeNow . ',
  1099. `u`.`time_in`
  1100. ),
  1101. `u`.`time_out` = IF(' . $bitmaskUpdate . ' & ' . $fullOnlineMask . ' > 0,
  1102. 0,
  1103. ' . $timeNow . '
  1104. )
  1105. WHERE `u`.`user_id` = `tom`.`user_id`';
  1106. $db->writequery('user', $query, __METHOD__);
  1107. // Апдейтим user_new
  1108. $query = 'UPDATE `user_new` AS `u`, `tmp_online_masks` AS `tom` SET
  1109. `u`.`online_mask` = IF(' . $bitmaskUpdate . ' & ' . $fullOnlineMask . ' > 0,
  1110. ' . $bitmaskUpdate . ',
  1111. 0
  1112. ),
  1113. `u`.`time_in` = IF(`u`.`time_out` > 0,
  1114. ' . $timeNow . ',
  1115. `u`.`time_in`
  1116. ),
  1117. `u`.`time_out` = IF(' . $bitmaskUpdate . ' & ' . $fullOnlineMask . ' > 0,
  1118. 0,
  1119. ' . $timeNow . '
  1120. )
  1121. WHERE `u`.`user_id` = `tom`.`user_id`';
  1122. $db->writequery('user_new', $query, __METHOD__);
  1123. }
  1124. $online = self::extractOnlinersBySrc($autoOnlineByUser, self::SOURCE_FS_ONLINE, true);
  1125. foreach ($online as &$data) {
  1126. $data = array_values($data);
  1127. }
  1128. unset($data, $autoOnlineByUser);
  1129. /* Ниже идёт код, перенесённый из старого крона */
  1130. self::fillOnlineHistory($online, $up, 300, self::ONLINE_TIMEOUT_UPDATE);
  1131. // обновляем онлайн во всех базах
  1132. self::OnlineReplica(array_keys($_new), array_keys($_off), 86400, array('database_heap','fotodb3','database'));
  1133. // Чистимся слегка
  1134. unset($_new, $_off, $_up);
  1135. //для добавлений акции пользователям
  1136. if ($new && Invites_Service_Vip::isAvailable()) {
  1137. Invites_Service_Vip::pushOnlineUsersForCron($newUsers);
  1138. }
  1139. //вайпаем лишние локейшены
  1140. $_tmpLocations = array();
  1141. foreach($new as $userId){
  1142. if(isset($usersLocation[$userId])) {
  1143. $_tmpLocations[$userId] = $usersLocation[$userId];
  1144. }
  1145. }
  1146. $usersLocation = $_tmpLocations;
  1147. unset($_tmpLocations);
  1148. // обработчики онлайн от пользователя
  1149. self::onOnline($new, $oldOnline, $returnData, $usersLocation);
  1150. self::onOffline($off, $offUsers, $online);
  1151. $fsmessengerPush = new Fsmessenger_Service_DaemonPush;
  1152. $fsmessengerPush->pushOnlineChanges($new, $off);
  1153. $fsmessengerPush->pushOnlineChanges($newMobileUsers, $offMobileUsers, 'mob_');
  1154. Partner_Service_Gameleads::traceAuth($newUsers);
  1155. $analytics = Base_Service_Counter_Analytics::getInstance();
  1156. $involvement = Base_Service_Counter_Involvement::getInstance();
  1157. // new users process
  1158. foreach ($newUsers as $value) { /** @var $value Base_Model_User */
  1159. $userLastVisit = $value->getLastVisit();
  1160. $refererId = isset($refererIds[$value->getId()]) ? $refererIds[$value->getId()] : null;
  1161. Base_Service_Counter_Stats::incrementLogin($value['time_out'], $value, $refererId);
  1162. // Считаем тех, кто был вчера и впервые зашёл сегодня (то есть сколько сегодня зашло тех, кто был на сайте вчера)
  1163. //if ($value->isPetAppUser() && strtotime(date('Y-m-d 00:00:00', strtotime('-1 day'))) <= $value->getLastVisit() && $value->getLastVisit() < strtotime(date('Y-m-d 00:00:00'))) {
  1164. if ($value->isPetAppUser() && $prevDayTS <= $userLastVisit && $userLastVisit < $currDayTS) {
  1165. $stats = $value->getNativeProject()->getStatisticClient();
  1166. $stats->increment($value, 'login_return');
  1167. }
  1168. // @analytics stats
  1169. $analytics->increment($value, 'sessions_count', 1);
  1170. Base_Service_Counter_Analytics::incrementAnalytics($value, 'new_sessions_count');
  1171. if ($value->hasPet()){
  1172. $analytics->increment($value, 'pet_session_count', 1);
  1173. }
  1174. if ($value->getCash() > 0 && $userLastVisit < $currDayTS) {
  1175. $analytics->increment($value, 'cash_rest', $value->getCash() * 100);
  1176. //инкремент статсы биллинга остатки на счетах
  1177. $billingStats = Base_Service_Counter_Billing::getInstance(); /** @var Base_Service_Counter_Billing $billingStats */
  1178. $billingStats->incrementCashRest($value, $billingStats::SERVICE_ID_ALL, $value->getCash() * 100, null, Base_Service_Counter_Billing::BILLING_ID_ALL);
  1179. }
  1180. $activityDay = Base_Service_Counter_Analytics::isNewbie($value);
  1181. if ($activityDay) { /** @var $value Base_Model_User */
  1182. // был вчера и вернулся сегодня
  1183. if ($userLastVisit >= $prevDayTS && $userLastVisit < $currDayTS) {
  1184. Base_Service_Counter_Analytics::incrementAnalytics($value, 'new_retention_1day');
  1185. }
  1186. if ($activityDay > 1 && $userLastVisit < $currDayTS) {
  1187. Base_Service_Counter_Analytics::incrementAnalytics($value, 'new_return_' . $activityDay . 'day');
  1188. }
  1189. }
  1190. }
  1191. // up users process
  1192. foreach ($upUsers as $value) {
  1193. if (!isset($online[$value->getId()][0])) {
  1194. continue;
  1195. }
  1196. // temporary log
  1197. if ($value->getCash() < 0) {
  1198. Base_Service_Log::log('wrong_user_balance', array('old', $value->getId(), $value->getCash()));
  1199. }
  1200. $refererId = isset($refererIds[$value->getId()]) ? $refererIds[$value->getId()] : null;
  1201. Base_Service_Counter_Stats::incrementLogin($online[$value['user_id']][0], $value, $refererId);
  1202. }
  1203. // up mobile users process
  1204. //$hourNow = strtotime(date('Y-m-d H:00:00'));
  1205. //foreach ($upMoibleUsers as $value) {
  1206. // if (isset($online[$value['user_id']][0])
  1207. // && ($online[$value['user_id']][0] < $hourNow)
  1208. // ) {
  1209. // Base_Service_Counter_Main::getInstance()->increment($value, 'traffic_m_login_hour');
  1210. // }
  1211. //}
  1212. // off users process
  1213. foreach ($offUsers as $oUser) { /** @var $oUser Base_Model_User */
  1214. if (empty($online[$oUser->getId()])) {
  1215. continue;
  1216. }
  1217. // общее время сессии
  1218. $minutes = round(($online[$oUser->getId()][0] - $online[$oUser->getId()][1]) / 60);
  1219. if ($minutes) {
  1220. // тест - посчитаем разницу времении сессии, если ботов не отсеивать
  1221. $analytics->increment($oUser, 'sessions_time_botfriendly', $minutes);
  1222. }
  1223. if ($minutes && $minutes < 3 * 3600) {
  1224. $analytics->increment($oUser, 'sessions_time', $minutes);
  1225. Base_Service_Counter_Analytics::incrementAnalytics($oUser, 'new_sessions_time', $minutes);
  1226. // определять, была ли эта сессия первой для человека
  1227. if (!$oUser->get('time_out')) {
  1228. $analytics->increment($oUser, 'first_session_time', $minutes);
  1229. }
  1230. if (Base_Service_User::isFirstSession($oUser)) {
  1231. $analytics->increment($oUser, 'socialnetwork_1session_time', $minutes);
  1232. }
  1233. if ($oUser->hasPet()){ // статистика сессий петов
  1234. $analytics->increment($oUser, 'pet_session_time', $minutes);
  1235. }
  1236. $involvement->incrementStats($oUser, Base_Service_Counter_Involvement::FIELD_SESSION_TIME, $minutes);
  1237. // Статистикапо трафику партнёров
  1238. Base_Service_Counter_Stats::incrementPartnerTrafficStats($oUser, 'sess_time', $minutes);
  1239. }
  1240. // считаем первую сессию для приглешенных пользователей
  1241. if (Base_Service_User::isFirstSession($oUser)) {
  1242. Invites_Service_Stats::countUserFirstSession($oUser, $minutes);
  1243. }
  1244. // заполняем очередь для проверки возвращаемости
  1245. $dateInsertedTS = strtotime($oUser->getDateInserted());
  1246. if ($oUser->isEmailApproved() && $timeNow-86400*7 <= $dateInsertedTS && $timeNow-86400*1 >= $dateInsertedTS) {
  1247. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_USERRETURN_CHECK, $oUser->getId());
  1248. }
  1249. }
  1250. Usercontact_Dao_Talk2::saveOnlineOffline($new, $off);
  1251. return 'online=' . count($new) . '; refresh=' . count($up) . '; offline=' . count($off);
  1252. }
  1253. /**
  1254. * Обновление статистики по крону онлайн. Выполняется раз в минуту.
  1255. * Вся логика вынесена в демон, подсчёт статсы работает так же как в updateAutoOnline
  1256. * @static
  1257. * @return string
  1258. */
  1259. public static function updateAutoOnlineDaemon()
  1260. {
  1261. $timeNow = time();
  1262. $currDayTS = strtotime(date('Y-m-d 00:00:00'));
  1263. $prevDayTS = strtotime(date('Y-m-d 00:00:00', strtotime('-1 day')));
  1264. if (PRODUCTION) {
  1265. $sqData = Base_Service_SharedQueue::popAll(Base_Service_SharedQueue::INDEX_CRON_ONLINE_STATISTIC);
  1266. } else {
  1267. $sqData = array ( 0 => array ( 2048 => array ( 1 => array ( 'prev_time_out' => 1348073242, 'r_data' => '', 'r_id' => 0, 'status' => 1, 'time_in' => 0, 'time_update' => 0, ), ), ), 1 => array ( 60713389 => array ( 1 => array ( 'prev_time_out' => 0, 'r_data' => '', 'r_id' => 0, 'status' => 2, 'time_in' => 1348048112, 'time_update' => 1348076160, ), ), ), );
  1268. }
  1269. if (empty($sqData)) {
  1270. return 'No status changes from daemon received';
  1271. }
  1272. $analytics = Base_Service_Counter_Analytics::getInstance();
  1273. $involvement = Base_Service_Counter_Involvement::getInstance();
  1274. $counters = array(
  1275. self::ONLINE_STATUS_ONLINE => 0,
  1276. self::ONLINE_STATUS_UPDATE => 0,
  1277. self::ONLINE_STATUS_OFFLINE => 0
  1278. );
  1279. foreach ($sqData as $k => $item) {
  1280. if (empty($item)) {
  1281. continue;
  1282. }
  1283. /*
  1284. 'userId1' => array(
  1285. ONLINE_SOURCE_FS => array(
  1286. 'status' => ONLINE_STATUS_ONLINE,
  1287. 'r_id' => '...',
  1288. 'r_data' => '...',
  1289. 'time_in' => 0,
  1290. 'time_update' => 0,
  1291. 'prev_time_out' => 0,
  1292. ),
  1293. ...
  1294. ),
  1295. */
  1296. $userModels = Base_Dao_User::getUsersByIds(array_keys($item));
  1297. foreach ($item as $userId => $onlineInfo) {
  1298. if (!isset($userModels[$userId])) {
  1299. continue;
  1300. }
  1301. /* @var $userModel Base_Model_User */
  1302. $userModel = $userModels[$userId];
  1303. foreach ($onlineInfo as $source => $sourceInfo) {
  1304. // Предыдущее время выхода, у нас получается из демона
  1305. $userModel->set('time_out', $sourceInfo['prev_time_out']);
  1306. ++$counters[$sourceInfo['status']];
  1307. switch ($source) {
  1308. case self::ONLINE_SOURCE_FS:
  1309. switch ($sourceInfo['status']) {
  1310. // increment online statuses
  1311. case self::ONLINE_STATUS_ONLINE:
  1312. $userLastVisit = $userModel->getLastVisit();
  1313. $refererId = empty($sourceInfo['r_id']) ? null : $sourceInfo['r_id'];
  1314. Base_Service_Counter_Stats::incrementLogin($userModel->get('time_out'), $userModel, $refererId);
  1315. // Считаем тех, кто был вчера и впервые зашёл сегодня (то есть сколько сегодня зашло тех, кто был на сайте вчера)
  1316. //if ($value->isPetAppUser() && strtotime(date('Y-m-d 00:00:00', strtotime('-1 day'))) <= $value->getLastVisit() && $value->getLastVisit() < strtotime(date('Y-m-d 00:00:00'))) {
  1317. if ($userModel->isPetAppUser() && $prevDayTS <= $userLastVisit && $userLastVisit < $currDayTS) {
  1318. $stats = $userModel->getNativeProject()->getStatisticClient();
  1319. $stats->increment($userModel, 'login_return');
  1320. }
  1321. // Возвраты виральных пользователей в первую неделю (но не в день регистрации)
  1322. if(Base_Service_UserSource::isMailInvitedUser($userModel)
  1323. && $userModel->getDaysRegistered() <= 7
  1324. && date('Y-m-d', strtotime($userModel->getDateInserted())) != date('Y-m-d')) {
  1325. $client = Base_Service_Counter_Invites::getInstance();
  1326. $client->increment($userModel, 'first_week_return');
  1327. }
  1328. // @analytics stats
  1329. $analytics->increment($userModel, 'sessions_count', 1);
  1330. Base_Service_Counter_Analytics::incrementAnalytics($userModel, 'new_sessions_count');
  1331. if ($userModel->hasPet()){
  1332. $analytics->increment($userModel, 'pet_session_count', 1);
  1333. }
  1334. if ($userModel->getCash() > 0 && $userLastVisit < $currDayTS) {
  1335. $analytics->increment($userModel, 'cash_rest', $userModel->getCash() * 100);
  1336. //инкремент статсы биллинга остатки на счетах
  1337. $billingStats = Base_Service_Counter_Billing::getInstance(); /** @var Base_Service_Counter_Billing $billingStats */
  1338. $billingStats->incrementCashRest($userModel, $billingStats::SERVICE_ID_ALL, $userModel->getCash() * 100, null, Base_Service_Counter_Billing::BILLING_ID_ALL);
  1339. }
  1340. $activityDay = Base_Service_Counter_Analytics::isNewbie($userModel);
  1341. if ($activityDay) {
  1342. // был вчера и вернулся сегодня
  1343. if ($userLastVisit >= $prevDayTS && $userLastVisit < $currDayTS) {
  1344. Base_Service_Counter_Analytics::incrementAnalytics($userModel, 'new_retention_1day');
  1345. }
  1346. if ($activityDay > 1 && $userLastVisit < $currDayTS) {
  1347. Base_Service_Counter_Analytics::incrementAnalytics($userModel, 'new_return_' . $activityDay . 'day');
  1348. }
  1349. }
  1350. break;
  1351. // increment status updates
  1352. case self::ONLINE_STATUS_UPDATE:
  1353. if (empty($sourceInfo['time_update'])) {
  1354. continue;
  1355. }
  1356. $refererId = empty($sourceInfo['r_id']) ? null : $sourceInfo['r_id'];
  1357. Base_Service_Counter_Stats::incrementLogin($sourceInfo['time_update'], $userModel, $refererId);
  1358. break;
  1359. // increment offline statuses
  1360. case self::ONLINE_STATUS_OFFLINE:
  1361. // общее время сессии
  1362. $minutes = round(($sourceInfo['time_update'] - $sourceInfo['time_in']) / 60);
  1363. if ($minutes) {
  1364. // тест - посчитаем разницу времении сессии, если ботов не отсеивать
  1365. $analytics->increment($userModel, 'sessions_time_botfriendly', $minutes);
  1366. }
  1367. if ($minutes && $minutes < 3 * 3600) {
  1368. $analytics->increment($userModel, 'sessions_time', $minutes);
  1369. Base_Service_Counter_Analytics::incrementAnalytics($userModel, 'new_sessions_time', $minutes);
  1370. // определять, была ли эта сессия первой для человека
  1371. if (!$userModel->get('time_out')) {
  1372. $analytics->increment($userModel, 'first_session_time', $minutes);
  1373. }
  1374. if (Base_Service_User::isFirstSession($userModel)) {
  1375. $analytics->increment($userModel, 'socialnetwork_1session_time', $minutes);
  1376. }
  1377. if ($userModel->hasPet()){ // статистика сессий петов
  1378. $analytics->increment($userModel, 'pet_session_time', $minutes);
  1379. }
  1380. $involvement->incrementStats($userModel, Base_Service_Counter_Involvement::FIELD_SESSION_TIME, $minutes);
  1381. // Статистикапо трафику партнёров
  1382. Base_Service_Counter_Stats::incrementPartnerTrafficStats($userModel, 'sess_time', $minutes);
  1383. }
  1384. // заполняем очередь для проверки возвращаемости
  1385. $dateInsertedTS = strtotime($userModel->getDateInserted());
  1386. if ($userModel->isEmailApproved() && $timeNow-86400*7 <= $dateInsertedTS && $timeNow-86400*1 >= $dateInsertedTS) {
  1387. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_USERRETURN_CHECK, $userModel->getId());
  1388. }
  1389. break;
  1390. default:
  1391. trigger_error('Unknown online status: ' . $sourceInfo['status'], E_WARNING);
  1392. }
  1393. break;
  1394. case self::ONLINE_SOURCE_IPHONE:
  1395. case self::ONLINE_SOURCE_MOBILE:
  1396. case self::ONLINE_SOURCE_FSM:
  1397. // skip other sources
  1398. continue;
  1399. break;
  1400. default:
  1401. trigger_error('Unknown online source: ' . $source, E_WARNING);
  1402. }
  1403. }
  1404. }
  1405. }
  1406. return 'online=' . $counters[self::ONLINE_STATUS_ONLINE]
  1407. . '; refresh=' . $counters[self::ONLINE_STATUS_UPDATE]
  1408. . '; offline=' . $counters[self::ONLINE_STATUS_OFFLINE];
  1409. }
  1410. private static function extractOnlinersBySrc($onliners, $src, $associative = false)
  1411. {
  1412. $return = array();
  1413. foreach ($onliners as $userId => $sources) {
  1414. foreach ($sources as $srcId => $data) {
  1415. if ($srcId == $src) {
  1416. if ($associative) {
  1417. $return[$userId] = $data;
  1418. } else {
  1419. $return[] = $data;
  1420. }
  1421. }
  1422. }
  1423. }
  1424. return $return;
  1425. }
  1426. /**
  1427. * Returns away flag associated with online flag
  1428. * @static
  1429. * @param $onlineFlag
  1430. * @return int
  1431. */
  1432. private static function getOppositeOnlineBit($bit)
  1433. {
  1434. switch ($bit) {
  1435. // FS
  1436. case self::SOURCE_FS_ONLINE: return self::SOURCE_FS_AWAY;
  1437. case self::SOURCE_FS_AWAY: return self::SOURCE_FS_ONLINE;
  1438. // FSm
  1439. case self::SOURCE_FSM_ONLINE: return self::SOURCE_FSM_AWAY;
  1440. case self::SOURCE_FSM_AWAY: return self::SOURCE_FSM_ONLINE;
  1441. // Mobile
  1442. case self::SOURCE_MOBILE_ONLINE: return self::SOURCE_MOBILE_AWAY;
  1443. case self::SOURCE_MOBILE_AWAY: return self::SOURCE_MOBILE_ONLINE;
  1444. // iPhone
  1445. case self::SOURCE_IPHONE_ONLINE: return self::SOURCE_IPHONE_AWAY;
  1446. case self::SOURCE_IPHONE_AWAY: return self::SOURCE_IPHONE_ONLINE;
  1447. // --
  1448. default: return 0;
  1449. }
  1450. }
  1451. private static function getFullOnlineMask($set)
  1452. {
  1453. return $set << self::ONLINE_BIT_SHIFT | $set | self::getOppositeOnlineBit($set);
  1454. }
  1455. public static function updateFullOnlineMask($mask, $resetMask)
  1456. {
  1457. // $resetMask = self::getFullOnlineMask($set);
  1458. // Эта же штука выполняется в запросе
  1459. return $mask & ~$resetMask | $resetMask >> self::ONLINE_BIT_SHIFT;
  1460. }
  1461. /**
  1462. * New offline method
  1463. * @static
  1464. * @param $users
  1465. * @return mixed
  1466. */
  1467. private static function makeUsersOffline($off, &$memcacheBitMask)
  1468. {
  1469. $db = Base_Context::getInstance()->getDbConnection();
  1470. $toDelete = /*$toTmpInsert = */array();
  1471. foreach ($off as $userId => $sources) {
  1472. $bitMask = 0;
  1473. foreach ($sources as $srcId => $data) {
  1474. $toDelete[] = '(`user_id` = ' . (int) $userId . ' AND `source_id` = ' . (int) $srcId . ')';
  1475. $bitMask ^= self::getFullOnlineMask(self::getOppositeOnlineBit($srcId)); // Prepare reset mask
  1476. }
  1477. // $toTmpInsert[] = '(' . (int) $userId . ', ' . $bitMask . ')';
  1478. if (!isset($memcacheBitMask[$userId])) {
  1479. $memcacheBitMask[$userId] = 0;
  1480. }
  1481. $memcacheBitMask[$userId] ^= $bitMask;
  1482. unset($off[$userId]);
  1483. }
  1484. if (!empty($toDelete)) {
  1485. // удаляем из auto_online
  1486. $sql = 'DELETE FROM auto_online WHERE ' . implode(' OR ', $toDelete);
  1487. $db->writequery('auto_online', $sql, __METHOD__);
  1488. }
  1489. // if (!empty($toTmpInsert)) {
  1490. // $db->writequery('tmp_online_masks', 'INSERT INTO `tmp_online_masks` (`user_id`, `new_mask`) VALUES ' . implode(', ', $toTmpInsert), __METHOD__);
  1491. // }
  1492. return $memcacheBitMask;
  1493. }
  1494. private static function updateOnlineUsers($up)
  1495. {
  1496. $db = Base_Context::getInstance()->getDbConnection();
  1497. $toUpdate = array();
  1498. foreach ($up as $userId => $sources) {
  1499. foreach ($sources as $srcId => $data) {
  1500. $toUpdate[] = '(' . (int) $userId . ', ' . (int) $srcId . ', ' . TIME . ')';
  1501. }
  1502. unset($up[$userId]);
  1503. }
  1504. // Апдейтим auto_online
  1505. $query = 'INSERT INTO `auto_online` (`user_id`, `source_id`, `time_update`)
  1506. VALUES ' . implode(', ', $toUpdate) . '
  1507. ON DUPLICATE KEY UPDATE `time_update` = ' . TIME;
  1508. $db->writequery('auto_online', $query, __METHOD__);
  1509. }
  1510. private static function makeUsersOnline($on, &$memcacheBitMask)
  1511. {
  1512. $db = Base_Context::getInstance()->getDbConnection();
  1513. $toInsert = $oldOnline = array();
  1514. // $toTmpInsert = array();
  1515. foreach ($on as $userId => $sources) {
  1516. $bitMask = 0;
  1517. foreach ($sources as $srcId => $data) {
  1518. $toInsert[] = '(' . (int) $userId . ', ' . (int) $srcId . ', ' . TIME . ', ' . TIME . ')';
  1519. $bitMask ^= self::getFullOnlineMask($srcId); // Prepare reset mask
  1520. }
  1521. // $toTmpInsert[] = '(' . (int) $userId . ', ' . $bitMask . ')';
  1522. if (!isset($memcacheBitMask[$userId])) {
  1523. $memcacheBitMask[$userId] = 0;
  1524. }
  1525. $memcacheBitMask[$userId] ^= $bitMask;
  1526. // unset($on[$userId]);
  1527. }
  1528. if (!empty($toInsert)) {
  1529. // Добавляем записи по юзерам в auto_online
  1530. $sql = 'INSERT INTO `auto_online` (`user_id`, `source_id`, `time_in`, `time_update`)
  1531. VALUES ' . implode(', ', $toInsert) . '
  1532. ON DUPLICATE KEY UPDATE `time_update`= ' . TIME;
  1533. $db->writequery('auto_online', $sql, __METHOD__);
  1534. }
  1535. // if (!empty($toTmpInsert)) {
  1536. // $db->writequery('tmp_online_masks', 'INSERT INTO `tmp_online_masks` (`user_id`, `new_mask`) VALUES ' . implode(', ', $toTmpInsert), __METHOD__);
  1537. // }
  1538. $oldUsers = Db_User::getMemcachedUser(array_keys($on));
  1539. foreach ($oldUsers as $userId => $userData) {
  1540. if (isset($userData['time_in'])) {
  1541. $oldOnline[$userId] = $userData['time_in'];
  1542. }
  1543. }
  1544. unset($oldUsers);
  1545. return $oldOnline;
  1546. }
  1547. public static function getAllOnlineMask()
  1548. {
  1549. $return = 0;
  1550. foreach (self::$onlineAwayTimeout as $onlineBit => $time) {
  1551. $return |= $onlineBit;
  1552. }
  1553. return $return;
  1554. }
  1555. public static function createTmpTableForMasks()
  1556. {
  1557. $db = Base_Context::getInstance()->getDbConnection();
  1558. return $db->writequery('tmp_online_masks', '
  1559. DROP TABLE IF EXISTS `tmp_online_masks`;
  1560. CREATE TEMPORARY TABLE `tmp_online_masks` (
  1561. `user_id` INT UNSIGNED NOT NULL ,
  1562. `new_mask` INT UNSIGNED NOT NULL ,
  1563. PRIMARY KEY (`user_id`)
  1564. ) ENGINE = MEMORY;', __METHOD__);
  1565. }
  1566. public static function dropTmpTableForMasks()
  1567. {
  1568. $db = Base_Context::getInstance()->getDbConnection();
  1569. return $db->writequery('tmp_online_masks', 'DROP TABLE `tmp_online_masks`', __METHOD__);
  1570. }
  1571. /**
  1572. * Крон обнуления time_out и побитовых масок онлайна
  1573. * Запускается каждый день в 2 часа ночи
  1574. * Обнуляет time_out и маску тем юзерам, у которых time_out == 0 и time_in > time() + 60 * 24
  1575. */
  1576. public static function resetOnlineStatus()
  1577. {
  1578. $db = Base_Context::getInstance()->getDbConnection();
  1579. // достать юзеров и таймин
  1580. $rows = $db->selectAll('user', "SELECT `user_id`, `time_in` FROM `user` WHERE `user_is_hidden` IS NULL AND `time_out` = 0", __METHOD__);
  1581. $toReset = array();
  1582. foreach ($rows as $row) {
  1583. if ((TIME - $row['time_in']) > self::ONLINE_MAX_ONLINE_TIME) {
  1584. $toReset[] = (int) $row['user_id'];
  1585. }
  1586. }
  1587. unset($rows);
  1588. if (!empty($toReset)) {
  1589. // обновить поля в юзерс
  1590. $db->writequery('user', 'UPDATE `user` SET `time_out` = ' . TIME . ', `online_mask` = 0 WHERE `user_id` IN(' . implode(', ', $toReset) . ')', __METHOD__);
  1591. // удалить из auto_online
  1592. $db->writequery('auto_online', 'DELETE FROM `auto_online` WHERE `user_id` IN(' . implode(', ', $toReset) . ')', __METHOD__);
  1593. Db_User::resetOnlineStatusMemcache($toReset);
  1594. }
  1595. return 'reseted=' . count($toReset);
  1596. }
  1597. public static function fireActiveUsersEvents()
  1598. {
  1599. $alreadyFoundUsersKey = 'activeUserFlag';
  1600. $users = Base_Service_SharedQueue::popAll(Base_Service_SharedQueue::INDEX_USERRETURN_CHECK);
  1601. $users = array_unique($users);
  1602. $activeUsersCount = 0;
  1603. if ($users) {
  1604. $usersToIncrementIds = array();
  1605. $i = 0; $ch = 250;
  1606. while($usersPart=array_slice($users,$i,$ch)) {
  1607. $alreadyFoundUsersKeys = array();
  1608. foreach($usersPart as $userId) {
  1609. $alreadyFoundUsersKeys[] = array($alreadyFoundUsersKey, $userId);
  1610. }
  1611. $alreadyFoundUsers = Base_Service_Lemon::mget(__METHOD__, $alreadyFoundUsersKeys);
  1612. if(!is_array($alreadyFoundUsers)) {
  1613. // не получили ответа от Lemon
  1614. return $activeUsersCount;
  1615. }
  1616. foreach($usersPart as $userId) {
  1617. if (!isset($alreadyFoundUsers[$alreadyFoundUsersKey.':'.$userId])) {
  1618. $usersToIncrementIds[] = $userId;
  1619. Base_Service_Lemon::set(__METHOD__, $alreadyFoundUsersKey, $userId, 1, 86400*7);
  1620. }
  1621. }
  1622. $i += $ch;
  1623. }
  1624. $usersToIncrement = Base_Dao_User::getUsersByIds($usersToIncrementIds);
  1625. if ($usersToIncrement) {
  1626. $statistics = new Base_Service_Counter_Main();
  1627. $statistics->increments($usersToIncrement, array('week_returned_user' => 1));
  1628. // @analytics stats
  1629. $analitycs = new Base_Service_Counter_Analytics();
  1630. $analitycs->increments($usersToIncrement, array('new_week_returned_user' => 1));
  1631. foreach($usersToIncrement as $userToIncrement) {
  1632. if (!$userToIncrement) {
  1633. continue;
  1634. }
  1635. // $eventData = array(
  1636. // 'ref_id' => $userToIncrement->getRefId(),
  1637. // 'time' => strtotime($userToIncrement->getDateInserted()),
  1638. // 'reg' => Base_Service_Counter_Stats::definePeriodId($userToIncrement->getDateInserted()),
  1639. // 'track' => Userinfo_Dao_Base::getUserSubId(),
  1640. // 'ignore_track' => 0,
  1641. // 'source' => Base_Service_Counter_Stats::castPartnerEventSource($userToIncrement->getSource(), $userToIncrement->getDateInserted()),
  1642. // 'user_id' => $userToIncrement->getId(),
  1643. // 'data' => array(
  1644. // 'act_returned_user' => 1
  1645. // ),
  1646. // );
  1647. // Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_PARTNER_EVENT, $eventData);
  1648. Base_Service_Counter_Stats::incrementPartner(
  1649. $userToIncrement,
  1650. 'new_week_returned_user',
  1651. 1,
  1652. $dummy,
  1653. strtotime($userToIncrement->getDateInserted())
  1654. );
  1655. }
  1656. $activeUsersCount = count($usersToIncrement);
  1657. }
  1658. }
  1659. return $activeUsersCount;
  1660. }
  1661. /**
  1662. * @static
  1663. * @param array $userIds
  1664. * @param array $oldOnline - время последнего онлайна
  1665. * @param array $returnData
  1666. * @param array $usersLocation - массив вида: userId => longIp
  1667. */
  1668. private static function onOnline($userIds,$oldOnline,$returnData = array(), $usersLocation = array())
  1669. {
  1670. //обновляем статус в таблицах новых юзеров для гидов
  1671. Support_Dao_Base::updateNewUsersOnlineStatus($userIds, 1);
  1672. if ($userIds) {
  1673. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_ON_ONLINE_ACTION, array($userIds, $oldOnline, $returnData, $usersLocation), __METHOD__); // для всех, кто хочет что-то делать на onOnline, обрабатывается раз в минуту в self::cronProcessOnOnlineUsers
  1674. // временно deprecated. временно!
  1675. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_ON_ONLINE_ACTION2, array($userIds, $oldOnline, $returnData, $usersLocation), __METHOD__); // второй крон на он-онлайн, только Новый Год
  1676. }
  1677. }
  1678. private static function onOffline($userIds, $users, $online = array())
  1679. {
  1680. //обновляем статус в таблицах новых юзеров для гидов
  1681. Support_Dao_Base::updateNewUsersOnlineStatus($userIds, 0);
  1682. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_ON_OFFLINE_ACTION, array($userIds, $online), __METHOD__); // для всех, кто хочет что-то делать на onOnline, обрабатывается раз в минуту в self::cronProcessOnOnlineUsers
  1683. return true;
  1684. }
  1685. /**
  1686. * тут обработка пользователей на onOnline. в self::onOnline они пихаются в очередь, разбираются тут раз в 1 минуту
  1687. *
  1688. */
  1689. public static function cronProcessOnOnlineUsers()
  1690. {
  1691. $queueData = Base_Service_SharedQueue::popAll(Base_Service_SharedQueue::INDEX_ON_ONLINE_ACTION, __METHOD__);
  1692. $userIds = $oldOnline = $returnData = $usersLocation = array();
  1693. foreach ($queueData as $record) {
  1694. $userIds = array_merge($userIds, $record[0]);
  1695. if (is_array($record[1])) {
  1696. $oldOnline = $oldOnline + $record[1];
  1697. }
  1698. if (is_array($record[2])) {
  1699. $returnData = $returnData + $record[2];
  1700. }
  1701. //isset для старых очередей
  1702. if(isset($record[3]) && is_array($record[3])){
  1703. $usersLocation = $usersLocation + $record[3];
  1704. }
  1705. }
  1706. $userIds = array_unique($userIds);
  1707. // цепляемся сюда, чтобы добавить логины в стату пытавшихся удалиться
  1708. Support_Dao_NotDeletedStats::addLogins($userIds);
  1709. $users = Base_Dao_User::getUsersByIds($userIds);
  1710. unset($queueData);
  1711. // считаем посещения пользователей за последние 2 недели
  1712. Base_Service_UserActivity::updateVisitsInformation($users, $userIds, $oldOnline);
  1713. self::updateComeBackUsers($oldOnline);
  1714. self::updateUserLocations($userIds, $usersLocation);
  1715. unset($usersLocation);
  1716. // BEGIN: стата возватов
  1717. $returnStats = new Base_Service_Counter_Returns();
  1718. $involvement = new Base_Service_Counter_Involvement();
  1719. $emailReturnStats = new Base_Service_Counter_EmailReturn();
  1720. $trackingCampaigns = Base_Service_Counter_Returns::getTrackingCampaigns();
  1721. $messenger = new Messenger_Interface_Base();
  1722. foreach ($users as $value) { /** @var $value Base_Model_User */
  1723. $returnDataRec = isset($returnData[$value->getId()]) ? $returnData[$value->getId()] : null;
  1724. $oldTimeIn = isset($oldOnline[$value->getId()]) ? $oldOnline[$value->getId()] : 0;
  1725. $value['old_time_in'] = $oldTimeIn;
  1726. $lastVisitInt = Base_Service_Counter_Returns::getLastvisit($value);
  1727. if ($oldTimeIn > 0) {
  1728. // не удаляйте, эта штука активно юзается во встречах
  1729. Base_Dao_User::setPreviousTimeIn($value->getId(), $oldTimeIn);
  1730. }
  1731. Profile_Service_Change::cancelDeleteRequest($value->getId());
  1732. $involvement->incrementStats($value, Base_Service_Counter_Involvement::FIELD_LOGINS, 1);
  1733. // был вчера или ранее и вернулся сегодня, или зарегился сегодня и это не первая сессия
  1734. if ($value->getLastVisit() < strtotime(date('Y-m-d 00:00:00')) || (strtotime(date('Y-m-d 00:00:00')) == strtotime(date('Y-m-d 00:00:00', strtotime($value->getDateInserted()))) && !Base_Service_User::isFirstSession($value))) {
  1735. // @involvement stats
  1736. $involvement->incrementStats($value, Base_Service_Counter_Involvement::FIELD_RETURNS, 1);
  1737. }
  1738. $involvement->incrementStats($value, Base_Service_Counter_Involvement::FIELD_RETURNS_WEEK, 1);
  1739. if (substr($returnDataRec, 0, 17) != 'email_unsubscribe') {
  1740. if ($oldTimeIn) { //&& $oldTimeIn <= TIME - 1080
  1741. $returnStats->increment($value, 'all_all_return');
  1742. if ($lastVisitInt > 0) {
  1743. $returnStats->increment($value, 'all_return_period_' . $lastVisitInt);
  1744. }
  1745. if ($oldTimeIn >= TIME - 7 * 24 * 60 * 60) {
  1746. $returnStats->increment($value, 'all_return_active');
  1747. } else {
  1748. $returnStats->increment($value, 'all_return_nonactive');
  1749. }
  1750. $regDaysAgo = (int)floor((time() - strtotime($value->getDateInserted())) / 60 / 60 / 24);
  1751. $returnStats->increment($value, ((0 <= $regDaysAgo && $regDaysAgo <= Base_Service_Counter_Returns::BY_DAYS_MAX_DAY) ? ('return_day_' . $regDaysAgo) : 'return_day_after'));
  1752. $returnDataRec = strpos($returnDataRec, '|') ? explode('|', $returnDataRec) : false;
  1753. $returnDataRec = isset($returnDataRec[0]) ? $returnDataRec : false;
  1754. if ($returnDataRec) {
  1755. $returnDataRec = array('utm_campaign' => $returnDataRec[0], 'utm_source' => isset($returnDataRec[1]) ? $returnDataRec[1] : '', 'utm_medium' => isset($returnDataRec[2]) ? $returnDataRec[2] : '', 'utm_content' => isset($returnDataRec[3]) ? $returnDataRec[3] : '');
  1756. $returnDataRec['old_time_in'] = $oldTimeIn; // this old_time_in key nowhere used on the face of it
  1757. $campaing = $returnDataRec['utm_campaign'];
  1758. Base_Service_Lemon2::set(__METHOD__, 'userReturnData', $value->getId(), $returnDataRec, 12 * 60 * 60);
  1759. if ($campaing == 'email_notify' || $campaing == 'email_generic' || $campaing == 'email_immediate' || $campaing == 'email_confirm' || $campaing == 'email_genericnew') {
  1760. $returnStats->increment($value, 'return_fromemail');
  1761. $returnStats->increment($value, $oldTimeIn >= TIME - 7 * 24 * 60 * 60 ? 'return_active_fromemail' : 'return_nonactive_fromemail');
  1762. if ($oldTimeIn >= TIME - 7 * 24 * 60 * 60) {
  1763. $returnStats->increment($value, strtotime($value->getDateInserted()) >= TIME - 7 * 24 * 60 * 60 ? 'return_active_fromemail_newbie' : 'return_active_fromemail_oldusers');
  1764. } else {
  1765. $month1ago = TIME - 31 * 24 * 60 * 60;
  1766. $month6ago = TIME - 6 * 31 * 24 * 60 * 60;
  1767. if (TIME - $month1ago <= $oldTimeIn) {
  1768. $returnStats->increment($value, 'return_nonactive_fromemail_month');
  1769. } else if (TIME - $month6ago <= $oldTimeIn && $oldTimeIn <= TIME - $month1ago) {
  1770. $returnStats->increment($value, 'return_nonactive_fromemail_severalmonths');
  1771. } else {
  1772. $returnStats->increment($value, 'return_nonactive_fromemail_sixmonths');
  1773. }
  1774. }
  1775. }
  1776. if (isset($trackingCampaigns[$campaing])) {
  1777. $returnStats->increment($value, 'all_return');
  1778. $returnStats->increment($value, $oldTimeIn >= TIME - 7 * 24 * 60 * 60 ? 'all_return_track_active' : 'all_return_track_nonactive');
  1779. $returnStats->increment($value, 'return_' . $campaing);
  1780. if (!empty($returnDataRec['utm_source'])) {
  1781. $returnStats->increment($value, 'return_' . $campaing . '_' . $returnDataRec['utm_source']);
  1782. }
  1783. if ($campaing == 'email_notify') {
  1784. $eventId = (int)substr($returnDataRec['utm_source'], 6);
  1785. $typeId = (int)substr($returnDataRec['utm_medium'], 6);
  1786. Base_Notification_Base::returnCounter($value, $typeId, $eventId);
  1787. // Костыль для подсчета возвратов с конкретных email типов
  1788. $returnStats->increment($value, ('return_' . $campaing . '_') . 'email_' . $typeId);
  1789. // Костыль для подсчета возвратов с конкретных ссылок в email типе
  1790. $linkId = isset($returnDataRec['utm_content']) ? $returnDataRec['utm_content'] : false;
  1791. $blockLinkId = $linkId && strpos($returnDataRec['utm_content'], 'block_') === 0 ? (int)substr($returnDataRec['utm_content'], 6) : false;
  1792. if ($blockLinkId) {
  1793. $blockTypeId = Base_Mailer_NewTypes::getFakeTypeId($typeId, $blockLinkId);
  1794. $returnStats->increment($value, ('return_' . $campaing . '_') . 'email_' . $blockTypeId);
  1795. }
  1796. } else if ($campaing == 'email_generic') {
  1797. $typeId = Base_Mailer_NewTypes::getMassMailTypeId((int)substr($returnDataRec['utm_medium'], 5));
  1798. $returnStats->increment($value, ('return_email_notify_') . 'email_' . $typeId);
  1799. }
  1800. } else {
  1801. $returnStats->increment($value, 'all_return_notrackcampaign');
  1802. }
  1803. } else {
  1804. $returnStats->increment($value, 'all_return_nosource');
  1805. }
  1806. }
  1807. // Вернулся давно не заходивший пользователь, надо проверить не через систему ли возвратов старичков он вернулся
  1808. if ($returnDataRec && $oldTimeIn && $oldTimeIn <= time() - Return_Dao_Registry::$oldTimeInterval) {
  1809. $lastFilter = Return_Dao_Registry::getLastUserFilter($value->getId());
  1810. if ($lastFilter) {
  1811. Return_Dao_Registry::oldUserReturned($value, $lastFilter, $emailReturnStats);
  1812. }
  1813. }
  1814. //Рассылка приглашений в элитарную отднодневку
  1815. Contest_Service_OneDay::inviteUser($value, $message);
  1816. }
  1817. //временная статса
  1818. Admin_Controller_Emaillinks::addEvent($value, Admin_Controller_Emaillinks::ONLINE, $returnDataRec);
  1819. }
  1820. // END: стата возватов
  1821. Base_Notification_Daemon::clearUsers($userIds, Base_Notification_Common::MEDIA_EMAIL);
  1822. Base_Mailer_Service_Cron::onUsersOnline($users);
  1823. // Новые нотификации (новые?)
  1824. $notificationInterface = Base_Interface_Factory::get('Notifications');
  1825. /** @var $notificationInterface Notifications_Interface_Common */
  1826. $notificationInterface->updateOnOnlineCommunityNotifications($userIds);
  1827. $chunked = array_chunk($userIds, 500);
  1828. foreach ($chunked as $chunkedIds) {
  1829. Return_Dao_Registry::updateUserByIds($chunkedIds, array('email_sent_count' => 0, 'email_view_count' => 0, 'email_click_count' => 0));
  1830. Contest_Service_Cron::onOnline($chunkedIds);
  1831. }
  1832. // аналитика остатоков магий на счетах петов
  1833. Pet_Service_KarmaTop::saveMagicRest($users, $oldOnline);
  1834. // выставляем таргетинг автоимпорта ньюсфида
  1835. $res = '';
  1836. try{
  1837. $res = Advsocial_Service_Base::updateTargetingNewsfeedAutoimport($userIds);
  1838. } catch(exception $e){}
  1839. /*foreach ($users as $user) {
  1840. if (Abtest_Dao_Base::getUserGroup(Abtest_Dao_Base::TEST_NOTIFY_ALGORITHM, $user) == 2 || Abtest_Dao_Base::getUserGroup(Abtest_Dao_Base::TEST_NOTIFY_ALGORITHM_OLDUSERS, $user) == 2) {
  1841. Base_Service_Lemon2::delete(__METHOD__, 'userHadImmediateNotify', $user->getId());
  1842. }
  1843. }*/
  1844. /*if (!PRODUCTION) {
  1845. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_ON_ONLINE_ACTION, array($userIds, $oldOnline, $returnData), __METHOD__);
  1846. }*/
  1847. //биллинг раздача бонусов за посещение
  1848. $dividendService = new Billing_Service_Dividend();
  1849. $dividendService->addUserDayLoginBonus($users);
  1850. //добавляние айди юзеров в очередь для напоминаний о др друзей
  1851. Fs2Friends_Service_Birthdayadvsoc::addUsersIdsToQueue($users, $oldOnline);
  1852. return $res;
  1853. }
  1854. /**
  1855. * тут обработка пользователей на onOffline. в self::onOffline они пихаются в очередь, разбираются тут раз в 1 минуту
  1856. *
  1857. */
  1858. public static function cronProcessOnOfflineUsers()
  1859. {
  1860. $queueData = Base_Service_SharedQueue::popAll(Base_Service_SharedQueue::INDEX_ON_OFFLINE_ACTION, __METHOD__);
  1861. $userIds = $online = array();
  1862. foreach ($queueData as $record) {
  1863. $userIds = array_merge($userIds, $record[0]);
  1864. if (is_array($record[1])) {
  1865. $online = $online + $record[1];
  1866. }
  1867. }
  1868. $userIds = array_unique($userIds);
  1869. $users = Base_Dao_User::getUsersByIds($userIds);
  1870. unset($queueData);
  1871. if (Admin_Service_CoreServices::isSeparatedCronEnabled()) {
  1872. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_CORE_SERVICES_STATS, $userIds, __METHOD__); // обрабатывается раз в минуту в Admin_Service_CoreServices::statsCountCron
  1873. }
  1874. // Стата по популярности
  1875. Popularity_Dao_Base::popularityStats($userIds, $users);
  1876. // BEGIN: стата возватов
  1877. $returnStats = new Base_Service_Counter_Returns();
  1878. $mailStats = new Base_Service_Counter_NewMail();
  1879. $trackingCampaigns = Base_Service_Counter_Returns::getTrackingCampaigns();
  1880. $involvement = new Base_Service_Counter_Involvement();
  1881. foreach ($users as $oUser) { /** @var $oUser Base_Model_User */
  1882. if (!empty($online[$oUser->getId()])) {
  1883. // общее время сессии
  1884. $minutes = round(($online[$oUser->getId()][0] - $online[$oUser->getId()][1]) / 60);
  1885. // Трекаем среднее время сессии
  1886. $sessionTime = null;
  1887. if ($minutes < 10 * 60) {
  1888. $sessionTime = (int)round($minutes * 60);
  1889. }
  1890. Base_Service_Counter_FriendConvert::incrementStat($oUser, Base_Service_Counter_FriendConvert::FIELD_SESSION_COUNT);
  1891. Base_Service_Counter_FriendConvert::incrementStat($oUser, Base_Service_Counter_FriendConvert::FIELD_SESSION_TIME, $sessionTime);
  1892. // if ($minutes && $minutes < 3 * 3600) {
  1893. // //$involvement->incrementStats($oUser, Base_Service_Counter_Involvement::FIELD_SESSION_TIME, $minutes);
  1894. // }
  1895. $returnDataRec = Base_Service_Lemon2::get(__METHOD__, 'userReturnData', $oUser->getId());
  1896. $returnDataRec = isset($returnDataRec['utm_campaign']) ? $returnDataRec : false;
  1897. // Стата по активным возвратам (> 5 минут)
  1898. if ($minutes >= 5) {
  1899. if ($returnDataRec) {
  1900. $campaing = $returnDataRec['utm_campaign'];
  1901. if (isset($trackingCampaigns[$campaing])) {
  1902. $returnStats->increment($oUser, 'all_confirmed_return');
  1903. $returnStats->increment($oUser, 'return_confirmed_' . $returnDataRec['utm_campaign']);
  1904. if (!empty($returnDataRec['utm_source'])) {
  1905. $returnStats->increment($oUser, 'return_confirmed_' . $returnDataRec['utm_campaign'] . '_' . $returnDataRec['utm_source']);
  1906. }
  1907. if ($returnDataRec['utm_campaign'] == 'email_notify') {
  1908. $eventId = (int)substr($returnDataRec['utm_source'], 6);
  1909. $typeId = (int)substr($returnDataRec['utm_medium'], 6);
  1910. $tTime = Service_Base::getCookie(crc32('mtiti_'.$typeId));
  1911. $time = ($tTime) ? $tTime : 0;
  1912. Base_Notification_Base::returnCounter($oUser, $typeId, $eventId, '6min');
  1913. // Костыль для подсчета возвратов с конкретных email типов
  1914. $returnStats->increment($oUser, ('return_confirmed_' . $campaing . '_') . 'email_' . $typeId);
  1915. /**var $mailStats Base_Service_Counter_NewMail*/
  1916. $mailStats->increment($oUser, 'return_confirmed_' . $typeId);//todel
  1917. // Base_Mailer_Service_Stats::increment($oUser, $typeId, Base_Mailer_Service_Stats::MAIL_RETURN_COUNT_CONFIRMED);//METKA
  1918. // Костыль для подсчета возвратов с конкретных ссылок в email типе
  1919. $linkId = isset($returnDataRec['utm_content']) ? $returnDataRec['utm_content'] : false;
  1920. $blockLinkId = $linkId && strpos($returnDataRec['utm_content'], 'block_') === 0 ? (int)substr($returnDataRec['utm_content'], 6) : false;
  1921. if ($blockLinkId) {
  1922. $blockTypeId = Base_Mailer_NewTypes::getFakeTypeId($typeId, $blockLinkId);
  1923. $returnStats->increment($oUser, ('return_confirmed_' . $campaing . '_') . 'email_' . $blockTypeId);
  1924. }
  1925. } else if ($campaing == 'email_generic') {
  1926. $typeId = Base_Mailer_NewTypes::getMassMailTypeId((int)substr($returnDataRec['utm_medium'], 5));
  1927. $returnStats->increment($oUser, ('return_confirmed_email_notify_') . 'email_' . $typeId);
  1928. }
  1929. } else {
  1930. $returnStats->increment($oUser, 'all_return_notrackcampaign_confirmed');
  1931. }
  1932. } else {
  1933. $returnStats->increment($oUser, 'all_return_nosource_confirmed_return');
  1934. }
  1935. $returnStats->increment($oUser, 'all_all_return_confirmed');
  1936. }
  1937. // Стата по среднему времени сессии
  1938. $returnStats->incrementSession($oUser, 'all_all_return', $sessionTime);
  1939. if ($returnDataRec) {
  1940. $campaing = $returnDataRec['utm_campaign'];
  1941. if (isset($trackingCampaigns[$campaing])) {
  1942. $returnStats->incrementSession($oUser, 'all_return', $sessionTime);
  1943. $returnStats->incrementSession($oUser, 'return_' . $campaing , $sessionTime);
  1944. if (!empty($returnDataRec['utm_source'])) {
  1945. $returnStats->incrementSession($oUser, 'return_' . $campaing . '_' . $returnDataRec['utm_source'], $sessionTime);
  1946. }
  1947. if ($campaing == 'email_notify') {
  1948. // Костыль для подсчета возвратов с конкретных email типов
  1949. $typeId = (int)substr($returnDataRec['utm_medium'], 6);
  1950. $tTime = Service_Base::getCookie(crc32('mtiti_'.$typeId));
  1951. $returnStats->incrementSession($oUser, ('return_' . $campaing . '_') . 'email_' . $typeId, $sessionTime);//todel
  1952. /**var $mailStats Base_Service_Counter_NewMail*/
  1953. Base_Mailer_Service_Stats::increment($oUser, $typeId, Base_Mailer_Service_Stats::MAIL_SESSION_TIME, $sessionTime);//METKA
  1954. $mailStats->increment($oUser, 'session_time_' . $typeId, $sessionTime);//todel
  1955. if ($sessionTime <= 60) {
  1956. /**var $mailStats Base_Service_Counter_NewMail*/
  1957. $mailStats->increment($oUser, 'return_reject_' . $typeId);
  1958. // Base_Mailer_Service_Stats::increment($oUser, $typeId, Base_Mailer_Service_Stats::MAIL_RETURN_COUNT_REJECT);//METKA
  1959. }
  1960. if ($sessionTime <= 120) {
  1961. $returnStats->increment($oUser, ('return_reject_' . $campaing . '_') . 'email_' . $typeId);
  1962. }
  1963. // Костыль для подсчета возвратов с конкретных ссылок в email типе
  1964. $linkId = isset($returnDataRec['utm_content']) ? $returnDataRec['utm_content'] : false;
  1965. $blockLinkId = $linkId && strpos($returnDataRec['utm_content'], 'block_') === 0 ? (int)substr($returnDataRec['utm_content'], 6) : false;
  1966. if ($blockLinkId) {
  1967. $blockTypeId = Base_Mailer_NewTypes::getFakeTypeId($typeId, $blockLinkId);
  1968. $returnStats->incrementSession($oUser, ('return_' . $campaing . '_') . 'email_' . $blockTypeId, $sessionTime);
  1969. if ($sessionTime <= 120) {
  1970. $returnStats->increment($oUser, ('return_reject_' . $campaing . '_') . 'email_' . $blockTypeId);
  1971. }
  1972. }
  1973. } else if ($campaing == 'email_generic') {
  1974. $typeId = Base_Mailer_NewTypes::getMassMailTypeId((int)substr($returnDataRec['utm_medium'], 5));
  1975. $returnStats->incrementSession($oUser, ('return_email_notify_') . 'email_' . $typeId, $sessionTime);
  1976. if ($sessionTime <= 120) {
  1977. $returnStats->increment($oUser, ('return_reject_email_notify_') . 'email_' . $typeId);
  1978. }
  1979. }
  1980. } else {
  1981. $returnStats->incrementSession($oUser, 'all_return_notrackcampaign', $sessionTime);
  1982. }
  1983. } else {
  1984. $returnStats->incrementSession($oUser, 'all_return_nosource', $sessionTime);
  1985. }
  1986. // Подсчет времени сессии по периодам
  1987. $oUser['old_time_in'] = $oUser->getPreviousTimeIn(); // костыль для корректной отработки метода ниже
  1988. $lastVisitInt = Base_Service_Counter_Returns::getLastvisit($oUser);
  1989. if ($lastVisitInt > 0) {
  1990. $returnStats->incrementSession($oUser, 'all_return_period_' . $lastVisitInt, $sessionTime);
  1991. $returnStats->incrementSession($oUser, $oUser['old_time_in'] >= TIME - 7 * 24 * 60 * 60 ? 'all_return_active_session' : 'all_return_nonactive_session', $sessionTime);
  1992. }
  1993. Base_Service_Lemon2::delete(__METHOD__, 'userReturnData', $oUser->getId());
  1994. Base_Dao_User::setPreviousTimeIn($oUser->getId(), 0);
  1995. }
  1996. //временная статса
  1997. Admin_Controller_Emaillinks::addEvent($oUser, Admin_Controller_Emaillinks::OFFLINE);
  1998. }
  1999. // END: стата возватов
  2000. Return_Dao_Registry::updateOffline($userIds);
  2001. Base_Mailer_Service_Cron::onUsersOffine($users);
  2002. // работа с сессиями VIP АБтестов
  2003. Vip_Service_Base::onOffline($users);
  2004. if (!Admin_Service_CoreServices::isSeparatedCronEnabled()) {
  2005. /** Подсчёт статы для KPI сервисов >> **/
  2006. $userApps = Base_Interface_Access::mGetUserApps($userIds);
  2007. $stanUserApps = array();
  2008. $statServices = Service_StatsFuncToId::getServicesInfo();
  2009. $totalServicesInterest = 0;
  2010. foreach (array_chunk($userIds, 500) as $usersChunk) {
  2011. $userInterests = Bi_Dao_InterestServices::calcMulti($usersChunk, Bi_Dao_AbTest_Base::DEFAULT_AB_TEST_ID, __METHOD__);
  2012. if (empty($userInterests[0])) {
  2013. continue;
  2014. }
  2015. foreach ($userInterests[0] as $uId => $appsData) {
  2016. // crutch for resolve daemon "feature" (it returns '0' if no data for user found)
  2017. if (empty($appsData)) {
  2018. continue;
  2019. }
  2020. foreach ($appsData as $appData) {
  2021. list($sType, $sId, $sWeight, $sLastVisit) = $appData;
  2022. if (round($sWeight, 0) == 0) {
  2023. continue;
  2024. }
  2025. switch ($sType) {
  2026. case Bi_Dao_Base::SERVICE_TYPE_INTERNAL:
  2027. if (isset($statServices[$sId]['storeAppUid'])) {
  2028. $stanUserApps[$uId][] = $statServices[$sId]['storeAppUid'];
  2029. $totalServicesInterest += $sWeight;
  2030. }
  2031. break;
  2032. case Bi_Dao_Base::SERVICE_TYPE_EXTERNAL:
  2033. $stanUserApps[$uId][] = $sId;
  2034. $totalServicesInterest += $sWeight;
  2035. break;
  2036. }
  2037. }
  2038. }
  2039. }
  2040. $usersCount = $allAppsCount = $interestAppsCount = 0;
  2041. foreach ($userApps as $uId => $uApps) {
  2042. $aC = count($uApps);
  2043. if ($aC > 0) {
  2044. ++$usersCount;
  2045. $allAppsCount += $aC;
  2046. if (isset($stanUserApps[$uId])) {
  2047. $interestAppsCount += count(array_intersect($stanUserApps[$uId], $uApps));
  2048. }
  2049. }
  2050. }
  2051. /*
  2052. $usersCount - кол-во обработанных юзеров
  2053. $allAppsCount - общее кол-во приложений по всем юзерам
  2054. $interestAppsCount - общее кол-во интересных сервисов по всем юзерам
  2055. $totalServicesInterest - суммарный интерес всех пользователей к сервисам
  2056. Среднее число установленных сервисов на человека (кол-во людей)
  2057. $allAppsCount / $usersCount
  2058. среднее число сервисов, в которых пользователь активен
  2059. $interestAppsCount / $usersCount
  2060. Общий суммарный интерес пользователей к сервисам на ФС по Базе Интересов
  2061. $totalServicesInterest / $usersCount
  2062. */
  2063. $defaultUser = Base_Dao_User::getUserById(Messenger_Service::SYSTEM_MESSENGER_ID);
  2064. $analytics = new Base_Service_Counter_Analytics();
  2065. if ($usersCount > 0) {
  2066. $analytics->increment($defaultUser, 'services_user_count', $usersCount);
  2067. }
  2068. if ($allAppsCount > 0) {
  2069. $analytics->increment($defaultUser, 'services_all_apps', $allAppsCount);
  2070. }
  2071. if ($interestAppsCount > 0) {
  2072. $analytics->increment($defaultUser, 'services_interest_apps', $interestAppsCount);
  2073. }
  2074. if ($totalServicesInterest > 0) {
  2075. $analytics->increment($defaultUser, 'services_total_interest', $totalServicesInterest);
  2076. }
  2077. /** << Подсчёт статы для KPI сервисов **/
  2078. }
  2079. /*if (!PRODUCTION) {
  2080. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_ON_OFFLINE_ACTION, array($userIds, $online), __METHOD__);
  2081. }*/
  2082. }
  2083. /**
  2084. * тут еще одна обработка пользователей на onOnline. в self::onOnline они пихаются в очередь, разбираются тут раз в 1 минуту
  2085. * основной крон -- self::cronProcessOnOnlineUsers
  2086. * используется для спама из сервисов знакомств
  2087. */
  2088. public static function cronProcessOnOnlineUsers2()
  2089. {
  2090. $queueData = Base_Service_SharedQueue::popAll(Base_Service_SharedQueue::INDEX_ON_ONLINE_ACTION2, __METHOD__);
  2091. $userIds = $oldOnline = $returnData = $usersLocation = array();
  2092. foreach ($queueData as $record) {
  2093. $userIds = array_merge($userIds, $record[0]);
  2094. if (is_array($record[1])) {
  2095. $oldOnline = $oldOnline + $record[1];
  2096. }
  2097. if (is_array($record[2])) {
  2098. $returnData = $returnData + $record[2];
  2099. }
  2100. //isset для старых очередей
  2101. if(isset($record[3]) && is_array($record[3])){
  2102. $usersLocation = $usersLocation + $record[3];
  2103. }
  2104. }
  2105. $userIds = array_unique($userIds);
  2106. // create static cache
  2107. $users = Base_Dao_User::getUsersByIds($userIds);
  2108. unset($queueData);
  2109. $text = '';
  2110. // Отправка нотификаций "бывалым юзерам"
  2111. $checkMeetingUsers = array();
  2112. /** @var $userModel Base_Model_User */
  2113. foreach ($users as $userModel) {
  2114. if ($userModel->getRealDaysRegistered() > 7) {
  2115. $checkMeetingUsers[] = $userModel->getId();
  2116. }
  2117. }
  2118. list($in, $out) = Meeting_Service_Base::sendFreeShowsNotify($checkMeetingUsers);
  2119. $text .= 'free_20_shows: ' . $out . ' (' . $in . '); ';
  2120. //Отправляем нотификации для свиты
  2121. $count = Team_Service_Base::sendBonusNotify($userIds);
  2122. $text .= 'n_team_bonus: ' . $count . '; ';
  2123. return $text;
  2124. }
  2125. private static function OnlineReplica($new, $off, $timeoff, $dbHosts){
  2126. $timeNow = time();
  2127. $timeOff = $timeNow - $timeoff;
  2128. // апдейтим auto_online
  2129. if(!empty($new)){
  2130. $usersNew = implode(",$timeNow),(", $new).",$timeNow";
  2131. $sqlNew = "INSERT INTO auto_online_replica (`user_id`,`time_in`) VALUES (".$usersNew.") ON DUPLICATE KEY UPDATE `time_in`=NOW()";
  2132. }
  2133. if(!empty($off)){
  2134. $usersOff = implode("','", $off);
  2135. $sqlOff = "DELETE FROM auto_online_replica WHERE user_id IN ('".$usersOff."')";
  2136. }
  2137. $sqlOld = "DELETE FROM auto_online_replica WHERE time_in < $timeOff";
  2138. foreach($dbHosts as $host){
  2139. if(!empty($new)) Driver_DBQuery::queryWrite($sqlNew, $host, __METHOD__);
  2140. if(!empty($off)) Driver_DBQuery::queryWrite($sqlOff, $host, __METHOD__);
  2141. Driver_DBQuery::queryWrite($sqlOld, $host, __METHOD__);
  2142. }
  2143. }
  2144. /**
  2145. * Add users' IDs to user_online_history table.
  2146. * Must be called before any changes in auto_online table.
  2147. */
  2148. private static function fillOnlineHistory($online, $up, $onlineDuration = 300, $timeOnLineRefresh = 180)
  2149. {
  2150. $db = Base_Context::getInstance()->getDbConnection();
  2151. $updated = 0;
  2152. $now = time();
  2153. if (date('H:i') == '23:55') { // Fixed bug table lock conflict
  2154. return $updated;
  2155. }
  2156. $dayNumber = intval(floor(($now / 86400))); // Days passed from unixtime started
  2157. switch ($dayNumber % 3) {
  2158. case 0: $dayBitMask = 1; break; // 1st day
  2159. case 1: $dayBitMask = 2; break; // 2nd day
  2160. case 2: $dayBitMask = 4; break; // 3rd day
  2161. default: $dayBitMask = 0; break;
  2162. }
  2163. $result = array();
  2164. foreach($online as $userId => $value){
  2165. if((($value[0] - $value[1]) > $onlineDuration) && in_array($userId, $up) )
  2166. {
  2167. $result[] = "($userId, $dayBitMask)";
  2168. }
  2169. }
  2170. if(!empty($result)){
  2171. $insert = "INSERT INTO user_online_history (user_id, bitmask)"."\n"
  2172. . " VALUES " . implode(',', $result)."\n"
  2173. . " ON DUPLICATE KEY UPDATE bitmask = ($dayBitMask | bitmask), updated=NOW()";
  2174. $updated += $db->writequery('user_online_history', $insert, __METHOD__);
  2175. }
  2176. // $select = $db->select()
  2177. // ->from('auto_online', array('user_id'))
  2178. // ->where('(time_in + ?) <= time_update', $onlineDuration);
  2179. // $result = $db->fetchCol($select, __METHOD__);
  2180. //
  2181. // $dayNumber = intval(floor(($now / 86400))); // Days passed from unixtime started
  2182. // switch ($dayNumber % 3) {
  2183. // case 0: $dayBitMask = 1; break; // 1st day
  2184. // case 1: $dayBitMask = 2; break; // 2nd day
  2185. // case 2: $dayBitMask = 4; break; // 3rd day
  2186. // default: $dayBitMask = 0; break;
  2187. // }
  2188. // if (!empty($result)) {
  2189. // foreach ($result as $key => $value) {
  2190. // $result[$key] = "($value, $dayBitMask)";
  2191. // }
  2192. // $insert = "INSERT INTO user_online_history (user_id, bitmask)"."\n"
  2193. // . " VALUES " . implode(',', $result)."\n"
  2194. // . " ON DUPLICATE KEY UPDATE bitmask = ($dayBitMask | bitmask), updated=NOW()";
  2195. // if (date('H:i') == '23:55') { // Fixed bug table lock conflict
  2196. // return $updated;
  2197. // } else {
  2198. // $updated += $db->writequery('user_online_history', $insert, __METHOD__);
  2199. // }
  2200. // }
  2201. return $updated;
  2202. }
  2203. public static function getUserIdByText($text)
  2204. {
  2205. if ((int)$text > 0) {
  2206. return (int)$text;
  2207. }
  2208. Utf::preg_match_all('/user\/(\d+)+/i', $text, $matches);
  2209. if (!empty($matches[1][0])) {
  2210. return (int)$matches[1][0];
  2211. }
  2212. $text = str_replace(array('http://', 'www.', ' '), array('', '', ''), $text);
  2213. $begin = Utf::strpos($text, '.fotostrana.ru');
  2214. $userDomain = Utf::substr($text, 0, $begin);
  2215. if ($userDomain) {
  2216. $user = Base_Dao_User::getUserByPageName($userDomain);
  2217. return $user ? $user['user_id'] : false;
  2218. }
  2219. return false;
  2220. }
  2221. public static function generatePassword($length = 6, $strength = 0, $generateWordPassword = false)
  2222. {
  2223. if ($generateWordPassword) {
  2224. $aFreqBase = array('through', 'around', 'something', 'little', 'before', 'another', 'behind', 'toward', 'nothing', 'because', 'moment', 'himself', 'across', 'really', 'should', 'anything', 'against', 'happen', 'window', 'street', 'enough', 'minute', 'inside', 'follow', 'second', 'suddenly', 'almost', 'without', 'better', 'course', 'continue', 'always', 'believe', 'office', 'answer', 'remember', 'friend', 'though', 'mother', 'listen', 'understand', 'between', 'father', 'morning', 'please', 'everything', 'slowly', 'someone', 'shoulder', 'question', 'hundred', 'outside', 'change', 'finger', 'scream', 'ground', 'already', 'forward', 'figure', 'become', 'glance', 'matter', 'number', 'police', 'twenty', 'together', 'return', 'appear', 'control', 'probably', 'finally', 'wonder', 'suppose', 'corner', 'quickly', 'shadow', 'myself', 'perhaps', 'business', 'notice', 'pretty', 'everyone', 'several', 'silence', 'problem', 'thousand', 'approach', 'realize', 'either', 'interest', 'surprise', 'forget', 'finish', 'picture', 'chance', 'couple', 'doctor', 'officer', 'anyone', 'pocket', 'family', 'breath', 'reason', 'machine', 'beside', 'expect', 'careful', 'school', 'whisper', 'herself', 'yourself', 'trouble', 'thirty', 'different', 'coffee', 'anyway', 'station', 'brother', 'kitchen', 'decide', 'straight', 'remain', 'whatever', 'actually', 'dollar', 'children', 'afraid', 'except', 'exactly', 'computer', 'explain', 'centre', 'corridor', 'report', 'living', 'middle', 'bottle', 'somebody', 'driver', 'rather', 'freeze', 'bedroom', 'beautiful', 'darkness', 'person', 'nobody', 'within', 'cigarette', 'attention', 'direction', 'search', 'company', 'weapon', 'apartment', 'reveal', 'charge', 'record', 'clothes', 'beyond', 'tonight', 'position', 'arrive', 'animal', 'somewhere', 'soldier', 'colour', 'strike', 'security', 'tomorrow', 'strange', 'disappear', 'distance', 'possible', 'beneath', 'bright', 'country', 'handle', 'afternoon', 'telephone', 'evening', 'certain', 'quietly', 'island', 'bathroom', 'mirror', 'throat', 'important', 'sometimes', 'engine', 'itself', 'letter', 'everybody', 'silent', 'strong', 'camera', 'consider', 'million', 'remove', 'struggle', 'promise', 'instead', 'among', 'slightly', 'hospital', 'during', 'information', 'system', 'surface', 'service', 'bridge', 'attack', 'lawyer', 'motion', 'elevator', 'button', 'general', 'flight', 'doorway', 'jacket', 'circle', 'desert', 'certainly', 'single', 'bottom', 'secret', 'immediately', 'concern', 'husband', 'escape', 'simply', 'message', 'detective', 'manage', 'creature', 'themselves', 'dinner', 'gesture', 'switch', 'expression', 'yellow', 'sergeant', 'however', 'present', 'settle', 'daughter', 'anybody', 'completely', 'nearly', 'gather', 'sister', 'special', 'hallway', 'mountain', 'gently', 'spread', 'bullet', 'stretch', 'master', 'counter', 'private', 'entrance', 'entire', 'perfect', 'command', 'screen', 'conversation', 'whether', 'ceiling', 'narrow', 'imagine', 'simple', 'colonel', 'excuse', 'tunnel', 'planet', 'double', 'recognize', 'sudden', 'monitor', 'square', 'mention', 'uniform', 'travel', 'fifteen', 'memory', 'serious', 'softly', 'contact', 'action', 'prepare', 'thought');
  2225. $iBaseIndex = rand(0, count($aFreqBase)-1);
  2226. $iBasePostfix = rand(100, 999);
  2227. return $aFreqBase[$iBaseIndex] . $iBasePostfix;
  2228. }
  2229. $alphabet = '0123456789';
  2230. if ($strength >= 1) {
  2231. $alphabet .= 'abcdefghijklmnopqrstuvwxyz';
  2232. }
  2233. if ($strength >= 2) {
  2234. $alphabet .= 'ABCDEFGHIJKLMNPQRSTUVWXYZ';
  2235. }
  2236. if ($strength >= 3) {
  2237. $alphabet .= '!@#$^_()';
  2238. }
  2239. $password = '';
  2240. for ($i = 0; $i < $length; $i++) {
  2241. $password .= Utf::getChar($alphabet, (rand() % Utf::strlen($alphabet)));
  2242. }
  2243. return $password;
  2244. }
  2245. public static function getUserName($userId)
  2246. {
  2247. $user = Base_Dao_User::getUserById($userId);
  2248. return $user['user_name'];
  2249. }
  2250. /**
  2251. * Сгенерить новый пароль для юзера и поставить ему
  2252. */
  2253. public static function resetPassword($userId)
  2254. {
  2255. $user = Base_Dao_User::getUserById($userId);
  2256. if (!$user) {
  2257. return false;
  2258. }
  2259. $newPassword = Base_Service_User::generatePassword(6, 1);
  2260. Db_User::getInstance()->saveProfile($user->getId(), array('password_hash' => Base_Service_User::getPasswordHash($newPassword)));
  2261. return $newPassword;
  2262. }
  2263. /**
  2264. * @param Base_Model_User $user
  2265. */
  2266. public static function getAutologinHash($user, $oldSalt = false)
  2267. {
  2268. if (!($user instanceof Base_Model_User )) {
  2269. $user = new Base_Model_User($user);
  2270. }
  2271. if ($oldSalt) {
  2272. return Utf::substr(self::getUserPasswordSalt($user->getId(), $user->getEmail(), $user->getPasswordHash()), 0, 15);
  2273. }
  2274. return Utf::substr(self::getAuthHash($user->getId(), $user->getEmail(), $user->getPasswordHash()), 0, 15);
  2275. }
  2276. /**
  2277. * @param Base_Model_User $user
  2278. */
  2279. public static function getAutologinLink($user, $redirectUrl = '', array $options = array(), $params = array())
  2280. {
  2281. if (!($user instanceof Base_Model_User )) {
  2282. $user = new Base_Model_User($user);
  2283. }
  2284. $projectDomain = $user->getNativeProject()->getDomain();
  2285. // Код для статистики - устарел, не надо его использовать
  2286. $tail = (!empty($options['code'])) ? '&c='.$options['code'] : '';
  2287. if (!empty($options['code']) || !empty($options['from_sysmail'])) {
  2288. $tail .= '&from=sysmail';
  2289. }
  2290. if (!empty($params)) {
  2291. foreach ($params as $title => $value) {
  2292. $tail .= '&' . $title . '=' . urlencode($value);
  2293. }
  2294. }
  2295. // Код для статистики возвратов из e-mail
  2296. if (!empty($options['mail_type_id'])) {
  2297. $tail .= '&t='.$options['mail_type_id'];
  2298. }
  2299. // Код для статистики возвратов из e-mail по событиям
  2300. if (!empty($options['event_id'])) {
  2301. $tail .= '&e=' . $options['event_id'];
  2302. }
  2303. // Код для статистики возвратов из e-mail по событиям
  2304. if (!empty($options['mail_type_ver'])) {
  2305. $tail .= '&v=' . $options['mail_type_ver'];
  2306. $tail .= '&time=' . time();
  2307. }
  2308. if (!empty($options['mail_data'])) {
  2309. $tail .= '&md='.$options['mail_data'];
  2310. }
  2311. $projectDomain = !empty($options['domain']) ? $options['domain'] : $projectDomain;
  2312. return 'http://' . $projectDomain . '/user/autologin/?u=' . $user->getId() . '&h=' . self::getAutologinHash($user) . (!$user->isEmailApproved() && !empty($options['add_confirm_hash']) ? '&c=' . Base_Service_User::getEmailConfirmHash($user) : '') .
  2313. $tail . ($redirectUrl ? '&to_url=' . urlencode($redirectUrl) : '');
  2314. }
  2315. public static function getPasswordChangeLink($user, $redirectUrl = '')
  2316. {
  2317. if (!($user instanceof Base_Model_User )) {
  2318. $user = new Base_Model_User($user);
  2319. }
  2320. $projectDomain = Base_Project_Manager::getProject()->getDomain();
  2321. return 'http://' . $projectDomain . '/user/changepassword/?u=' . $user->getId() . '&h=' . self::getAutologinHash($user) . ($redirectUrl ? '&to_url=' . urlencode($redirectUrl) : '');
  2322. }
  2323. /**
  2324. * @param Base_Model_User $user
  2325. * @param sting $newEmail Новый пароль(если есть)
  2326. */
  2327. public static function getEmailConfirmHash($user, $newEmail = '')
  2328. {
  2329. if (!($user instanceof Base_Model_User )) {
  2330. $user = new Base_Model_User($user);
  2331. }
  2332. return Utf::substr(md5($user->getId() . $user->getEmail() . $user->getPasswordHash() . $newEmail . self::getSalt(self::SALT_TYPE_EMAIL)), 0, 10);
  2333. }
  2334. /**
  2335. * @deprecated Use self::getZodiacV2 or $user->getZodiacSign()
  2336. *
  2337. * @param $date
  2338. * @return int|string
  2339. */
  2340. public static function getZodiacSign($date)
  2341. {
  2342. $signs = array(
  2343. _('Овен') => array(
  2344. 'from' => array('d' => 21, 'm' => 3),
  2345. 'to' => array('d' => 20, 'm' => 4),
  2346. ),
  2347. _('Телец') => array(
  2348. 'from' => array('d' => 21, 'm' => 4),
  2349. 'to' => array('d' => 21, 'm' => 5),
  2350. ),
  2351. _('Близнецы') => array(
  2352. 'from' => array('d' => 22, 'm' => 5),
  2353. 'to' => array('d' => 21, 'm' => 6),
  2354. ),
  2355. _('Рак') => array(
  2356. 'from' => array('d' => 22, 'm' => 6),
  2357. 'to' => array('d' => 22, 'm' => 7),
  2358. ),
  2359. _('Лев') => array(
  2360. 'from' => array('d' => 23, 'm' => 7),
  2361. 'to' => array('d' => 23, 'm' => 8),
  2362. ),
  2363. _('Дева') => array(
  2364. 'from' => array('d' => 24, 'm' => 8),
  2365. 'to' => array('d' => 23, 'm' => 9),
  2366. ),
  2367. _('Весы') => array(
  2368. 'from' => array('d' => 24, 'm' => 9),
  2369. 'to' => array('d' => 23, 'm' => 10),
  2370. ),
  2371. _('Скорпион') => array(
  2372. 'from' => array('d' => 24, 'm' => 10),
  2373. 'to' => array('d' => 22, 'm' => 11),
  2374. ),
  2375. _('Стрелец') => array(
  2376. 'from' => array('d' => 23, 'm' => 11),
  2377. 'to' => array('d' => 21, 'm' => 12),
  2378. ),
  2379. _('Козерог') => array(
  2380. 'from' => array('d' => 22, 'm' => 12),
  2381. 'to' => array('d' => 20, 'm' => 1),
  2382. ),
  2383. _('Водолей') => array(
  2384. 'from' => array('d' => 21, 'm' => 1),
  2385. 'to' => array('d' => 19, 'm' => 2),
  2386. ),
  2387. _('Рыбы') => array(
  2388. 'from' => array('d' => 20, 'm' => 2),
  2389. 'to' => array('d' => 20, 'm' => 3),
  2390. ),
  2391. );
  2392. $day = (int) (date('d', is_int($date) ? $date : strtotime($date)));
  2393. $month = (int) (date('m', is_int($date) ? $date : strtotime($date)));
  2394. foreach ($signs as $sign => $data) {
  2395. if ($month == $data['from']['m'] && $day >= $data['from']['d'] || $month == $data['to']['m'] && $day <= $data['to']['d']) {
  2396. return $sign;
  2397. }
  2398. }
  2399. }
  2400. /**
  2401. * Определение знака зодиака по дате
  2402. *
  2403. * @param int|string $date timestamp или дата в виде строки
  2404. * @param bool $asNum вернуть числовой идентификатор
  2405. *
  2406. * @return int|string название либо числовой идентификатор
  2407. */
  2408. public static function getZodiacV2($date, $asNum = false)
  2409. {
  2410. $date = (int) date('md', is_int($date) ? $date : strtotime($date));
  2411. $zodiacId = 0;
  2412. $name = '';
  2413. foreach (self::$zodiacs as $zodiacId => $zodiacConf) {
  2414. $from = $zodiacConf['range'][0];
  2415. $to = $zodiacConf['range'][1];
  2416. if ($from < $to && $date >= $from && $date <= $to || $from > $to && ($date >= $from || $date <= $to)) {
  2417. $name = $zodiacConf['name'];
  2418. break;
  2419. }
  2420. }
  2421. return $asNum ? $zodiacId : _($name);
  2422. }
  2423. /**
  2424. *
  2425. * Проверяет совместимость 2х юзеров по знакам зодиака без учета пола
  2426. * Таблица: https://docs.google.com/a/playform.com/spreadsheet/ccc?key=0AgxrrF4XHBjzdHpPcHRIcVZVbGpGTmZVNGxxalluWnc#gid=0
  2427. * Автор таблицы: Лена Лакки, автор метода: darazum
  2428. *
  2429. * @param Base_Model_User $userA
  2430. * @param Base_Model_User $userB
  2431. */
  2432. public static function checkZodiacSuitable(Base_Model_User $userA, Base_Model_User $userB)
  2433. {
  2434. if(!isset(self::$zodiacs[$userA->getZodiacSign(true)]) || !isset(self::$zodiacs[$userB->getZodiacSign(true)])) { return false; }
  2435. return in_array($userA->getZodiacSign(true), self::$zodiacs[$userB->getZodiacSign(true)]['suitable_signs'])
  2436. || in_array($userB->getZodiacSign(true), self::$zodiacs[$userA->getZodiacSign(true)]['suitable_signs']);
  2437. }
  2438. /**
  2439. * @static
  2440. * @param string $family
  2441. * @return string
  2442. */
  2443. public static function prepareUserLastName($family)
  2444. {
  2445. if (empty($family)) {
  2446. return $family;
  2447. }
  2448. $family = Utf::preg_replace('/[0-9\!\?\#\&\_\.\/]/', '', $family);
  2449. $family = Utf::trim($family, '-/._&?!');
  2450. $family = mb_strtolower($family, Utf::charset());
  2451. $nameParts = explode('-', $family);
  2452. foreach ($nameParts as &$part) {
  2453. $part = mb_convert_case(Utf::trim($part), MB_CASE_TITLE, Utf::charset());
  2454. }
  2455. $family = implode('-', $nameParts);
  2456. return $family;
  2457. }
  2458. /**
  2459. *
  2460. * @param Base_Model_User $user
  2461. */
  2462. public static function checkNeedToModerateUserpic($user)
  2463. {
  2464. if (!($user instanceof Base_Model_User)) {
  2465. return false;
  2466. }
  2467. //return $user->getAge() && $user->getAge() >= 16;
  2468. return true;
  2469. }
  2470. /**
  2471. * Обновление даты последнего визита пользователя
  2472. * @param int $userId
  2473. * @param $sourceId - откуда пришёл юзер (с сайта/мобильной версии/айфона/итп)
  2474. * @param int|null $referrerId
  2475. * @param int|null $returnData
  2476. * @return void
  2477. */
  2478. public static function updateLastVisit($userId, $sourceId, $referrerId = null, $returnData = null)
  2479. {
  2480. $timeNow = time();
  2481. $queueNum = ($userId % 2 == 0)
  2482. ? Base_Service_SharedQueue::INDEX_ONLINE_EVEN
  2483. : Base_Service_SharedQueue::INDEX_ONLINE;
  2484. Base_Service_SharedQueue::push($queueNum, array(
  2485. 'user_id' => $userId,
  2486. 'time' => $timeNow,
  2487. 'r_id' => $referrerId,
  2488. 'r_data' => $returnData,
  2489. 'src_id' => $sourceId,
  2490. 'long_ip' => ip2long(Base_Service_Common::getRealIp()),
  2491. ), __METHOD__);
  2492. }
  2493. public static function isPresetEmail($email)
  2494. {
  2495. return $email && strpos($email, '.playform.com') !== false;
  2496. }
  2497. public static function isNonApprovableEmail($email)
  2498. {
  2499. return (empty($email) || $email==Db_User::DEFAULT_EMAIL || Base_Service_User::isPresetEmail($email));
  2500. }
  2501. public static function isNewProfileEnabled($user)
  2502. {
  2503. return true;
  2504. }
  2505. public static function isNewFullProfileEnabled($user)
  2506. {
  2507. if (!self::isNewProfileEnabled($user)) {
  2508. return false;
  2509. }
  2510. if (!empty($_COOKIE['newprofile_newuser'])) {
  2511. return false;
  2512. }
  2513. return $user && (strtotime($user->getDateInserted()) + 7 * 24 * 60 * 60) <= time();
  2514. }
  2515. public static function isNewHeaderEnabled($user)
  2516. {
  2517. return self::isNewProfileEnabled($user);
  2518. }
  2519. public static function confirmUserEmail($user, $chash, $newEmail = '', $params = array(), $type = null)
  2520. {
  2521. if (!$user) {
  2522. return array('res' => false, 'error' => 'nouser');
  2523. }
  2524. foreach(
  2525. array(
  2526. 'force_hash',
  2527. 'gift',
  2528. 'contest',
  2529. 'from_confirm_hash',
  2530. 'utm_campaign',
  2531. 'utm_medium'
  2532. ) as $param) {
  2533. if (!isset($params[$param])) {
  2534. $params[$param] = '';
  2535. }
  2536. }
  2537. if ($user) {
  2538. if ( Antifraud_Service_Blacklist::checkIsInBlacklist(Antifraud_Dao_Blacklist::TYPE_EMAIL, $user['user_email'])
  2539. || ($newEmail && Antifraud_Service_Blacklist::checkIsInBlacklist(Antifraud_Dao_Blacklist::TYPE_EMAIL, $newEmail))
  2540. || Antifraud_Service_Blacklist::checkIsInBlacklist(Antifraud_Dao_Blacklist::TYPE_IP, $user['user_ip'])
  2541. || Antifraud_Service_Blacklist::checkIsInBlacklist(Antifraud_Dao_Blacklist::TYPE_REFER, $user['user_ref_id'])
  2542. || Antifraud_Service_Location::checkMultiLogin(Base_Service_Common::getRealIp(), $user->getId())) {
  2543. return array('res' => false, 'error' => 'blacklist');
  2544. }
  2545. }
  2546. $dbUser = Db_User::getInstance();
  2547. if ($params['force_hash']
  2548. || ($chash && $chash == Base_Service_User::getEmailConfirmHash($user, $newEmail))
  2549. ) {
  2550. $oldEmail = $user->getEmail();
  2551. if ($newEmail) {
  2552. $req = array('user_email' => $newEmail);
  2553. $dbUser->saveProfile($user->getId(), $req);
  2554. $user->set('user_email', $newEmail);
  2555. }
  2556. //notify to old user email if email changed
  2557. if (isset($oldEmail) && !empty($newEmail) && $user->isEmailApproved()) {
  2558. $data = array(
  2559. 'oldEmail' => $oldEmail,
  2560. 'newEmail' => $newEmail,
  2561. 'gift' => false,
  2562. );
  2563. Base_Mailer_Base::addImmediateMail($user, Base_Mailer_NewTypes::TYPE_BASE_NEWEMAIL_NOTIFY, __METHOD__, $data);
  2564. }
  2565. if (!$user->isEmailApproved()) {
  2566. $statsClient = Base_Service_Counter_Main::getInstance();
  2567. if ($params['from_confirm_hash'] && $params['utm_campaign'] == 'email_notify') {
  2568. $statsClient->increment($user, 'confirm_from_regular_email');
  2569. if (
  2570. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL ||
  2571. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_2 ||
  2572. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_3 ||
  2573. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V2 ||
  2574. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V2_1 ||
  2575. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V2_2 ||
  2576. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V2_3 ||
  2577. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V2_4 ||
  2578. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V2_5 ||
  2579. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V2_6 ||
  2580. $params['utm_medium'] == 'email_' . Base_Mailer_NewTypes::TYPE_PLEASE_CONFIRM_EMAIL_V3
  2581. ) {
  2582. Base_Dao_User::updateUser($user->getId(), array('user_source' => Base_Service_UserSource::SOURCE_REAPPROVE_EMAIL));
  2583. Start_Service_UserScheduleAction::addInitialActivityToUser($user);
  2584. Start_Service_UserScheduleAction::addAction($user, Start_Service_UserScheduleAction::TYPE_SEND_INVOLVE_OLDUSERS_REAPPROVE, '+'. mt_rand(18, 26) .' hour');
  2585. }
  2586. }
  2587. Signup_Service_Confirm::setEmailApproved($user, true, $type);
  2588. $user = Base_Dao_User::getUserById($user->getId());
  2589. if ($user->isEmailApproved()) { // @TODO temporary
  2590. $statsClient->increment($user, 'traffic_test_email_confirm_ok');
  2591. }
  2592. //-------------------landing tracker-----------
  2593. $landingTrackerService = new Start_Service_LandingTracker();
  2594. if ($landingTrackerService->isLandingTrackerUser()) {
  2595. $landingTrackerService->trackEvent(Start_Service_LandingTracker::EVENT_CONFIRMATION);
  2596. }
  2597. unset($landingTrackerService);
  2598. //-------------------end landing tracker-----------
  2599. if ($params['gift']) {
  2600. Db_UserGood::getInstance()->sendGift(DatingApp_Base_Service_Base::EMAIL_APPROVE_FREE_GIFT_ID, $user->getId());
  2601. $messengerInterface = Base_Interface_Factory::get('Messenger'); /** @var Messenger_Interface_Base $messengerInterface */
  2602. $text = "Ты получил замечательный подарок от ". Base_Project_Manager::getProject()->getTitle(1) ."! Ищи его на своей странице.";
  2603. $messengerInterface->sendMessageToUser(Messenger_Service::SYSTEM_MESSENGER_ID, $user->getId(), $text, false, false);
  2604. }
  2605. if ($user->hasMainPhoto()) {
  2606. /* @var $iface Meeting_Interface_Base */
  2607. $iface = Base_Interface_Factory::get(Meeting_Service_Base::APP_ID);
  2608. $iface->upUserShows($user->getId(), Meeting_Dao_Base::EVENT_TYPE_ADDSHOWS_CUSTOM, 10);
  2609. }
  2610. } else {
  2611. $ids = array(
  2612. Spider_Service_SocialLinks::TYPE_ID_FS => $user->getId(),
  2613. Spider_Service_SocialLinks::TYPE_ID_EMAIL => array($user->getEmail()),
  2614. );
  2615. Spider_Service_SocialLinks::getInstance()->addUser($ids);
  2616. Base_Service_Counter_Main::getInstance()->increment($user, 'traffic_test_email_confirm_already');
  2617. }
  2618. if ($user->isHidden() == Db_Moders::BAN_REASON_NEED_APPROVE_EMAIL) {
  2619. $dbUser->show($user->getId(), true);
  2620. }
  2621. if ($inviter = $user->getInviterId()) {
  2622. $inviter = Base_Dao_User::getUserById($inviter);
  2623. }
  2624. // Для приглашения от участников соревнований
  2625. if ($params['contest']) {
  2626. Service_Base::setCookie('contestInvite', $params['contest'], 7);
  2627. }
  2628. if ($inviter && Base_Service_Invite_Email::isInviteCanBeApproved($inviter->getId(), $user->getId())) {
  2629. // Начисление баллов в двух местах, тут и в каптче
  2630. Contest_Service_Invite::tryToApproveIvite($user);
  2631. }
  2632. $emailConfirmationPopup = array(
  2633. 'user_to' => $user->getId(),
  2634. 'feed' => Base_Service_NotifyWS::CHANNEL_MAIN,
  2635. 'message' => array(
  2636. 'type' => Base_Service_NotifyWS::NOTIFY_POPUP_EMAIL_CONFIRMATION,
  2637. 'feed' => Base_Service_NotifyWS::CHANNEL_MAIN,
  2638. 'data' => array(
  2639. 'confirmed' => '1'
  2640. )
  2641. ),
  2642. );
  2643. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_SOCKET_PROXY, $emailConfirmationPopup);
  2644. // Update object
  2645. return array('res' => true, 'user' => Base_Dao_User::getUserById($user->getId()));
  2646. }
  2647. Base_Service_Counter_Main::getInstance()->increment($user, 'traffic_test_email_confirm_hash_error');
  2648. if ($user->isEmailApproved()) {
  2649. Base_Service_Counter_Main::getInstance()->increment($user, 'traffic_test_email_confirm_hash_error_already');
  2650. }
  2651. return array('res' => false, 'error' => 'badhash');
  2652. }
  2653. public static function approveEmailFromEmailTrackingImg($userId, $hash, $time)
  2654. {
  2655. if (!$userId || !$hash) {
  2656. return false;
  2657. }
  2658. $user = Base_Dao_User::getUserById($userId);
  2659. if ($user->isEmailApproved()) {
  2660. return false;
  2661. }
  2662. if ($time) {
  2663. $daysAgo = round((time() - $time) / 60 / 60 / 24, 2);
  2664. $minutesAgo = round((time() - $time) / 60, 1);
  2665. } else {
  2666. $daysAgo = 'und';
  2667. $minutesAgo = 'und';
  2668. }
  2669. /*if ($hash == Base_Service_User::getEmailConfirmHash($user)) {
  2670. if ($user->getIp() == Base_Service_Common::getRealIp()) {
  2671. Base_Service_Log::log('email_approve', array($user->getId(), $user->getEmail(), $user->getIp(), Base_Service_Common::getRealIp(), $minutesAgo . ' min', $daysAgo . ' days', $time, time()));
  2672. } else {
  2673. Base_Service_Log::log('email_approve_badip', array($user->getId(), $user->getEmail(), $user->getIp(), Base_Service_Common::getRealIp(), $minutesAgo . ' min', $daysAgo . ' days', $time, time()));
  2674. }
  2675. } else {
  2676. Base_Service_Log::log('email_approve_badhash', array($user->getId(), $user->getEmail(), $user->getIp(), Base_Service_Common::getRealIp(), $minutesAgo . ' min', $daysAgo . ' days', $time, time()));
  2677. }*/
  2678. if ($hash == Base_Service_User::getEmailConfirmHash($user)) {
  2679. Base_Service_Log::log('email_img_approve_ok', array($user->getId(), $user->getEmail(), $user->getIp(), Base_Service_Common::getRealIp(), $minutesAgo . ' min', $daysAgo . ' days', $time, time()));
  2680. $stats = new Base_Service_Counter_NewMail();
  2681. $stats->increment($user, 'email_img_approve');
  2682. } else {
  2683. Base_Service_Log::log('email_img_approve_badhash', array($user->getId(), $user->getEmail(), $user->getIp(), Base_Service_Common::getRealIp(), $minutesAgo . ' min', $daysAgo . ' days', $time, time()));
  2684. }
  2685. $res = self::confirmUserEmail($user, $hash, '', array('from_confirm_hash' => 1, 'utm_campaign' => 'email_notify', 'utm_medium' => 'email_' . @$_GET['t']));
  2686. if ($res['res']) {
  2687. self::logIn($res['user']);
  2688. Base_Service_User::updateLastVisit($user->getId(), Base_Service_User::SOURCE_FS_ONLINE, Base_Service_UserSource::getCurrentReferrerId(), null); // КОСТЫЛЬ!!! обновим что юзер был онлайн при аппруве мыла по картинке
  2689. }
  2690. return $res['res'];
  2691. }
  2692. /**
  2693. * Проверяет, первая ли это сессия юзера.
  2694. *
  2695. * @param Base_Model_User $user
  2696. * @return boolean
  2697. */
  2698. public static function isFirstSession($user)
  2699. {
  2700. if (!$user) {
  2701. return false;
  2702. }
  2703. $regDate = strtotime($user->getDateInserted()); // дата реги
  2704. $timeIn = (int) $user->get('time_in'); // дата последнего входа
  2705. $timeOut = $user->get('time_out'); // пользователь не онлайн
  2706. // если пользователь онлайн, дата входа больше или равна дате реги
  2707. // и меньше чем дата реги + 1 минута, то это первая сессия пользователя
  2708. if (!$timeOut && $timeIn >= $regDate && $timeIn <= $regDate + 60) {
  2709. return true;
  2710. }
  2711. return false;
  2712. }
  2713. public static function getActivityGroupForUsers($users)
  2714. {
  2715. if(empty($users)) {
  2716. return array();
  2717. }
  2718. if(!is_object(reset($users))) {
  2719. $userIds = $users;
  2720. $users = Base_Dao_User::getUsersByIds($users);
  2721. }
  2722. $return = array();
  2723. if($users) {
  2724. foreach ($users as $id => $user) {
  2725. $return[$id] = $user->getActivity();
  2726. }
  2727. }
  2728. return $return;
  2729. }
  2730. /**
  2731. * Получение активности по email пользователя
  2732. *
  2733. * Не будь уверен что он работает корректно - он был написан, но так и не использовался
  2734. *
  2735. * @param array $userEmails
  2736. * @return array
  2737. */
  2738. public static function getActivityGroupForUsersByEmails($userEmails)
  2739. {
  2740. $users = Base_Dao_User::getUsersByEmails($userEmails);
  2741. $users = empty($users) ? array() : $users;
  2742. $result = self::getActivityGroupForUsers($users);
  2743. $newResult = $resultRecivedEmails = array();
  2744. foreach ($result as $id => $group ) {
  2745. $newResult[$users[$id]->getEmail()] = $group;
  2746. $resultRecivedEmails[] = $users[$id]->getEmail();
  2747. }
  2748. $noUserEmails = array_diff($userEmails,$resultRecivedEmails);
  2749. foreach($noUserEmails as $email) {
  2750. $newResult[$email] = self::ACTIVITY_GROUP_ERROR;
  2751. }
  2752. return $newResult;
  2753. }
  2754. public static function getActivityGroups()
  2755. {
  2756. return array(
  2757. Base_Service_User::ACTIVITY_GROUP_NEWUSER => 'Новички',
  2758. Base_Service_User::ACTIVITY_GROUP_ACTIVE => 'Активные',
  2759. Base_Service_User::ACTIVITY_GROUP_INACTIVE => 'Неактивные',
  2760. Base_Service_User::ACTIVITY_GROUP_LOST => 'Потерянные',
  2761. Base_Service_User::ACTIVITY_GROUP_ERROR => 'Не определено',
  2762. );
  2763. }
  2764. public static function getActivityGroupsWithoutError()
  2765. {
  2766. $gr = self::getActivityGroups();
  2767. unset($gr[Base_Service_User::ACTIVITY_GROUP_ERROR]);
  2768. return $gr;
  2769. }
  2770. /* END */
  2771. public static function getAgeGroups()
  2772. {
  2773. return array(
  2774. self::M_GROUP_AGE_KID => 'Дети ( < 16 )',
  2775. self::M_GROUP_AGE_ADULT => 'Взрослые (>= 16)',
  2776. self::M_GROUP_AGE_NO_AGE=> 'Без возроста',
  2777. );
  2778. }
  2779. /**
  2780. *
  2781. * @param Base_Model_User $user
  2782. */
  2783. public static function getSexGroupForUser($user)
  2784. {
  2785. return $user->isFemale() ? self::M_GROUP_FEMALE : self::M_GROUP_MALE;
  2786. }
  2787. public static function getSexGroups()
  2788. {
  2789. return array(
  2790. self::M_GROUP_MALE => 'Мужчины',
  2791. self::M_GROUP_FEMALE => 'Женщины',
  2792. );
  2793. }
  2794. /**
  2795. *
  2796. * @param array $users
  2797. */
  2798. public static function getAgeGroupForUsers($users = array())
  2799. {
  2800. $res = array();
  2801. foreach ($users as $user) {
  2802. if(is_object($user)) {
  2803. $res[$user->getId()] = self::getAgeGroupForUser($user);
  2804. }
  2805. }
  2806. return $res;
  2807. }
  2808. /**
  2809. *
  2810. * @param Base_Model_User $user
  2811. */
  2812. public static function getAgeGroupForUser($user)
  2813. {
  2814. $age = $user->getAge();
  2815. if(!$age) {
  2816. return self::M_GROUP_AGE_NO_AGE;
  2817. }
  2818. if ($age < 16) {
  2819. return self::M_GROUP_AGE_KID;
  2820. } else {
  2821. return self::M_GROUP_AGE_ADULT;
  2822. }
  2823. }
  2824. /**
  2825. * Возвращает либо crc, либо конкат $activityGroup.'-'.$sexGroup.'-'.$ageGroup
  2826. * @param type $user
  2827. * @param type $getCrc
  2828. * @return boolean
  2829. */
  2830. public static function getMainGroupForUser($user, $getCrc = false)
  2831. {
  2832. if(!is_object($user)) {
  2833. return false;
  2834. }
  2835. $ageGroup = self::getAgeGroupForUser($user);
  2836. $sexGroup = self::getSexGroupForUser($user);
  2837. $activityGroup = self::getActivityGroupForUsers(array($user));
  2838. $concat = reset($activityGroup).'-'.$sexGroup.'-'.$ageGroup;
  2839. return $getCrc ? crc32($concat) : $concat;
  2840. }
  2841. private static function simplifyName($name)
  2842. {
  2843. $name = mb_strtolower(Utf::trim($name));
  2844. $name = explode(' ', $name);
  2845. $nameStr = '';
  2846. foreach ($name as $val) {
  2847. if ($val && (strlen($val) >= 3 || count($name) <= 1)) {
  2848. $nameStr = $val;
  2849. break;
  2850. }
  2851. }
  2852. $name = $nameStr;
  2853. $converter = array(
  2854. 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e',
  2855. 'ж' => 'zh', 'з' => 'z', 'и' => 'i', 'й' => 'y', 'к' => 'k',
  2856. 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r',
  2857. 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c',
  2858. 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sch', 'ь' => '\'', 'ъ' => '\'', 'ю' => 'yu', 'я' => 'ya',
  2859. 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E',
  2860. 'Ё' => 'E', 'Ж' => 'Zh', 'З' => 'Z', 'И' => 'I', 'Й' => 'Y', 'К' => 'K',
  2861. 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O', 'П' => 'P', 'Р' => 'R',
  2862. 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C',
  2863. 'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sch', 'Ь' => '\'', 'Ъ' => '\'', 'Ю' => 'Yu', 'Я' => 'Ya',
  2864. );
  2865. $converter = array_flip($converter);
  2866. $name = strtr($name, $converter);
  2867. $name = Utf::preg_replace('/[^a-zA-Zа-яА-ЯЁё ]/', '', $name);
  2868. $name = str_replace(array('ё', 'Ё', 'Ч', 'Я'), array('е', 'Е', 'ч', 'я'), $name);
  2869. return $name;
  2870. }
  2871. /**
  2872. * Определить пол пользователей по именам
  2873. * @param array $names
  2874. */
  2875. public static function getGenderByNames($names, $asString = false)
  2876. {
  2877. if (!is_array($names)) {
  2878. $names = (array) $names;
  2879. }
  2880. $map = array(
  2881. self::USER_GENDER_FEMALE => 'w',
  2882. self::USER_GENDER_MALE => 'm',
  2883. self::USER_GENDER_UNKNOWN => null,
  2884. );
  2885. $result = array();
  2886. $namesCrc = array();
  2887. $simple = array();
  2888. foreach ($names as $name) {
  2889. $simple[$name] = self::simplifyName($name);
  2890. if (!$simple[$name]) {
  2891. $result[$name] = $asString ? $map[self::USER_GENDER_UNKNOWN] : self::USER_GENDER_UNKNOWN;
  2892. unset($simple[$name]);
  2893. } else {
  2894. $namesCrc[] = crc32($simple[$name]);
  2895. }
  2896. }
  2897. if (!empty($simple)) {
  2898. $db = Base_Context::getInstance()->getDbConnection();
  2899. $res = $db->fetchAssoc($db->select()->from('user_names', array('user_name', 'gender'))->where('user_name_crc IN (' . implode(', ' , $namesCrc) . ')'), __METHOD__);
  2900. foreach ($simple as $name => $simple) {
  2901. $gender = isset($res[$simple]['gender']) && in_array($res[$simple]['gender'], array(self::USER_GENDER_FEMALE, self::USER_GENDER_MALE))
  2902. ? (int) $res[$simple]['gender']
  2903. : self::USER_GENDER_UNKNOWN;
  2904. $result[$name] = $asString ? $map[$gender] : $gender;
  2905. }
  2906. }
  2907. return $result;
  2908. }
  2909. public static function getGenderByName($name, $asString = false)
  2910. {
  2911. $result = self::getGenderByNames($name, $asString);
  2912. return $result[$name];
  2913. }
  2914. /**
  2915. * Метод обновления таблицы user_location_short, вызывается в ShareQueue
  2916. *
  2917. * @static
  2918. * @param array $userIds - array($userId, $userId2, ...)
  2919. * @param array $usersLocation - array($userId => $userLongIp, ...)
  2920. */
  2921. private static function updateUserLocations(array $userIds, array $usersLocation)
  2922. {
  2923. $data = array();
  2924. foreach($userIds as $userId){
  2925. if(!isset($usersLocation[$userId])){
  2926. continue;
  2927. }
  2928. $data[] = array(
  2929. 'userId' => $userId,
  2930. 'longIp' => $usersLocation[$userId]
  2931. );
  2932. Base_Service_SharedQueue::push(Base_Service_SharedQueue::INDEX_ANTIFRAUD_LOCATION, array('user_id' => $userId, 'user_ip' => long2ip($usersLocation[$userId])));
  2933. }
  2934. $parts = array_chunk($data, 1000);
  2935. foreach($parts as $part){
  2936. Antifraud_Dao_LocationShort::updateCounters($part);
  2937. }
  2938. }
  2939. /**
  2940. * Проверяем подтвержден ли e-mail у юзера и загружена ли аватарка
  2941. *
  2942. * @param Base_Model_User $user
  2943. *
  2944. * @return bool
  2945. */
  2946. public static function isApprovedPhotoAndMail(Base_Model_User $user)
  2947. {
  2948. return ($user->hasApprovedMainPhoto() && $user->isEmailApproved()) ? true : false;
  2949. }
  2950. /**
  2951. * Вернет ссылку "Моя работа"
  2952. *
  2953. * @param Base_Model_User $user
  2954. *
  2955. * @return string
  2956. */
  2957. public static function getUserWorkUrl($user)
  2958. {
  2959. $workCommunity = 0;
  2960. /* @var Ticket_Interface $iTs */
  2961. $iTs = Base_Interface_Factory::get('TicketSystem');
  2962. $wcs = $iTs->getWorkCommunities($user->getId());
  2963. if (sizeof($wcs) > 0) {
  2964. $workCommunity = array_pop($wcs);
  2965. }
  2966. $avamoderator = false;
  2967. $supportAcl = new Support_Acl($user->getId());
  2968. if (in_array($user->getId(), $supportAcl->getAllSupportUsers()) && $supportAcl->isAllowed(Support_Acl::PERM_AVAMODERATOR_ACCESS)
  2969. ) {
  2970. $avamoderator = true;
  2971. }
  2972. $workHref = "";
  2973. if ($user->isExpert()) {
  2974. $workHref = "/ticket/work/?pc=1";
  2975. } elseif ($user->getProfessionId() == Base_Model_User::PETGUIDE) {
  2976. $workHref = "/pet/petgid";
  2977. } elseif ($user->isFakeModer()) {
  2978. $workHref = "/support/fake/?pc=1";
  2979. } elseif ($avamoderator) {
  2980. $workHref = "/moderation/?communityId=108&typeId=1671";
  2981. } elseif ($user->isVolunteer()) {
  2982. $workHref = "/support/volunteer/?pc=1";
  2983. } elseif ($user->isGuide()) {
  2984. $workHref = "/support/index/guide/?pc=1";
  2985. } elseif ($user->isWriter()) {
  2986. $workHref = "/event/editor/?pc=1";
  2987. } elseif ($workCommunity) {
  2988. $workHref = "/ticket/work/inWork?c=".$workCommunity."&pc=1";
  2989. }
  2990. return $workHref;
  2991. }
  2992. /**
  2993. * Возвращает список id системных пользователей
  2994. * @return array
  2995. */
  2996. public static function getSystemUsersIds()
  2997. {
  2998. return self::$_systemUsersIds;
  2999. }
  3000. /**
  3001. * Проверяет, является ли айдишник айдишником системного пользователя
  3002. * @param int $userId
  3003. * @return bool
  3004. */
  3005. public static function isSystemUserId($userId)
  3006. {
  3007. return in_array($userId, self::getSystemUsersIds());
  3008. }
  3009. /**
  3010. * Вычишает несуществующих юзеров
  3011. * и добавляет к каждому элементу массива поле с именем $field,
  3012. * в котором находится соответствующая модель юзера
  3013. *
  3014. * @param array $usersData массив, ключи которого - id пользователей
  3015. * @param string $field название параметра для модели юзера
  3016. * @param callback $callback колбек-функция для удаления отсутствующих юзеров,
  3017. * должна принимать список id единственным параметром
  3018. * @return array
  3019. */
  3020. public static function addUsersAndRemoveDeleted($usersData, $field='user', $callback=null)
  3021. {
  3022. $existingUsers = Base_Dao_User::getUsersByIds(array_keys($usersData));
  3023. $nonexistingUserIds = array();
  3024. foreach($usersData as $userId => $data) {
  3025. if (!isset($existingUsers[$userId])) {
  3026. unset($usersData[$userId]);
  3027. $nonexistingUserIds[] = $userId;
  3028. } else {
  3029. $usersData[$userId][$field] = $existingUsers[$userId];
  3030. }
  3031. }
  3032. $nonexistingUserIds = array_unique($nonexistingUserIds);
  3033. if ($nonexistingUserIds && is_callable($callback)) {
  3034. $callback($nonexistingUserIds);
  3035. }
  3036. return $usersData;
  3037. }
  3038. /**
  3039. * Вычишает записи с несуществующими юзерами из массива,
  3040. * в котором id юзера содержится в поле $userIdField
  3041. *
  3042. * @param array $records массив с записями
  3043. * @param string $userIdField название поля с id юзера
  3044. * @param callback $callback колбек-функция для удаления отсутствующих юзеров,
  3045. * должна принимать список id единственным параметром
  3046. * @return array
  3047. */
  3048. public static function removeDeletedUsersRecords($records, $userIdField='user_id', $callback=null)
  3049. {
  3050. $userIds = array();
  3051. foreach($records as $record) {
  3052. $userIds[] = $record[$userIdField];
  3053. }
  3054. $existingUsers = Base_Dao_User::getUsersByIds($userIds);
  3055. $nonexistingUserIds = array();
  3056. foreach($records as $key => $record) {
  3057. if (!isset($existingUsers[$record[$userIdField]])) {
  3058. unset($records[$key]);
  3059. $nonexistingUserIds[] = $record[$userIdField];
  3060. }
  3061. }
  3062. $nonexistingUserIds = array_unique($nonexistingUserIds);
  3063. if ($nonexistingUserIds && is_callable($callback)) {
  3064. $callback($nonexistingUserIds);
  3065. }
  3066. return $records;
  3067. }
  3068. /**
  3069. * Являются ли аватарки пользователей их реальной фоткой
  3070. *
  3071. * @param array $users
  3072. *
  3073. * @return array
  3074. */
  3075. public static function isMainPhotosReal($users)
  3076. {
  3077. $extPhotoIds = $return = array();
  3078. foreach($users as $user) {
  3079. /** @var Base_Model_User $user */
  3080. $return[$user->getId()] = false;
  3081. if ($user->getRealMainPhotoId()) {
  3082. $extPhotoIds[$user->getId()] = $user->getRealMainPhotoId();
  3083. }
  3084. }
  3085. $photoDao = Userphoto_Dao_PhotoUser::getInstance();
  3086. $photos = $photoDao->getByIds($extPhotoIds);
  3087. foreach($extPhotoIds as $userId => $extPhotoId) {
  3088. if (!empty($photos[$extPhotoId]) && $photos[$extPhotoId]['is_photo']) {
  3089. $return[$userId] = true;
  3090. }
  3091. }
  3092. return $return;
  3093. }
  3094. /**
  3095. * @return array of userIds
  3096. */
  3097. public static function getNovices()
  3098. {
  3099. $db = Base_Context::getInstance()->getDbConnection();
  3100. $res = $db->fetchAll(
  3101. $db->select()->from('user_new', array('user_id'))->order('user_id DESC')->limit(60), __METHOD__
  3102. );
  3103. return Base_Util_Array::extract($res, 'user_id');
  3104. }
  3105. private static function updateComeBackUsers($oldOnline)
  3106. {
  3107. $comeBackUserList = array();
  3108. foreach($oldOnline as $userId => $lastOnlineTime) {
  3109. if ($lastOnlineTime < (TIME - 24 * 60 * 3600)) {
  3110. $comeBackUserList[] = $userId;
  3111. }
  3112. }
  3113. Base_Service_Lemon::incrby(__METHOD__, 'come_back_users_count', 1, count($comeBackUserList), 0, false);
  3114. }
  3115. }