PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Account.php

https://github.com/whale2/users
PHP | 666 lines | 573 code | 86 blank | 7 comment | 51 complexity | 555e6ca002f52b46e052734eadb780c6 MD5 | raw file
  1. <?php
  2. require_once(dirname(__FILE__).'/Plan.php');
  3. class Account
  4. {
  5. private $id;
  6. private $name;
  7. private $role;
  8. private $plan;
  9. private $schedule;
  10. private $charges;
  11. private $nextCharge;
  12. private $paymentEngine;
  13. private $isIndividual;
  14. private $isActive;
  15. const ROLE_USER = 0;
  16. const ROLE_ADMIN = 1;
  17. /**
  18. * Gets Account by ID
  19. */
  20. public static function getByID($id)
  21. {
  22. $db = UserConfig::getDB();
  23. $account = null;
  24. if ($stmt = $db->prepare('SELECT name, plan, schedule, engine, active, next_charge FROM '.UserConfig::$mysql_prefix.'accounts WHERE id = ?'))
  25. {
  26. if (!$stmt->bind_param('i', $id))
  27. {
  28. throw new Exception("Can't bind parameter".$stmt->error);
  29. }
  30. if (!$stmt->execute())
  31. {
  32. throw new Exception("Can't execute statement: ".$stmt->error);
  33. }
  34. if (!$stmt->store_result())
  35. throw new Exception("Can't store result: ".$stmt->error);
  36. if (!$stmt->bind_result($name, $plan_id, $schedule_id, $engine_id, $active, $next_charge))
  37. {
  38. throw new Exception("Can't bind result: ".$stmt->error);
  39. }
  40. if ($stmt->fetch() === TRUE)
  41. {
  42. $charges = self::fillCharges($id);
  43. $account = new self($id, $name, $plan_id, Account::ROLE_USER, $engine_id, $schedule_id, $charges, $active, $next_charge);
  44. }
  45. $stmt->close();
  46. }
  47. else
  48. {
  49. throw new Exception("Can't prepare statement: ".$db->error);
  50. }
  51. return $account;
  52. }
  53. public static function getUserAccounts($user)
  54. {
  55. $db = UserConfig::getDB();
  56. $accounts = array();
  57. $userid = $user->getID();
  58. if ($stmt = $db->prepare('SELECT a.id, a.name, a.plan, a.schedule, a.engine, a.active, a.next_charge, au.role FROM '.UserConfig::$mysql_prefix.'accounts a INNER JOIN '.UserConfig::$mysql_prefix.'account_users au ON a.id = au.account_id WHERE au.user_id = ?'))
  59. {
  60. if (!$stmt->bind_param('i', $userid))
  61. {
  62. throw new Exception("Can't bind parameter".$stmt->error);
  63. }
  64. if (!$stmt->execute())
  65. {
  66. throw new Exception("Can't execute statement: ".$stmt->error);
  67. }
  68. if (!$stmt->store_result())
  69. throw new Exception("Can't store result: ".$stmt->error);
  70. if (!$stmt->bind_result($id, $name, $plan_id, $schedule_id, $engine_id, $active, $next_charge, $role))
  71. {
  72. throw new Exception("Can't bind result: ".$stmt->error);
  73. }
  74. while($stmt->fetch() === TRUE)
  75. {
  76. $charges = self::fillCharges($id);
  77. $accounts[] = new self($id, $name, $plan_id, $role, $schedule_id, $engine_id, $charges, $active, $next_charge);
  78. }
  79. $stmt->close();
  80. }
  81. else
  82. {
  83. throw new Exception("Can't prepare statement: ".$db->error);
  84. }
  85. if (count($accounts) == 0)
  86. {
  87. // there must be at least one personal account for each user
  88. throw new Exception("No accounts are set for the user");
  89. }
  90. return $accounts;
  91. }
  92. private function __construct($id, $name, $plan, $role, $schedule = NULL, $engine = NULL, $charges = NULL, $active = TRUE, $next_charge = NULL)
  93. {
  94. $this->id = $id;
  95. $this->name = $name;
  96. $this->plan = Plan::getPlan($plan);
  97. if(is_null($this->plan))
  98. $this->plan = Plan::getPlan(UserConfig::$default_plan);
  99. $this->schedule = is_null($schedule) ? NULL : $this->plan->getPaymentSchedule($schedule);
  100. $this->nextCharge = is_null($schedule) ? NULL : $next_charge;
  101. $this->role = $role;
  102. $this->isActive = $active;
  103. if($engine !== NULL) {
  104. UserConfig::loadModule($engine);
  105. $this->paymentEngine = new $engine;
  106. }
  107. $this->charges = is_null($charges) ? array() : $charges;
  108. }
  109. public function getID()
  110. {
  111. return $this->id;
  112. }
  113. public function getName()
  114. {
  115. if ($this->isIndividual)
  116. {
  117. $users = $this->getUsers();
  118. return $users[0]->getName();
  119. }
  120. else
  121. {
  122. return $this->name;
  123. }
  124. }
  125. public function getUsers()
  126. {
  127. $db = UserConfig::getDB();
  128. $userids = array();
  129. if ($stmt = $db->prepare('SELECT user_id FROM '.UserConfig::$mysql_prefix.'account_users WHERE account_id = ?'))
  130. {
  131. if (!$stmt->bind_param('i', $this->id))
  132. {
  133. throw new Exception("Can't bind parameter".$stmt->error);
  134. }
  135. if (!$stmt->execute())
  136. {
  137. throw new Exception("Can't execute statement: ".$stmt->error);
  138. }
  139. if (!$stmt->bind_result($userid))
  140. {
  141. throw new Exception("Can't bind result: ".$stmt->error);
  142. }
  143. while($stmt->fetch() === TRUE)
  144. {
  145. $userids[] = $userid;
  146. }
  147. $stmt->close();
  148. }
  149. else
  150. {
  151. throw new Exception("Can't prepare statement: ".$db->error);
  152. }
  153. $users = User::getUsersByIDs($userids);
  154. return $users;
  155. }
  156. public function getPlan()
  157. {
  158. return $this->plan;
  159. }
  160. public function getSchedule() {
  161. return $this->schedule;
  162. }
  163. public function getUserRole()
  164. {
  165. return $this->role;
  166. }
  167. public function isActive() {
  168. return $this->isActive;
  169. }
  170. public function getCharges() {
  171. return $this->charges;
  172. }
  173. public function getNextCharge() {
  174. return $this->nextCharge;
  175. }
  176. public static function createAccount($name, $plan, $schedule = null, $user = null, $role = Account::ROLE_USER, $engine = null)
  177. {
  178. $name = mb_convert_encoding($name, 'UTF-8');
  179. $db = UserConfig::getDB();
  180. if ($stmt = $db->prepare('INSERT INTO '.UserConfig::$mysql_prefix.'accounts (name, plan, engine) VALUES (?, ?, ?)'))
  181. {
  182. if (!$stmt->bind_param('sss', $name, $plan, $engine))
  183. {
  184. throw new Exception("Can't bind parameter".$stmt->error);
  185. }
  186. // var_dump($stmt->bind_result());die();
  187. if (!$stmt->execute())
  188. {
  189. throw new Exception("Can't execute statement: ".$stmt->error);
  190. }
  191. $id = $stmt->insert_id;
  192. $stmt->close();
  193. }
  194. else
  195. {
  196. throw new Exception("Can't prepare statement: ".$db->error);
  197. }
  198. if ($user !== null)
  199. {
  200. $userid = $user->getID();
  201. if ($stmt = $db->prepare('INSERT INTO '.UserConfig::$mysql_prefix.'account_users (account_id, user_id, role) VALUES (?, ?, ?)'))
  202. {
  203. if (!$stmt->bind_param('iii', $id, $userid, $role))
  204. {
  205. throw new Exception("Can't bind parameter".$stmt->error);
  206. }
  207. if (!$stmt->execute())
  208. {
  209. throw new Exception("Can't execute statement: ".$stmt->error);
  210. }
  211. $stmt->close();
  212. }
  213. else
  214. {
  215. throw new Exception("Can't prepare statement: ".$db->error);
  216. }
  217. }
  218. $account = new self($id, $name, $plan, $role, NULL, $engine);
  219. $account->activatePlan($plan, $schedule);
  220. return $account;
  221. }
  222. public static function getCurrentAccount($user)
  223. {
  224. $db = UserConfig::getDB();
  225. $userid = $user->getID();
  226. if ($stmt = $db->prepare('SELECT a.id, a.name, a.plan, a.schedule, a.engine, a.active, a.next_charge, au.role FROM '.
  227. UserConfig::$mysql_prefix.'user_preferences up INNER JOIN '.
  228. UserConfig::$mysql_prefix.'accounts a ON a.id = up.current_account_id INNER JOIN '.
  229. UserConfig::$mysql_prefix.'account_users au ON a.id = au.account_id WHERE up.user_id = ? AND au.user_id = ?'))
  230. {
  231. $id = null;
  232. if (!$stmt->bind_param('ii', $userid, $userid))
  233. {
  234. throw new Exception("Can't bind parameter: ".$stmt->error);
  235. }
  236. if (!$stmt->execute())
  237. {
  238. throw new Exception("Can't execute statement: ".$stmt->error);
  239. }
  240. if (!$stmt->bind_result($id, $name, $plan_id, $schedule_id, $engine, $active, $next_charge, $role))
  241. {
  242. throw new Exception("Can't bind result: ".$stmt->error);
  243. }
  244. $stmt->fetch();
  245. $stmt->close();
  246. if ($id)
  247. {
  248. $charges = self::fillCharges($id);
  249. return new self($id, $name, $plan_id, $role, $schedule_id, $engine, $charges, $active, $next_charge);
  250. }
  251. else
  252. {
  253. $user_accounts = self::getUserAccounts($user);
  254. if (count($user_accounts) > 0)
  255. {
  256. $user_accounts[0]->setAsCurrent($user);
  257. return $user_accounts[0];
  258. }
  259. }
  260. throw new Exception("No accounts are set for the user");
  261. }
  262. else
  263. {
  264. throw new Exception("Can't prepare statement: ".$db->error);
  265. }
  266. return $current_account;
  267. }
  268. public function setAsCurrent($user)
  269. {
  270. $db = UserConfig::getDB();
  271. $accounts = self::getUserAccounts($user);
  272. $valid_account = false;
  273. foreach ($accounts as $account)
  274. {
  275. if ($this->isTheSameAs($account))
  276. {
  277. $valid_account = true;
  278. break;
  279. }
  280. }
  281. if (!$valid_account)
  282. {
  283. return; // silently ignore if user is not connected to this account
  284. }
  285. if ($stmt = $db->prepare('UPDATE '.UserConfig::$mysql_prefix.'user_preferences SET current_account_id = ? WHERE user_id = ?'))
  286. {
  287. $userid = $user->getID();
  288. if (!$stmt->bind_param('ii', $this->id, $userid))
  289. {
  290. throw new Exception("Can't bind parameter");
  291. }
  292. if (!$stmt->execute())
  293. {
  294. throw new Exception("Can't update user preferences (set current account)");
  295. }
  296. $stmt->close();
  297. }
  298. else
  299. {
  300. throw new Exception("Can't update user preferences (set current account)");
  301. }
  302. }
  303. public function isTheSameAs($account)
  304. {
  305. if (is_null($account)) {
  306. return false;
  307. }
  308. return $this->getID() == $account->getID();
  309. }
  310. /*
  311. * Returns true if account has requested feature enabled
  312. */
  313. public function hasFeature($feature) {
  314. // checking if we got feature ID instead of object for backwards compatibility
  315. if (is_int($feature)) {
  316. $feature = Feature::getByID($feature);
  317. }
  318. return $feature->isEnabledForAccount($this);
  319. }
  320. private static function fillCharges($account_id) {
  321. $db = UserConfig::getDB();
  322. if(!($stmt = $db->prepare('SELECT date_time, amount FROM '.UserConfig::$mysql_prefix.'account_charge WHERE account_id = ? ORDER BY date_time')))
  323. throw new Exception("Can't prepare statement: ".$db->error);
  324. if (!$stmt->bind_param('i', $account_id))
  325. throw new Exception("Can't bind parameter".$stmt->error);
  326. if (!$stmt->execute())
  327. throw new Exception("Can't execute statement: ".$stmt->error);
  328. if (!$stmt->bind_result($datetime, $amount))
  329. throw new Exception("Can't bind result: ".$stmt->error);
  330. $charges = array();
  331. while($stmt->fetch() === TRUE)
  332. $charges[] = array('datetime' => $datetime, 'amount' => $amount);
  333. $stmt->close();
  334. return $charges;
  335. }
  336. public function paymentIsDue() {
  337. if (is_null($this->schedule)) return;
  338. $db = UserConfig::getDB();
  339. $charge_amount = $this->schedule->charge_amount;
  340. # Look if there is a negative charge, it should be a single element
  341. $c = reset(array_keys($this->charges));
  342. if ($c !== FALSE && $this->charges[$c]['amount'] < 0) {
  343. if ($this->charges[$c]['amount'] + $charge_amount > 0) { # This charge is greater than we owe to user
  344. $charge_amount += $this->charges[$c]['amount'];
  345. if (!($stmt = $db->prepare('DELETE FROM '.UserConfig::$mysql_prefix.'account_charge WHERE account_id = ?')))
  346. throw new Exception("Can't prepare statement: ".$db->error);
  347. if (!$stmt->bind_param('i', $this->id))
  348. throw new Exception("Can't bind parameter".$stmt->error);
  349. if (!$stmt->execute())
  350. throw new Exception("Can't execute statement: ".$stmt->error);
  351. $this->charges = array();
  352. $stmt->close();
  353. } else { # We still owe to user
  354. if (!($stmt = $db->prepare('UPDATE '.UserConfig::$mysql_prefix.'account_charge SET amount = ? WHERE account_id = ?')))
  355. throw new Exception("Can't prepare statement: ".$db->error);
  356. if (!$stmt->bind_param('i', $this->charges[$c]['amount'] + $charge_amount, $this->id))
  357. throw new Exception("Can't bind parameter".$stmt->error);
  358. if (!$stmt->execute())
  359. throw new Exception("Can't execute statement: ".$stmt->error);
  360. $this->charges[$c]['amount'] += $charge_amount;
  361. $stmt->close();
  362. return TRUE;
  363. }
  364. }
  365. # Rest of $charge_amount should be charged
  366. $charge = array('datetime' => date('Y-m-d H:i:s'), 'amount' => $charge_amount);
  367. $this->charges[] = $charge;
  368. if(!($stmt = $db->prepare('INSERT INTO '.UserConfig::$mysql_prefix.'account_charge (account_id, date_time, amount) VALUES (?, ?, ?)')))
  369. throw new Exception("Can't prepare statement: ".$db->error);
  370. if (!$stmt->bind_param('isd', $this->id, $charge['datetime'], $charge['amount']))
  371. throw new Exception("Can't bind parameter".$stmt->error);
  372. if (!$stmt->execute())
  373. throw new Exception("Can't execute statement: ".$stmt->error);
  374. $stmt->close();
  375. return TRUE;
  376. }
  377. public function paymentReceived($amount) {
  378. $cleared = array();
  379. $db = UserConfig::getDB();
  380. foreach(array_reverse(array_keys($this->charges)) as $n => $k) {
  381. if($amount <= 0) break;
  382. if($this->charges[$k]['amount'] <= $amount) {
  383. $amount -= $this->charges[$k]['amount'];
  384. $cleared[] = $this->charges[$k];
  385. unset($this->charges[$k]);
  386. } else {
  387. $this->charges[$k]['amount'] -= $amount;
  388. if(!($stmt = $db->prepare('UPDATE '.UserConfig::$mysql_prefix.'account_charge SET amount = ? WHERE account_id = ? and date_time = ?')))
  389. throw new Exception("Can't prepare statement: ".$db->error);
  390. if (!$stmt->bind_param('dis', $this->charges[$k]['amount'], $this->id, $this->charges[$k]['datetime']))
  391. throw new Exception("Can't bind parameter".$stmt->error);
  392. if (!$stmt->execute())
  393. throw new Exception("Can't execute statement: ".$stmt->error);
  394. $amount = 0;
  395. }
  396. }
  397. foreach($cleared as $n => $k) {
  398. if(!($stmt = $db->prepare('DELETE FROM '.UserConfig::$mysql_prefix.'account_charge WHERE account_id = ? and date_time = ?')))
  399. throw new Exception("Can't prepare statement: ".$db->error);
  400. if (!$stmt->bind_param('is', $this->id, $k['datetime']))
  401. throw new Exception("Can't bind parameter".$stmt->error);
  402. if (!$stmt->execute())
  403. throw new Exception("Can't execute statement: ".$stmt->error);
  404. }
  405. $stmt->close();
  406. # Store excessive payment as negative charge
  407. if($amount > 0) {
  408. $charge = array('datetime' => date('Y-m-d H:i:s'), 'amount' => -$amount);
  409. $this->charges[] = $charge;
  410. if(!($stmt = $db->prepare('INSERT INTO '.UserConfig::$mysql_prefix.'account_charge (account_id, date_time, amount) VALUES (?, ?, ?)')))
  411. throw new Exception("Can't prepare statement: ".$db->error);
  412. if (!$stmt->bind_param('isd', $this->id, $charge['datetime'], $charge['amount']))
  413. throw new Exception("Can't bind parameter".$stmt->error);
  414. if (!$stmt->execute())
  415. throw new Exception("Can't execute statement: ".$stmt->error);
  416. $stmt->close();
  417. }
  418. return TRUE;
  419. }
  420. public function activatePlan($plan_id, $schedule_id = NULL) {
  421. $new_plan = Plan::getPlan($plan_id);
  422. if(is_null($new_plan) || $new_plan === FALSE) return FALSE;
  423. if(!is_null($schedule_id)) {
  424. $new_schedule = $new_plan->getPaymentSchedule($schedule_id);
  425. if(is_null($new_schedule))
  426. $new_schedule = $new_plan->getDefaultPaymentSchedule();
  427. } else {
  428. $new_schedule = NULL;
  429. }
  430. $old_plan = $this->plan->id;
  431. $old_schedule = is_null($this->schedule) ? NULL : $this->schedule->id;
  432. $this->plan->deactivate_hook($plan_id, $schedule_id);
  433. $this->plan = $new_plan;
  434. $this->schedule = $new_schedule;
  435. if($this->paymentEngine)
  436. $this->paymentEngine->ChangeSubscription($plan_id, $schedule_id, $old_plan, $old_schedule);
  437. $this->plan->activate_hook($old_plan,$old_schedule);
  438. $this->isActive = 1;
  439. $this->nextCharge = is_null($this->schedule) ? NULL : date('Y-m-d H:i:s',time() + $this->schedule->charge_period * 86400);
  440. # Update db
  441. # There is a risk that this query fail. If so, object state will differ from db state.
  442. # Should be addressed in further releases
  443. $db = UserConfig::getDB();
  444. if (!($stmt = $db->prepare('UPDATE '.UserConfig::$mysql_prefix.
  445. 'accounts SET plan = ?, schedule = ?, active = 1, next_charge =? WHERE id = ?')))
  446. throw new Exception("Can't prepare statement: ".$db->error);
  447. if (!$stmt->bind_param('sssi', $plan_id, $schedule_id, $this->nextCharge, $this->id))
  448. throw new Exception("Can't bind parameter".$stmt->error);
  449. if (!$stmt->execute())
  450. throw new Exception("Can't execute statement: ".$stmt->error);
  451. return TRUE;
  452. }
  453. public function deactivatePlan() {
  454. $db = UserConfig::getDB();
  455. $this->plan->deactivate_hook($this->downgrade_to);
  456. if(!is_null($this->downgrade_to)) {
  457. $this->activatePlan($this->downgrade_to);
  458. return TRUE;
  459. } else {
  460. # Nothing to downgrade to - mark account as not active
  461. $this->isActive = FALSE;
  462. if (!($stmt = $db->prepare('UPDATE '.UserConfig::$mysql_prefix.'accounts SET active = 0 WHERE id = ?')))
  463. throw new Exception("Can't prepare statement: ".$db->error);
  464. if (!$stmt->bind_param('i', $this->id))
  465. throw new Exception("Can't bind parameters: ".$stmt->error);
  466. if (!$stmt->execute())
  467. throw new Exception("Can't execute statement: ".$stmt->error);
  468. return FALSE;
  469. }
  470. }
  471. public function setPaymentSchedule($schedule_id) {
  472. if(!($schedule = $this->plan->getPaymentSchedule($schedule_id)))
  473. return FALSE;
  474. if($this->paymentEngine)
  475. $this->paymentEngine->ChangeSubscription($schedule);
  476. $this->schedule = $schedule;
  477. $this->nextCharge = date('Y-m-d H:i:s',time() + $this->schedule->charge_period * 86400);
  478. # Update db
  479. $db = UserConfig::getDB();
  480. if (!($stmt = $db->prepare('UPDATE '.UserConfig::$mysql_prefix.'accounts SET schedule = ?, next_charge = ? WHERE id = ?')))
  481. throw new Exception("Can't prepare statement: ".$db->error);
  482. if (!$stmt->bind_param('ssi', $schedule_id, $this->nextCharge, $this->id))
  483. throw new Exception("Can't bind parameters: ".$stmt->error);
  484. if (!$stmt->execute())
  485. throw new Exception("Can't execute statement: ".$stmt->error);
  486. return TRUE;
  487. }
  488. public function getScheduleID() {
  489. return $this->schedule ? $this->schedule->id : NULL;
  490. }
  491. public function getPlanID() {
  492. return $this->plan->id;
  493. }
  494. public function getPaymentEngine() {
  495. return $this->paymentEngine; # do we need class or just id?
  496. }
  497. public function isIndividual() {
  498. return $this->isIndividual;
  499. }
  500. public function setPaymentEngine($engine) {
  501. if($engine == NULL)
  502. return FALSE;
  503. UserConfig::loadModule($engine);
  504. $this->paymentEngine = new $engine;
  505. # Update db
  506. $db = UserConfig::getDB();
  507. if (!($stmt = $db->prepare('UPDATE '.UserConfig::$mysql_prefix.'accounts SET engine = ? WHERE id = ?')))
  508. throw new Exception("Can't prepare statement: ".$db->error);
  509. if (!$stmt->bind_param('si', $engine, $this->id))
  510. throw new Exception("Can't bind parameters: ".$stmt->error);
  511. if (!$stmt->execute())
  512. throw new Exception("Can't execute statement: ".$stmt->error);
  513. return TRUE;
  514. }
  515. }