summaryrefslogtreecommitdiffstats
path: root/models/User.php
blob: 1c1fe6427ea0c50f04c8e320deb12fd24e97f8b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
<?php 

namespace app\models;

use \MongoDate; 
use \lithium\util\String;
use \lithium\util\Validator;
use \App\Libraries\openID\LightOpenID;
use \lithium\security\Password;

class User extends \lithium\data\Model {
	//To bypass mongo bug
	protected $_meta = array('key' => '_id');
	protected $_schema = array('_id' => array('type' => 'id'), 'feed' => array('type'=>'string', 'array'=>true));

	public static function __init()
	{                        
		//Initialize the parent if you want the database and everything setup (which of course we do)
		parent::__init();
		                                                    	
		//Confirms that the username isn't already in use.                           
		Validator::add('isUniqUser', function($username) {
			//If we can't find a user with the same user name then the name is unique. 
			return User::count(array('conditions' => compact('username'))) == 0;
		});

		//Checks if the username contains profanity
		Validator::add('isClean', function($username) {
			//Needs to do a dictonary lookup, but too lazy to implement right now.
			return true;	
		});

		Validator::add('isValidGender', function($gender) {
			//If the geneder is male or female return true.
			return ($gender == 'Male' || $gender == 'Female');
		});
		
		Validator::add('isUniqueEmail', function($email) {
			//Find all the email address that match the one inputted,
			//If there is none found (count == 0) then return true (Email address is unique)
			return User::count(array('conditions' => compact('email'))) == 0;
		});	

		Validator::add('validBirthday', function($birthday) {
			// :TODO: 
			//*birthday needs to be 1930 <= $birthday <= current year - 11 (11 or older); 
			return true;	
		});
	}


	/* Validation code */
	/*
		Things that need to be validated
		*The username cannot be taken
		*the username cannot cotain special chars or spaces
		*there must be an email address
			*The email address needs to be a valid email
			*it cannot be in use already
		*the password needs to be atleast 6 characters 
		*the username cannot contain profanity
		*birthday needs to be 1930 <= $birthday <= current year - 11 (11 or older); 
		*gender must be Male or Female
	*/
	public $validates = array(
		'username' => array(array('isUniqUser',   'message' => "Username is already taken."),
							array('notEmpty',     'message' => 'Please enter a Username.'),
							array('isClean',      'message' => 'Profanity is not allowed in Usernames'),
							array('alphaNumeric',  'message' => "Usernames cant contain special characters")
							),

		'email' => array(array('email', 		  'message' => 'The email address is not valid.'),
						 array('notEmpty', 		  'message' => 'An email address must be entered.'),
						 array('isUniqueEmail',	  'message' => 'That email address is already in use. Did you forget your password?')
						),

						
		//Passwords validation is invented.
		'password' => array(array('lengthBetween' =>array('min' => '6', 'max' =>'20'),
												  'message' => 'Your password must be between 6 and 20 characters')
						   ) /*,
						   
		//It's always possible for people to submit invalid data using cURL or something.
		'gender'  => array('isValidGender',     'message' => 'Please check for dangly bits or lack thereof.')
		*/
	);
	

	/* Defaults */
	/*
		joindate = today,
		accesslevel = "user"
	*/

	public function updateuser($entity, $data)
	{
		$conditions = array('_id' => $entity->_id, 'state' => 'default');
		$options = array('multiple' => false, 'safe' => true, 'upsert'=>true);
		return static::update($data, $conditions, $options);
	}

	/** 
	 * Creates a post and stores it into the appropriate user(s) array(s)
	 * @param User $entity the instance of the user posting the message
	 * @param Array $data The data from the request (usually submiited from the form)
	 * @param Array $options Currently not implemented 
	 * @return null Nothing for now, though should return true or false in the future :TODO:
	 */ 
	public function post($entity, $data, array $options = array())
	{
		//TODO, fix all methods so that they don't take $data directly
		//TODO add validators to these methods/models
		
		//Create the post
		$post = Post::create(array('datetime' => new MongoDate(),
								   'username' => $entity->username,
								   'user_id' => $entity->_id,
								   'level' => null));
		//1. Parse 
		//Break the string into an array of words
		$search = explode(" ", $data['body']);

		//if the first word is DM
		if ($search[0] == "DM")
		{
			//Remove the '@' symbol before we search for that username
			$to = substr($search[1], 1);

			$post->type = "DM";
			//:TODO: Catch the return incase it's false.
			return $post->directMessage($to);
		}

		//If the post beings with a mention (it's a reply / note)
		if ($search[0] == "@")
		{
			//Set the post level to hidden
			$post->level = "hidden";
		}


		//Check if there are any mentions or topics
		$post->body = $post->parse($search);


		//Because there is a chance that parse will set post level to something
		//We pass the current value to level since it will just
		//return the same it not set.
		//Yes, we could use an if ! null but whatever. 
		$post->level = $this->postLevel($entity, $post, $post->level);
		
		//Save the post to the posts database. 
		$post->save();
		
		//If the user has friends 
		if (count($entity->friends) > 0)
		{
			//Save this posts to the feed of all the Users Friends 
			$post->storeAll($entity->friends);
		}
		//Add this post to the user's feed array
		$post->store($entity);




		//$save = $entity->save(null, array('validate' => false));
		/* In the future, there should be a way to make this method quicker and non-blocking*/
		//For each friend, post it in their feed (friends is an array of usernames as strings)
		/*if (!empty($entity->friends))
		{
			foreach ($entity->friends as $friend)
			{
				//Grab the friend from the database
				$curFriend = User::find('first', array('conditions' => array('username' => $friend)));
				
				//Add the post to their feed.
				//Array unshift puts the new post at the first element of the feed array.
				$curFriend->feed = array_unshift($post->_id, $curFriend->feed); 
				
				//Save the friend to the database, 
				$curFriend->save(array('validates' => false));

				//Eventually, we can call a node.js endpoint here 
				//To tell all subscribers that there is a new a post
				//Something like "curl POST $post->_id nodeserver://endpoint/myFeed"
			}
		} */
	}

	/**
	 * Store's the post to the specified user's feed 
	 * @param User $user The user to add the post to
	 * @param Post $post The post too add to the feed 
	 * @return boolean True if the operation sucsceeded, false otherwise
	 */
	private function storePost($user, $post)
	{
		$updateData = array('$push' => array('feed' =>  $post['_id']->__toString()));
		$conditions =  array('_id' => $user['_id']);
		$result = User::update($updateData, $conditions, array('atomic' => false));

		return $result; 
	}


	/**
	 * Returns the appropriate post level for the post 
	 * @see app\models\Post
	 * @param User $user The user instance of the user that the post will be posted to
	 * @param Post $post The Post to determine the level for
	 * @param string $level The level (if you want to override the output)
	 * @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
	 */
	public static function postLevel($user, $post, $level = null)
	{
		//if for some crazy reason you need to set the post to a specific type using this
		// method then if $level is not null, return $level
		if ($level != null)
		{
			return $level;
		}
		//If the post is directed at user (begins with @username)
			//This is done in parse right now
			//return "hidden"
		
		//If the user has their post set to private
		if (isset($user->settings['private']));
		{
			//return private
			return "private";
		}
		
		//If none of the above apply
		return "public";
	}

	//When we switch to a graph database, there is a bidirection vertex function
	//So we can cut this search down to one query, and one line of code :P
	/**
	 * Check wether user1 and user2 are friends
	 *
	 * @param string $user1 The username of the first user
	 * @param string $user2 The username of the second user
	 * @return boolean True if the users are friends, false otherwise 
	 */
	public static function areFriends($user1, $user2)
	{
		//Get the first user from the database, 
		$usr1 = User::find('first', array('conditions' => array('username' => $user1)));

		//If user 2 is in user1's friends,
		if (in_array($user2, $usr1->friends))
		{
			$usr2 = User::find('first', array('conditions' => array('username' => $user2)));
			//And user1 is in user2s friends
			if (in_array($user1, $usr2->friends))
			{
				return true;
			}
		}
		//otherwise
		return false;
	}

	/**
	* GetPosts gets posts from the user (entity) based on some params, and returns them
	* @param User $entity, the calling object
	* @param Array $options, the options that will be used, you can see the defaults below 
	* @return array(), with all the posts found or false, if somehow that didn't happen  
	*/
	public function getPosts($entity, array $options = array())
	{
		$posts; 
		$defaults = array(
				'limit' => '20',
				'qualifier' => 'all',
				'level' => 'public',
			);

		//If the user passed in options 
		if (!empty($options))
		{
			//merge the options with the defaults (upsert them) 
			array_merge($defaults, $options);
		}	

		//var_dump($entity);
		//exit();
		//If the user has more posts than the limit, return the limit, if the have less return 
		$count = (count($entity->feed) >= $defaults['limit']) ? $defaults['limit'] : count($entity->feed); 
		if ($count == 0)
		{
			return false;
		}
		//else
		var_dump(Post::find('all', array('conditions' => array('$in' => $entity->feed))));
		exit();
		for ($i = 0; $i < $count; $i++)
		{
			$posts[] = Post::find('all', array('conditions' => array('$in' => $entity->feed)));
			$posts[] = Post::find($qaulifier, array('conditions' => array('_id' => $entity->feed[$i], 'level' => $defaults[level])));
		}
		return $posts;
	}

    /**
	 * Increments the amount profile views for the user.
	 * This is the command we are going to give to Mongo, which breaks down to db.users.update({_id : $id}, {$inc: {profileViews : 1}} )
	 * Which says, find the user by their mongo ID, then increment their profile views by one.
	 * @param User $entity The instance of user to increment
	 * @param string $type Not implemented but in the future will allow you to increment anime manga and kdrama list views :TODO:
	 * @return null 
	*/
	public function incrementViews($entity, $type = null)
	{
		if ($type = null)
		{
			$views = 'profileViews';
		}
		$updateData = array('$inc' => array('profileViews' => 1));
		$conditions =  array('_id' => $entity['_id']);
		$result = User::update($updateData, $conditions, array('atomic' => false));
		return $result;

	}


	//Overrides save so we can do some stuff before the data is commited to the database. 
	public function save($entity, $data = null, array $options = array()) 
	{
		//If the new password field is empty, or this is a new user 
		if(!empty($entity->newpass) || !$entity->exists())
		{
			//Generate Salt for the user.
			$salt = Password::salt('bf', 6);

			//Hash their password.
			$data['password'] = String::hashPassword($entity->newpass, $salt);
			$data['salt'] = $salt;
			unset($entity->newpass);
		}
		//If the entity doesn't exist or if the password password has been modified
		return parent::save($entity, $data, $options);
	}
}

?>