From 2389d66da849798f8d4ec5f10e3b07c11da49185 Mon Sep 17 00:00:00 2001 From: Michael Francis Date: Sat, 28 May 2011 13:28:16 -0400 Subject: Initial Commit --- models/Anime.php | 8 ++ models/Entry.php | 9 ++ models/Photo.php | 62 +++++++++ models/Post.php | 137 +++++++++++++++++++ models/Profile.php | 17 +++ models/ProfilePic.php | 28 ++++ models/Topic.php | 13 ++ models/User.php | 347 +++++++++++++++++++++++++++++++++++++++++++++++++ models/confirmKey.php | 46 +++++++ models/contentList.php | 17 +++ models/untitled.php | 18 +++ 11 files changed, 702 insertions(+) create mode 100644 models/Anime.php create mode 100644 models/Entry.php create mode 100644 models/Photo.php create mode 100644 models/Post.php create mode 100644 models/Profile.php create mode 100644 models/ProfilePic.php create mode 100644 models/Topic.php create mode 100644 models/User.php create mode 100644 models/confirmKey.php create mode 100644 models/contentList.php create mode 100644 models/untitled.php (limited to 'models') diff --git a/models/Anime.php b/models/Anime.php new file mode 100644 index 0000000..ffa40eb --- /dev/null +++ b/models/Anime.php @@ -0,0 +1,8 @@ + '_id', 'source' => 'anime'); + +} \ No newline at end of file diff --git a/models/Entry.php b/models/Entry.php new file mode 100644 index 0000000..3b27393 --- /dev/null +++ b/models/Entry.php @@ -0,0 +1,9 @@ + 'fs.files'); + public $validates = array(); + + + /** + * Generate a cached version under webroot + * @param string $id The image id as in mongodb + * @param array $options Possible values are + * width + * height + * @return mixed + */ + public static function version($id, $options = array()) { + if (!$id) + return false; + // This is the same as Photo::first($id) when called from inside itself + $self = static::first($id); + return ($self) ? $self->generateVersion($options) : false; + } + + /** + * Generate a cached version under webroot + * @param Document $self The document from the db + * @param array $options Possible values are + * @return mixed + */ + public function generateVersion($self, $options = array()) { + // This is quite naive, it would fail at .jpeg for example. Be more elaborate on production code! + $type = substr($self->file->file['filename'], -3); + $path = LITHIUM_APP_PATH . "/webroot/image/{$self->_id}"; + $originalPath = $path . "." . $type; + + // Always create the original variant if it doesnt exist yet. It is needed for resize + if (!file_exists($originalPath)) + file_put_contents($originalPath, $self->file->getBytes()); + + if (isset($options['width']) && isset($options['height'])) { + $width = (int) $options['width']; + $height = (int) $options['height']; + + $path .= "_{$width}x{$height}.{$type}"; + + // This requires imagemagick and access to system calls. + // It is possible to use gd but it is a much worse alternative so please dont. + $convertCommand = "convert $originalPath -resize {$width}x{$height}\> $path"; + shell_exec($convertCommand); + // Return data of the resized version + return file_get_contents($path); + } + // If no width/height were set, just return data of the original + return $self->file->getBytes(); + } +} + +?> \ No newline at end of file diff --git a/models/Post.php b/models/Post.php new file mode 100644 index 0000000..653beb2 --- /dev/null +++ b/models/Post.php @@ -0,0 +1,137 @@ + '_id'); + +//Overrides save so we can do some stuff before the data is commited to the database. + +/* Post model $_Schema: + * _id => MongoID + * user_id -> mongoID of the user that posted it + * datetime -> mongodate of when the post was posted + * body -> the text of the post + * level -> access level required to see the post + * comments[] => list of comments + * type -> the type of post, IE picture text, chat etc + */ + + /** + * Parses an array of words to find mentions and topic tags then converts them + * @param Entitiy $entity Not used, but otherwise lithium will pass entity into words which makes the program blowup + * @param Mixed $input, either a string, or a spilt array of words (ie an exploded string) + * @return String a string with the topics and mentions converted + */ + public function parse($entity, $input) + { + $words; + if (is_array($input)) { + $words = $input; + } + else { + $words = explode(" ", $input); + } + + //Count the number of words + $count = count($words); + + + //For each word in the array + for ($i = 0; $i < $count; $i++) + { + //If the word begins with a '@' convert it to a mention + if ($words[$i][0] == '@') + { + $words[$i] = $this->convertToMention($words[$i]); + } + //Else if the word beings with a '#' Convert to topic link + else if ($words[$i][0] == '#') + { + $words[$i] = $this->convertToTopic($words[$i]); + } + } + return implode(" ", $words); + } + + //TODO: Some sort of security check to make sure + //That the user is ok with receiving message + + public function directMessage($entity, $to) + { + //Get the user the message is to + $user = User::find('first', array('conditions' => array('username' => $to))); + + //If find() returned a user, + if ($user) + { + //Add the post to their feed, + return $post->store($user); + } + //If the user wasn't found + return false; + } + + /** + * Converts a string with a topic tag(eg: #madoka) to clickable link to the topic eg #madoka + * @param String $input The string with topic tag + * @return String the string with href + */ + public function convertToTopic($input) + { + //Remove the # character from the beginning + $output = substr($input, 1); + + //Formats the string and returns it. + return "$input"; + } + + /** + * converts a mention (eg: @bob) to a clickable link to the user's profile eg #madoka + * @param String $input The string with topic tag + * @return String the string with href + */ + public function convertToMention($input) + { + //Remove the @ character from the beginning + $output = substr($input, 1); + + //Formats the string and returns it. + return "$input"; + } + + /** + * Stores the post to all the user's friends feed + * @param Post $entity The post to be stored + * @param Array $users an Array of users objects to store the post to + * @return boolean True if sucsessful, false otherwise + */ + //Store all can take a single param as well, therefore it should replace the store method once we're sure it works properly :TODO: + public function storeAll($entity, $users) + { + $ids; + foreach($users as $user) + { + $ids[] = $user->_id; + } + + $updateData = array('$push' => array('feed' => $entity['_id']->__toString())); + + $conditions = array('_id' => array('$in' => $ids)); + $result = User::update($updateData, $conditions, array('atomic' => false)); + + return $result; + } + + public function store($entity, $user) + { + $updateData = array('$push' => array('feed' => $entity['_id']->__toString())); + $conditions = array('_id' => $user['_id']); + $result = User::update($updateData, $conditions, array('atomic' => false)); + + return $result; + } +} + +?> \ No newline at end of file diff --git a/models/Profile.php b/models/Profile.php new file mode 100644 index 0000000..a85be6d --- /dev/null +++ b/models/Profile.php @@ -0,0 +1,17 @@ + array(array('date')) + ); +} + +?> \ No newline at end of file diff --git a/models/ProfilePic.php b/models/ProfilePic.php new file mode 100644 index 0000000..079f5b7 --- /dev/null +++ b/models/ProfilePic.php @@ -0,0 +1,28 @@ + 'fs.files'); + + //Overriding save to do the thumbnailing :) + public function save($entity, $data, array $options = array()) + { + //Create a new imagemagick object from the uploaded file + $im = new Imagick($data['file']); + + //Create a thumbnail of the file, then store it in the "thumbnail" object + $data['thumbnail'] = $im->thumbnailImage(200, null); + + //Pass the changes off to the original save method. + return parent::save($entity, $data, $options); + } +} + +?> \ No newline at end of file diff --git a/models/Topic.php b/models/Topic.php new file mode 100644 index 0000000..6f12aa2 --- /dev/null +++ b/models/Topic.php @@ -0,0 +1,13 @@ + '_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 = String::genSalt('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); + } +} + +?> \ No newline at end of file diff --git a/models/confirmKey.php b/models/confirmKey.php new file mode 100644 index 0000000..6a941ad --- /dev/null +++ b/models/confirmKey.php @@ -0,0 +1,46 @@ + '_id'); + //array('isValidKey', 'message' => 'Key does not exist'); + + public static function __init() + { + //Make sure the class we extend inits. + parent::__init(); + + //Checks if the key is valid (in the database); + Validator::add('isValidKey', function($key) { + return confirmKey::count(array('conditions' => compact('key'))) == 1; + }); + } + + //For now, this will remain, but eventually it should just filter the save + //Method since the confirmation key doesn't really need to be returned to the controller. + public function generate($email) + { + //Doesn't need to be ultra secure since they just need to click the generated link + return String::hash($email.$this->secret, array('type' => 'crc32')); + } + + /* + * Old Validates function + public function isValidKey($key) + { + //If they key is valid, it should be found in the database + //If there is 1 key that matches the input key, + return confirmKey::count(array('conditions' => compact('key'))) == 1; + } + */ +} +?> \ No newline at end of file diff --git a/models/contentList.php b/models/contentList.php new file mode 100644 index 0000000..017e261 --- /dev/null +++ b/models/contentList.php @@ -0,0 +1,17 @@ + array('' + $conditions = array('_id' => $user['_id']); + $result = User::update($updateData, $conditions, array('atomic' => false)); + } +} \ No newline at end of file diff --git a/models/untitled.php b/models/untitled.php new file mode 100644 index 0000000..46fd26c --- /dev/null +++ b/models/untitled.php @@ -0,0 +1,18 @@ + \ No newline at end of file -- cgit v1.2.3