User.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <?php
  2. namespace app\models;
  3. use \MongoDate;
  4. use \lithium\util\String;
  5. use \lithium\util\Validator;
  6. use \App\Libraries\openID\LightOpenID;
  7. use \lithium\security\Password;
  8. class User extends \lithium\data\Model {
  9. //To bypass mongo bug
  10. protected $_meta = array('key' => '_id');
  11. protected $_schema = array('_id' => array('type' => 'id'),
  12. 'feed' => array('type'=>'string', 'array' => true),
  13. 'animelist' => array('type' => 'object', 'array' => true),
  14. 'mangalist' => array('type' => 'object', 'array' => true)
  15. );
  16. public static function __init()
  17. {
  18. //Initialize the parent if you want the database and everything setup (which of course we do)
  19. parent::__init();
  20. //Confirms that the username isn't already in use.
  21. Validator::add('isUniqUser', function($username) {
  22. //If we can't find a user with the same user name then the name is unique.
  23. return User::count(array('conditions' => compact('username'))) == 0;
  24. });
  25. //Checks if the username contains profanity
  26. Validator::add('isClean', function($username) {
  27. //Needs to do a dictonary lookup, but too lazy to implement right now.
  28. return true;
  29. });
  30. Validator::add('isValidGender', function($gender) {
  31. //If the geneder is male or female return true.
  32. return ($gender == 'Male' || $gender == 'Female');
  33. });
  34. Validator::add('isUniqueEmail', function($email) {
  35. //Find all the email address that match the one inputted,
  36. //If there is none found (count == 0) then return true (Email address is unique)
  37. return User::count(array('conditions' => compact('email'))) == 0;
  38. });
  39. Validator::add('validBirthday', function($birthday) {
  40. // :TODO:
  41. //*birthday needs to be 1930 <= $birthday <= current year - 11 (11 or older);
  42. return true;
  43. });
  44. }
  45. /* Validation code */
  46. /*
  47. Things that need to be validated
  48. *The username cannot be taken
  49. *the username cannot cotain special chars or spaces
  50. *there must be an email address
  51. *The email address needs to be a valid email
  52. *it cannot be in use already
  53. *the password needs to be atleast 6 characters
  54. *the username cannot contain profanity
  55. *birthday needs to be 1930 <= $birthday <= current year - 11 (11 or older);
  56. *gender must be Male or Female
  57. */
  58. public $validates = array(
  59. 'username' => array(array('isUniqUser', 'message' => "Username is already taken."),
  60. array('notEmpty', 'message' => 'Please enter a Username.'),
  61. array('isClean', 'message' => 'Profanity is not allowed in Usernames'),
  62. array('alphaNumeric', 'message' => "Usernames cant contain special characters")
  63. ),
  64. 'email' => array(array('email', 'message' => 'The email address is not valid.'),
  65. array('notEmpty', 'message' => 'An email address must be entered.'),
  66. array('isUniqueEmail', 'message' => 'That email address is already in use. Did you forget your password?')
  67. ),
  68. //Passwords validation is invented.
  69. 'password' => array(array('lengthBetween' =>array('min' => '6', 'max' =>'20'),
  70. 'message' => 'Your password must be between 6 and 20 characters')
  71. ) /*,
  72. //It's always possible for people to submit invalid data using cURL or something.
  73. 'gender' => array('isValidGender', 'message' => 'Please check for dangly bits or lack thereof.')
  74. */
  75. );
  76. /* Defaults */
  77. /*
  78. joindate = today,
  79. accesslevel = "user"
  80. */
  81. public function updateuser($entity, $data)
  82. {
  83. $conditions = array('_id' => $entity->_id, 'state' => 'default');
  84. $options = array('multiple' => false, 'safe' => true, 'upsert'=>true);
  85. return static::update($data, $conditions, $options);
  86. }
  87. /**
  88. * Creates a post and stores it into the appropriate user(s) array(s)
  89. * @param User $entity the instance of the user posting the message
  90. * @param Array $data The data from the request (usually submiited from the form)
  91. * @param Array $options Currently not implemented
  92. * @return null Nothing for now, though should return true or false in the future :TODO:
  93. */
  94. public function post($entity, $data, array $options = array())
  95. {
  96. //TODO, fix all methods so that they don't take $data directly
  97. //TODO add validators to these methods/models
  98. //Create the post
  99. $post = Post::create(array('datetime' => new MongoDate(),
  100. 'username' => $entity->username,
  101. 'user_id' => $entity->_id,
  102. 'level' => null));
  103. //1. Parse
  104. //Break the string into an array of words
  105. $search = explode(" ", $data['body']);
  106. //if the first word is DM
  107. if ($search[0] == "DM")
  108. {
  109. //Remove the '@' symbol before we search for that username
  110. $to = substr($search[1], 1);
  111. $post->type = "DM";
  112. //:TODO: Catch the return incase it's false.
  113. return $post->directMessage($to);
  114. }
  115. //If the post beings with a mention (it's a reply / note)
  116. if ($search[0] == "@")
  117. {
  118. //Set the post level to hidden
  119. $post->level = "hidden";
  120. }
  121. //Check if there are any mentions or topics
  122. $post->body = $post->parse($search);
  123. //Because there is a chance that parse will set post level to something
  124. //We pass the current value to level since it will just
  125. //return the same it not set.
  126. //Yes, we could use an if ! null but whatever.
  127. $post->level = $this->postLevel($entity, $post, $post->level);
  128. //Save the post to the posts database.
  129. $post->save();
  130. //If the user has friends
  131. if (count($entity->friends) > 0)
  132. {
  133. //Save this posts to the feed of all the Users Friends
  134. $post->storeAll($entity->friends);
  135. }
  136. //Add this post to the user's feed array
  137. $post->store($entity);
  138. //$save = $entity->save(null, array('validate' => false));
  139. /* In the future, there should be a way to make this method quicker and non-blocking*/
  140. //For each friend, post it in their feed (friends is an array of usernames as strings)
  141. /*if (!empty($entity->friends))
  142. {
  143. foreach ($entity->friends as $friend)
  144. {
  145. //Grab the friend from the database
  146. $curFriend = User::find('first', array('conditions' => array('username' => $friend)));
  147. //Add the post to their feed.
  148. //Array unshift puts the new post at the first element of the feed array.
  149. $curFriend->feed = array_unshift($post->_id, $curFriend->feed);
  150. //Save the friend to the database,
  151. $curFriend->save(array('validates' => false));
  152. //Eventually, we can call a node.js endpoint here
  153. //To tell all subscribers that there is a new a post
  154. //Something like "curl POST $post->_id nodeserver://endpoint/myFeed"
  155. }
  156. } */
  157. }
  158. /**
  159. * Store's the post to the specified user's feed
  160. * @param User $user The user to add the post to
  161. * @param Post $post The post too add to the feed
  162. * @return boolean True if the operation sucsceeded, false otherwise
  163. */
  164. private function storePost($user, $post)
  165. {
  166. $updateData = array('$push' => array('feed' => $post['_id']->__toString()));
  167. $conditions = array('_id' => $user['_id']);
  168. $result = User::update($updateData, $conditions, array('atomic' => false));
  169. return $result;
  170. }
  171. /**
  172. * Returns the appropriate post level for the post
  173. * @see app\models\Post
  174. * @param User $user The user instance of the user that the post will be posted to
  175. * @param Post $post The Post to determine the level for
  176. * @param string $level The level (if you want to override the output)
  177. * @return string $level if one is passed in, otherwise hidden if the post begins with a mention or private if the user has his posts protected
  178. */
  179. public static function postLevel($user, $post, $level = null)
  180. {
  181. //if for some crazy reason you need to set the post to a specific type using this
  182. // method then if $level is not null, return $level
  183. if ($level != null)
  184. {
  185. return $level;
  186. }
  187. //If the post is directed at user (begins with @username)
  188. //This is done in parse right now
  189. //return "hidden"
  190. //If the user has their post set to private
  191. if (isset($user->settings['private']));
  192. {
  193. //return private
  194. return "private";
  195. }
  196. //If none of the above apply
  197. return "public";
  198. }
  199. //When we switch to a graph database, there is a bidirection vertex function
  200. //So we can cut this search down to one query, and one line of code :P
  201. /**
  202. * Check wether user1 and user2 are friends
  203. *
  204. * @param string $user1 The username of the first user
  205. * @param string $user2 The username of the second user
  206. * @return boolean True if the users are friends, false otherwise
  207. */
  208. public static function areFriends($user1, $user2)
  209. {
  210. //Get the first user from the database,
  211. $usr1 = User::find('first', array('conditions' => array('username' => $user1)));
  212. //If user 2 is in user1's friends,
  213. if (in_array($user2, $usr1->friends))
  214. {
  215. $usr2 = User::find('first', array('conditions' => array('username' => $user2)));
  216. //And user1 is in user2s friends
  217. if (in_array($user1, $usr2->friends))
  218. {
  219. return true;
  220. }
  221. }
  222. //otherwise
  223. return false;
  224. }
  225. /**
  226. * GetPosts gets posts from the user (entity) based on some params, and returns them
  227. * @param User $entity, the calling object
  228. * @param Array $options, the options that will be used, you can see the defaults below
  229. * @return array(), with all the posts found or false, if somehow that didn't happen
  230. */
  231. public function getPosts($entity, array $options = array())
  232. {
  233. $posts;
  234. $defaults = array(
  235. 'limit' => '20',
  236. 'qualifier' => 'all',
  237. 'level' => 'public',
  238. );
  239. //If the user passed in options
  240. if (!empty($options))
  241. {
  242. //merge the options with the defaults (upsert them)
  243. array_merge($defaults, $options);
  244. }
  245. //var_dump($entity);
  246. //exit();
  247. //If the user has more posts than the limit, return the limit, if the have less return
  248. $count = (count($entity->feed) >= $defaults['limit']) ? $defaults['limit'] : count($entity->feed);
  249. if ($count == 0)
  250. {
  251. return false;
  252. }
  253. //else
  254. var_dump(Post::find('all', array('conditions' => array('$in' => $entity->feed))));
  255. exit();
  256. for ($i = 0; $i < $count; $i++)
  257. {
  258. $posts[] = Post::find('all', array('conditions' => array('$in' => $entity->feed)));
  259. $posts[] = Post::find($qaulifier, array('conditions' => array('_id' => $entity->feed[$i], 'level' => $defaults[level])));
  260. }
  261. return $posts;
  262. }
  263. /**
  264. * Increments the amount profile views for the user.
  265. * This is the command we are going to give to Mongo, which breaks down to db.users.update({_id : $id}, {$inc: {profileViews : 1}} )
  266. * Which says, find the user by their mongo ID, then increment their profile views by one.
  267. * @param User $entity The instance of user to increment
  268. * @param string $type Not implemented but in the future will allow you to increment anime manga and kdrama list views :TODO:
  269. * @return null
  270. */
  271. public function incrementViews($entity, $type = null)
  272. {
  273. if ($type = null)
  274. {
  275. $views = 'profileViews';
  276. }
  277. $updateData = array('$inc' => array('profileViews' => 1));
  278. $conditions = array('_id' => $entity['_id']);
  279. $result = User::update($updateData, $conditions, array('atomic' => false));
  280. return $result;
  281. }
  282. //Overrides save so we can do some stuff before the data is commited to the database.
  283. public function save($entity, $data = null, array $options = array())
  284. {
  285. //If the new password field is empty, or this is a new user
  286. if(!empty($entity->newpass) || !$entity->exists())
  287. {
  288. //Generate Salt for the user.
  289. $salt = Password::salt('bf', 6);
  290. //Hash their password.
  291. $data['password'] = Password::hash($entity->newpass, $salt);
  292. $data['salt'] = $salt;
  293. unset($entity->newpass);
  294. }
  295. //If the entity doesn't exist or if the password password has been modified
  296. return parent::save($entity, $data, $options);
  297. }
  298. }
  299. ?>