├── app ├── .htaccess ├── views │ ├── inc │ │ ├── footer.php │ │ ├── header.php │ │ ├── navbar_profile.php │ │ └── navbar.php │ ├── pages │ │ ├── about.php │ │ └── index.php │ ├── tweets │ │ ├── loadUserTweets.php │ │ ├── loadAllTweets.php │ │ └── index.php │ └── users │ │ ├── login.php │ │ ├── search.php │ │ ├── signup.php │ │ ├── followers.php │ │ ├── following.php │ │ ├── profile.php │ │ └── editprofile.php ├── bootstrap.php ├── config │ └── config.php ├── controllers │ ├── Pages.php │ ├── FollowSystem.php │ ├── Tweets.php │ └── Users.php ├── helpers │ ├── Auth.php │ ├── functions.php │ └── Validation.php ├── libraries │ ├── Controller.php │ ├── Core.php │ └── Database.php └── models │ ├── User.php │ ├── FollowSys.php │ └── Tweet.php ├── .gitignore ├── public ├── index.php ├── img │ ├── Twitter-Screenshot-1.png │ ├── Twitter-Screenshot-2.png │ ├── Twitter-Screenshot-3.png │ └── Twitter-Screenshot-home.png ├── .htaccess ├── js │ ├── login.js │ ├── search.js │ ├── follow.js │ ├── signup.js │ ├── profile.js │ ├── editprofile.js │ ├── index.js │ └── jquery.js └── css │ └── styles.css ├── .htaccess ├── README.md └── twitter.sql /app/.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes -------------------------------------------------------------------------------- /app/views/inc/footer.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public/css/fontawesome/ 2 | public/node_modules/ 3 | public/img/profile -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteRule ^$ public/ [L] 4 | RewriteRule (.*) public/$1 [L] 5 | -------------------------------------------------------------------------------- /public/img/Twitter-Screenshot-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techreagan/twitter-clone-php/HEAD/public/img/Twitter-Screenshot-home.png -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Options -Multiviews 3 | RewriteEngine On 4 | RewriteBase /twitter/public 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteRule ^(.+)$ index.php?url=$1 [QSA,L] 8 | 9 | -------------------------------------------------------------------------------- /app/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'Twitter Clone' 13 | ]; 14 | 15 | $this->view('pages/index', $data); 16 | } 17 | 18 | public function about() { 19 | $data = [ 20 | 'title' => 'About Us' 21 | ]; 22 | 23 | $this->view('pages/about', $data); 24 | } 25 | } -------------------------------------------------------------------------------- /app/views/pages/about.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |

Twitter is

8 |

what’s happening in the world and what people are talking about right now.

9 |
10 |
11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /app/helpers/Auth.php: -------------------------------------------------------------------------------- 1 | id; 8 | $_SESSION['last_login'] = time(); 9 | 10 | return true; 11 | } 12 | 13 | public static function logOut() { 14 | unset($_SESSION['user_id']); 15 | unset($_SESSION['last_login']); 16 | session_destroy(); 17 | 18 | return true; 19 | } 20 | 21 | public static function isLoggedIn() { 22 | return isset($_SESSION['user_id']); 23 | } 24 | 25 | public static function requireLogIn() { 26 | if(!self::isLoggedIn()) { 27 | redirect('/'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/libraries/Controller.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 | 7 |

See what’s happening in the world right now

8 |

Join Twitter today.

9 | 10 | 11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /app/views/inc/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <?php echo SITENAME; ?> 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/controllers/FollowSystem.php: -------------------------------------------------------------------------------- 1 | followSystem = $this->model('FollowSys'); 9 | } 10 | 11 | public function follow() { 12 | if(is_ajax_request()) { 13 | $follower_id = trim($_POST['followerId']); 14 | $following_id = trim($_POST['followingId']); 15 | 16 | if(!$this->followSystem->findFollow($follower_id, $following_id)) { 17 | $follower = $this->followSystem->follow($follower_id, $following_id); 18 | if($follower) { 19 | echo 'follow'; 20 | } 21 | } else { 22 | $unfollow = $this->followSystem->unfollow($follower_id, $following_id); 23 | if($unfollow) { 24 | echo 'unfollow'; 25 | } 26 | } 27 | 28 | } else { 29 | redirect('tweets'); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/views/inc/navbar_profile.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/js/login.js: -------------------------------------------------------------------------------- 1 | const username = document.querySelector('#username'); 2 | const password = document.querySelector('#password'); 3 | const form = document.querySelector('form'); 4 | 5 | username.addEventListener('keyup', validateUsername); 6 | password.addEventListener('keyup', validatePassword); 7 | 8 | function validateUsername(e) { 9 | const text = username.value; 10 | 11 | if(text == '') { 12 | username.classList.add('is-invalid'); 13 | return true; 14 | } else { 15 | username.classList.remove('is-invalid'); 16 | return false; 17 | } 18 | } 19 | 20 | function validatePassword(e) { 21 | const text = password.value; 22 | const re = /^[\w]{6,}$/i; 23 | const uppercase = /[A-Z]/; 24 | const number = /[0-9]/; 25 | 26 | if(text == '') { 27 | password.classList.add('is-invalid'); 28 | return true; 29 | } else { 30 | password.classList.remove('is-invalid'); 31 | return false; 32 | } 33 | } 34 | 35 | form.addEventListener('submit', (e) => { 36 | 37 | if( validateUsername() || validatePassword()) { 38 | validateUsername(); 39 | validatePassword(); 40 | } else { 41 | return true; 42 | } 43 | e.preventDefault(); 44 | }); 45 | 46 | document.addEventListener('DOMContentLoaded', function() { 47 | var sidenav = document.querySelectorAll('.sidenav'); 48 | var sideNavInstances = M.Sidenav.init(sidenav); 49 | }); -------------------------------------------------------------------------------- /app/views/tweets/loadUserTweets.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

No tweets from you yet.

4 | 5 | 6 |
7 |
8 | 9 |
10 |
11 | 15 |
16 | getTotalLikes($tweet->tweet_id); ?> 17 | user_id == $_SESSION['user_id']): ?> 18 | 19 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /public/js/search.js: -------------------------------------------------------------------------------- 1 | const url = 'http://localhost:8888/twitter/'; 2 | 3 | const followBtn = document.querySelectorAll('#followBtn'); 4 | const collection = document.querySelector('.collection'); 5 | 6 | followBtn.forEach((btn) => { 7 | if(btn.classList.contains('following')) { 8 | btn.innerHTML = 'Following'; 9 | } 10 | }) 11 | if(document.contains(collection)) { 12 | collection.addEventListener('click', (e) => { 13 | if(e.target.classList.contains('follow-btn')) { 14 | const followerId = e.target.getAttribute('data-follower-id'); 15 | const followingId = e.target.getAttribute('data-following-id'); 16 | fetch(url + 'followSystem/follow', { 17 | method: 'POST', 18 | headers: { 19 | 'content-type': 'application/x-www-form-urlencoded', 20 | 'X-Requested-With': 'XMLHttpRequest' 21 | }, 22 | body: `followerId=${followerId}&followingId=${followingId}` 23 | }) 24 | .then(res => res.text()) 25 | .then(data => { 26 | if(data == 'follow') { 27 | e.target.classList.add('following'); 28 | e.target.innerHTML = 'Following'; 29 | } else { 30 | e.target.classList.remove('following'); 31 | e.target.innerHTML = 'Follow'; 32 | } 33 | }) 34 | .catch(err => console.log(err)); 35 | e.preventDefault(); 36 | } 37 | }); 38 | } 39 | 40 | document.addEventListener('DOMContentLoaded', function() { 41 | var elems = document.querySelectorAll('.sidenav'); 42 | var instances = M.Sidenav.init(elems); 43 | }); -------------------------------------------------------------------------------- /public/js/follow.js: -------------------------------------------------------------------------------- 1 | let followForms = document.querySelectorAll('.followForm'); 2 | const followBtn = document.querySelectorAll('.follow-btn'); 3 | const username = document.querySelector('#username'); 4 | 5 | let url = 'http://localhost:8888/twitter/'; 6 | 7 | followBtn.forEach((btn) => { 8 | if(btn.classList.contains('following')) { 9 | btn.innerHTML = 'Following'; 10 | } 11 | }); 12 | 13 | followForms.forEach((followForm) => { 14 | followForm.addEventListener('click', (e) => { 15 | if(e.target.classList.contains('follow-btn')) { 16 | console.log('hello'); 17 | const followerId = e.target.getAttribute('data-follower-id'); 18 | const followingId = e.target.getAttribute('data-following-id'); 19 | fetch(url + 'followSystem/follow', { 20 | method: 'POST', 21 | headers: { 22 | 'content-type': 'application/x-www-form-urlencoded', 23 | 'X-Requested-With': 'XMLHttpRequest' 24 | }, 25 | body: `followerId=${followerId}&followingId=${followingId}` 26 | }) 27 | .then(res => res.text()) 28 | .then(data => { 29 | if(data == 'follow') { 30 | e.target.classList.add('following'); 31 | e.target.innerHTML = 'Following'; 32 | } else { 33 | e.target.classList.remove('following'); 34 | e.target.innerHTML = 'Follow'; 35 | } 36 | }) 37 | .catch(err => console.log(err)); 38 | e.preventDefault(); 39 | } 40 | 41 | }); 42 | }); 43 | 44 | document.addEventListener('DOMContentLoaded', function() { 45 | var elems = document.querySelectorAll('.sidenav'); 46 | var instances = M.Sidenav.init(elems); 47 | }); 48 | -------------------------------------------------------------------------------- /app/libraries/Core.php: -------------------------------------------------------------------------------- 1 | getUrl()); 14 | 15 | $url = $this->getUrl(); 16 | 17 | // Look in controllers for first value 18 | if(file_exists('../app/controllers/' . ucwords($url[0]) . '.php')) { 19 | // If exists, set as controller 20 | $this->currentController = ucwords($url[0]); 21 | // Unset 0 Index 22 | unset($url[0]); 23 | } 24 | 25 | // Require the controller 26 | require_once '../app/controllers/' . $this->currentController . '.php'; 27 | 28 | // Instantiate controller class 29 | $this->currentController = new $this->currentController; 30 | 31 | // Check for second part of url 32 | if(isset($url[1])) { 33 | // Check to see if method exists in controller 34 | if(method_exists($this->currentController, $url[1])) { 35 | $this->currentMethod = $url[1]; 36 | // Unset 1 index 37 | unset($url[1]); 38 | } 39 | } 40 | 41 | // Get params 42 | $this->params = $url ? array_values($url) : []; 43 | 44 | // Call a callback with array of params 45 | call_user_func_array([$this->currentController, $this->currentMethod], $this->params); 46 | } 47 | 48 | public function getUrl() { 49 | if(isset($_GET['url'])) { 50 | $url = rtrim($_GET['url'], '/'); 51 | $url = filter_var($url, FILTER_SANITIZE_URL); 52 | $url = explode('/', $url); 53 | return $url; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/views/tweets/loadAllTweets.php: -------------------------------------------------------------------------------- 1 | 7 | 8 |

Welcome to twitter, no tweets yet.

9 | 14 |
15 |
16 | 17 |
18 |
19 | 23 |
24 | getTotalLikes($tweet->id); ?> 25 | user_id == $_SESSION['user_id']): ?> 26 | 27 | 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitter Clone 2 | 3 | A twitter clone with basic functionality like tweet, like tweet, following system etc. 4 | 5 | ## Development 6 | Built with vanilla javascript, myql and [Boss PHP mini Framework](https://github.com/techreagan/Boss) 7 | 8 | ## Screenshot 9 | ![Screenshot](public/img/Twitter-Screenshot-home.png) 10 | ![Screenshot](public/img/Twitter-Screenshot-1.png) 11 | ![Screenshot](public/img/Twitter-Screenshot-2.png) 12 | ![Screenshot](public/img/Twitter-Screenshot-3.png) 13 | 14 | ## Installation 15 | Import the twitter.sql to your mysql database 16 | 17 | ### Fontawesome 18 | Download fontawesome and include it in public/css/ the complete fontawesome folder 19 | 20 | ### Configuration File 21 | 22 | Modify the app/config/config.php file according to your database credentials and also your root url 23 | 24 | ``` PHP 25 | //Database Configuration 26 | define('DB_HOST', '_YOUR_HOST'); 27 | define('DB_USER', '_YOUR_DATABSE_USERNAME'); 28 | define('DB_PASS', '_YOUR_DATABASE_PASSWORD'); 29 | define('DB_NAME', '_YOUR DATABASE_NAME'); 30 | // App Root 31 | define('APPROOT', dirname(dirname(__FILE__))); 32 | // URL Root 33 | define('URLROOT', '_YOUR_URL_'); 34 | ``` 35 | 36 | ### Htaccess file 37 | 38 | Modify the .htaccess file inside the public folder to match the name of your installation folder, Modify only the RewriteBase. 39 | 40 | ``` 41 | 42 | Options -Multiviews 43 | RewriteEngine On 44 | RewriteBase /twitter/public 45 | RewriteCond %{REQUEST_FILENAME} !-d 46 | RewriteCond %{REQUEST_FILENAME} !-f 47 | RewriteRule ^(.+)$ index.php?url=$1 [QSA,L] 48 | 49 | ``` 50 | 51 | ### Javascript files 52 | Modify the javascript files in public/js. Change the url to your url. Files to modify, index.js, follow.js, search.js and profile.js 53 | ```Javascript 54 | let url = 'http://localhost:8888/twitter/'; 55 | ``` -------------------------------------------------------------------------------- /app/libraries/Database.php: -------------------------------------------------------------------------------- 1 | host . ';dbname=' . $this->dbname; 22 | 23 | $options = [ 24 | PDO::ATTR_PERSISTENT => true, 25 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 26 | ]; 27 | 28 | // Create PDO instance 29 | try { 30 | $this->dbh = new PDO($dsn, $this->user, $this->pass, $options); 31 | } catch(PDOException $e) { 32 | $this->error = $e->getMessage(); 33 | echo $this->error; 34 | } 35 | } 36 | 37 | // Prepare statement with query 38 | public function query($sql) { 39 | $this->stmt = $this->dbh->prepare($sql); 40 | } 41 | 42 | // Bind values 43 | public function bind($param, $value, $type = null) { 44 | if(is_null($type)) { 45 | switch(true) { 46 | case is_int($value): 47 | $type = PDO::PARAM_INT; 48 | break; 49 | case is_bool($value): 50 | $type = PDO::PARAM_BOOL; 51 | break; 52 | case is_null($value): 53 | $type = PDO::PARAM_NULL; 54 | break; 55 | default: 56 | $type = PDO::PARAM_STR; 57 | } 58 | } 59 | 60 | $this->stmt->bindValue($param, $value, $type); 61 | } 62 | 63 | // Execute the prepared statement 64 | public function execute() { 65 | return $this->stmt->execute(); 66 | } 67 | 68 | // Get result set as array of objects 69 | public function resultSet() { 70 | $this->execute(); 71 | return $this->stmt->fetchAll(PDO::FETCH_OBJ); 72 | } 73 | 74 | // Get single recored as object 75 | public function single() { 76 | $this->execute(); 77 | return $this->stmt->fetch(PDO::FETCH_OBJ); 78 | } 79 | 80 | // Get row count 81 | public function rowCount() { 82 | return $this->stmt->rowCount(); 83 | } 84 | } -------------------------------------------------------------------------------- /app/views/users/login.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |

Log in to Twitter

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 |

Need a twitter? Sign Up

40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/helpers/functions.php: -------------------------------------------------------------------------------- 1 | '. $_SESSION[$name] .''; 67 | unset($_SESSION[$name]); 68 | unset($_SESSION[$name . '_class']); 69 | } 70 | } 71 | } 72 | 73 | function get_random_string() { 74 | return bin2hex(openssl_random_pseudo_bytes(32)); 75 | } 76 | 77 | function time_elapsed_string($datetime, $full = false) { 78 | $now = new DateTime; 79 | $ago = new DateTime($datetime); 80 | $diff = $now->diff($ago); 81 | 82 | $diff->w = floor($diff->d / 7); 83 | $diff->d -= $diff->w * 7; 84 | 85 | $string = array( 86 | 'y' => 'year', 87 | 'm' => 'month', 88 | 'w' => 'week', 89 | 'd' => 'day', 90 | 'h' => 'hour', 91 | 'i' => 'minute', 92 | 's' => 'second', 93 | ); 94 | foreach ($string as $k => &$v) { 95 | if ($diff->$k) { 96 | $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : ''); 97 | } else { 98 | unset($string[$k]); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /twitter.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 4.8.3 3 | -- https://www.phpmyadmin.net/ 4 | -- 5 | -- Host: localhost:8889 6 | -- Generation Time: Nov 11, 2018 at 10:47 AM 7 | -- Server version: 5.7.23 8 | -- PHP Version: 7.2.8 9 | 10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | -- 14 | -- Database: `twitter` 15 | -- 16 | 17 | -- -------------------------------------------------------- 18 | 19 | -- 20 | -- Table structure for table `following_sys` 21 | -- 22 | 23 | CREATE TABLE `following_sys` ( 24 | `id` int(255) NOT NULL, 25 | `follower_id` int(255) NOT NULL, 26 | `following_id` int(255) NOT NULL, 27 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 28 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 29 | 30 | -- -------------------------------------------------------- 31 | 32 | -- 33 | -- Table structure for table `likes` 34 | -- 35 | 36 | CREATE TABLE `likes` ( 37 | `id` int(255) NOT NULL, 38 | `user_id` int(255) NOT NULL, 39 | `tweet_id` int(255) NOT NULL, 40 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 41 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 42 | 43 | -- -------------------------------------------------------- 44 | 45 | -- 46 | -- Table structure for table `tweets` 47 | -- 48 | 49 | CREATE TABLE `tweets` ( 50 | `id` int(255) NOT NULL, 51 | `user_id` int(255) NOT NULL, 52 | `body` text NOT NULL, 53 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 54 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 55 | 56 | -- 57 | -- Dumping data for table `tweets` 58 | -- 59 | 60 | -- -------------------------------------------------------- 61 | 62 | -- 63 | -- Table structure for table `users` 64 | -- 65 | 66 | CREATE TABLE `users` ( 67 | `id` int(255) NOT NULL, 68 | `firstname` varchar(255) NOT NULL, 69 | `lastname` varchar(255) NOT NULL, 70 | `email` varchar(255) NOT NULL, 71 | `username` varchar(255) NOT NULL, 72 | `profileimg` varchar(255) DEFAULT NULL, 73 | `dob` varchar(255) NOT NULL, 74 | `bio` text, 75 | `password` varchar(255) NOT NULL, 76 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 77 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 78 | 79 | -- 80 | -- Indexes for dumped tables 81 | -- 82 | 83 | -- 84 | -- Indexes for table `following_sys` 85 | -- 86 | ALTER TABLE `following_sys` 87 | ADD PRIMARY KEY (`id`); 88 | 89 | -- 90 | -- Indexes for table `likes` 91 | -- 92 | ALTER TABLE `likes` 93 | ADD PRIMARY KEY (`id`); 94 | 95 | -- 96 | -- Indexes for table `tweets` 97 | -- 98 | ALTER TABLE `tweets` 99 | ADD PRIMARY KEY (`id`); 100 | 101 | -- 102 | -- Indexes for table `users` 103 | -- 104 | ALTER TABLE `users` 105 | ADD PRIMARY KEY (`id`); 106 | 107 | -- 108 | -- AUTO_INCREMENT for dumped tables 109 | -- 110 | 111 | -- 112 | -- AUTO_INCREMENT for table `following_sys` 113 | -- 114 | ALTER TABLE `following_sys` 115 | MODIFY `id` int(255) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=11; 116 | 117 | -- 118 | -- AUTO_INCREMENT for table `likes` 119 | -- 120 | ALTER TABLE `likes` 121 | MODIFY `id` int(255) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; 122 | 123 | -- 124 | -- AUTO_INCREMENT for table `tweets` 125 | -- 126 | ALTER TABLE `tweets` 127 | MODIFY `id` int(255) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12; 128 | 129 | -- 130 | -- AUTO_INCREMENT for table `users` 131 | -- 132 | ALTER TABLE `users` 133 | MODIFY `id` int(255) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6; 134 | -------------------------------------------------------------------------------- /app/views/inc/navbar.php: -------------------------------------------------------------------------------- 1 | 37 | 38 | -------------------------------------------------------------------------------- /app/controllers/Tweets.php: -------------------------------------------------------------------------------- 1 | userModel = $this->model('User'); 10 | $this->tweetModel = $this->model('Tweet'); 11 | $this->followModel = $this->model('FollowSys'); 12 | } 13 | 14 | public function index() { 15 | $data = [ 16 | 'user' => $this->userModel->getUserById(), 17 | 'users' => $this->userModel->getAllUser(4), 18 | 'getAllUsers' => $this->userModel->getAllUser(), 19 | 'total-tweets' => $this->tweetModel->getTotalTweets(), 20 | 'total-following' => $this->followModel->getTotalFollowing(), 21 | 'total-follower' => $this->followModel->getTotalFollower(), 22 | 'follow' => $this->followModel, 23 | ]; 24 | 25 | $this->view('tweets/index', $data); 26 | } 27 | 28 | public function postTweet() { 29 | 30 | if(is_ajax_request()) { 31 | $post = $this->tweetModel->postTweet($_POST['tweet']); 32 | if($post) { 33 | echo 'yes'; 34 | } else { 35 | echo 'no'; 36 | } 37 | } else { 38 | redirect('tweets'); 39 | } 40 | } 41 | 42 | public function loadAllTweets() { 43 | if($this->tweetModel->getAllTweets()) { 44 | $data = [ 45 | 'user' => $this->userModel->getUserById(), 46 | 'tweets' => $this->tweetModel->getAllTweets(), 47 | 'user_tweets' => '', 48 | 'likes' => $this->tweetModel 49 | ]; 50 | } else { 51 | $data = [ 52 | 'user' => $this->userModel->getUserById(), 53 | 'tweets' => '', 54 | 'user_tweets' => $this->tweetModel->getAllTweetsByUserSession(), 55 | 'likes' => $this->tweetModel 56 | ]; 57 | } 58 | 59 | $this->view('tweets/loadAllTweets', $data); 60 | } 61 | 62 | public function loadUserTweets($username) { 63 | 64 | $data = [ 65 | 'user' => $this->userModel->getUserById(), 66 | 'tweets' => $this->tweetModel->getAllTweetsByUserName($username), 67 | 'likes' => $this->tweetModel 68 | ]; 69 | 70 | $this->view('tweets/loadUserTweets', $data); 71 | } 72 | 73 | public function likeTweet() { 74 | if(is_ajax_request()) { 75 | $user_id = trim($_POST['userId']); 76 | $tweet_id = trim($_POST['tweetId']); 77 | $results = []; 78 | 79 | if(!$this->tweetModel->islike($user_id, $tweet_id)) { 80 | $like = $this->tweetModel->like($user_id, $tweet_id); 81 | $likeNumber = $this->tweetModel->getTotalLikes($tweet_id); 82 | 83 | if($like) { 84 | $results = [ 85 | 'like' => 'yes', 86 | 'like_number' => $likeNumber 87 | ]; 88 | } 89 | } else { 90 | $unlike = $this->tweetModel->unlike($user_id, $tweet_id); 91 | $likeNumber = $this->tweetModel->getTotalLikes($tweet_id); 92 | 93 | if($unlike) { 94 | $results = [ 95 | 'like' => 'no', 96 | 'like_number' => $likeNumber 97 | ]; 98 | } 99 | } 100 | 101 | 102 | echo json_encode($results); 103 | 104 | } else { 105 | // echo 'hello'; 106 | redirect('tweets'); 107 | } 108 | } 109 | 110 | public function deleteTweet() { 111 | if(is_ajax_request()) { 112 | $tweet_id = trim($_POST['tweetId']); 113 | // echo $tweet_id; 114 | $delete = $this->tweetModel->deleteTweet($tweet_id); 115 | 116 | if($delete) { 117 | echo 'deleted'; 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/views/users/search.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 13 |
14 |
15 | 21 |
22 | Tweets 23 | Following 24 | Followers 25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 | 34 |
35 | 36 |

Search for people on twitter.

37 | 38 |

User not found.

39 | 40 | 54 | 55 |
56 | 57 |
58 |
59 |
60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/models/User.php: -------------------------------------------------------------------------------- 1 | db = new Database; 6 | } 7 | 8 | public function signup($data) { 9 | $this->db->query('INSERT INTO users(firstname, lastname, email, username, dob, password) VALUES( 10 | :firstname, :lastname, :email, :username, :dob, :password)'); 11 | $this->db->bind('firstname', $data['firstName']); 12 | $this->db->bind('lastname', $data['lastName']); 13 | $this->db->bind('email', $data['email']); 14 | $this->db->bind('username', $data['username']); 15 | $this->db->bind('dob', $data['dob']); 16 | $this->db->bind('password', $data['password']); 17 | 18 | if($this->db->execute()) { 19 | return $this->getUserByUserName($data['username']); 20 | } else { 21 | return false; 22 | } 23 | } 24 | 25 | public function login($data) { 26 | $this->db->query('SELECT id, username, email, password FROM users WHERE username = :value or email = :value'); 27 | $this->db->bind('value', $data['username']); 28 | $user = $this->db->single(); 29 | 30 | if(password_verify($data['password'], $user->password)) { 31 | return $user; 32 | } else { 33 | return false; 34 | } 35 | } 36 | 37 | public function searchForUser($user) { 38 | $this->db->query('SELECT id, firstname, lastname, username FROM users 39 | WHERE firstname LIKE :user OR lastname LIKE :user OR username LIKE :user 40 | '); 41 | $this->db->bind('user', '%' . $user . '%'); 42 | return $this->db->resultSet(); 43 | } 44 | 45 | public function findEmailOrUsername($value) { 46 | $this->db->query('SELECT username, email FROM users WHERE username = :value or email = :value'); 47 | $this->db->bind('value', $value); 48 | $user = $this->db->single(); 49 | 50 | if(empty($this->db->rowCount())) { 51 | return true; 52 | } else { 53 | return false; 54 | } 55 | } 56 | 57 | public function getUserById() { 58 | $this->db->query('SELECT * FROM users WHERE id = :id'); 59 | $this->db->bind('id', $_SESSION['user_id']); 60 | $user = $this->db->single(); 61 | 62 | if($user) { 63 | return $user; 64 | } else { 65 | return false; 66 | } 67 | } 68 | 69 | public function getUserByUserName($username) { 70 | $this->db->query('SELECT * FROM users WHERE username = :username'); 71 | $this->db->bind('username', $username); 72 | $user = $this->db->single(); 73 | 74 | if($user) { 75 | return $user; 76 | } else { 77 | return false; 78 | } 79 | } 80 | 81 | public function getAllUser($number = 0) { 82 | if($number == 0) { 83 | $this->db->query('SELECT id, firstname, lastname, username FROM users WHERE id != :user_id'); 84 | } else { 85 | $this->db->query('SELECT id, firstname, lastname, username FROM users WHERE id != :user_id LIMIT :number'); 86 | $this->db->bind(':number', $number); 87 | } 88 | $this->db->bind('user_id', $_SESSION['user_id']); 89 | $users = $this->db->resultSet(); 90 | 91 | return $users; 92 | } 93 | 94 | public function updateInfo($info) { 95 | $query = 'UPDATE users SET '; 96 | $count = 0; 97 | foreach($info as $key => $value) { 98 | $count++; 99 | $query .= $key . ' = :' . $key . ($count == count($info) ? ' ' : ', '); 100 | } 101 | $query .= 'WHERE id = :id'; 102 | 103 | $this->db->query($query); 104 | foreach($info as $key => $value) { 105 | $this->db->bind($key, $value); 106 | } 107 | $this->db->bind('id', $_SESSION['user_id']); 108 | if($this->db->execute()) { 109 | return true; 110 | } else { 111 | return false; 112 | } 113 | } 114 | 115 | public function updatePassword($password) { 116 | $password = password_hash($password, PASSWORD_DEFAULT); 117 | $this->db->query('UPDATE users SET password = :password WHERE id = :id LIMIT 1'); 118 | $this->db->bind('password', $password); 119 | $this->db->bind('id', $_SESSION['user_id']); 120 | 121 | if($this->db->execute()) { 122 | return true; 123 | } else { 124 | return false; 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /public/js/signup.js: -------------------------------------------------------------------------------- 1 | const firstName = document.querySelector('#firstName'); 2 | const lastName = document.querySelector('#lastName'); 3 | const email = document.querySelector('#email'); 4 | const username = document.querySelector('#username'); 5 | const dob = document.querySelector('#dob'); 6 | const password = document.querySelector('#password'); 7 | const confirmPassword = document.querySelector('#confirmPassword'); 8 | const form = document.querySelector('#form'); 9 | 10 | firstName.addEventListener('keyup', validateFirstName); 11 | lastName.addEventListener('keyup', validateLastName); 12 | email.addEventListener('keyup', validateEmail); 13 | username.addEventListener('keyup', validateUsername); 14 | dob.addEventListener('keyup', validateDob); 15 | password.addEventListener('keyup', validatePassword); 16 | confirmPassword.addEventListener('keyup', validateConfirmPassword); 17 | 18 | function validateFirstName() { 19 | validateNames(firstName); 20 | } 21 | 22 | function validateLastName() { 23 | validateNames(lastName); 24 | } 25 | 26 | function validateNames(elem) { 27 | const text = elem.value; 28 | const re = /^[a-z]{3,15}$/i; 29 | 30 | if(!re.test(text) ) { 31 | elem.classList.add('is-invalid'); 32 | return true; 33 | } else { 34 | elem.classList.remove('is-invalid'); 35 | return false; 36 | } 37 | } 38 | 39 | function validateEmail(e) { 40 | const text = email.value; 41 | const re = /^[a-z]([\w\_\-\.])+@([\w\.])+\.(\w){2,5}$/i; 42 | 43 | if(!re.test(text)) { 44 | email.classList.add('is-invalid'); 45 | return true; 46 | } else { 47 | email.classList.remove('is-invalid'); 48 | return false; 49 | } 50 | } 51 | 52 | function validateUsername(e) { 53 | const text = username.value; 54 | const re = /^([\w\_]){3,10}$/i; 55 | 56 | if(!re.test(text)) { 57 | username.classList.add('is-invalid'); 58 | return true; 59 | } else { 60 | username.classList.remove('is-invalid'); 61 | return false; 62 | } 63 | } 64 | 65 | function validateDob(e) { 66 | const text = dob.value; 67 | const re = /^([\w\s])+\,(\s)([0-9]){4}$/i; 68 | 69 | if(!re.test(text)) { 70 | dob.classList.add('is-invalid'); 71 | return true; 72 | } else { 73 | dob.classList.remove('is-invalid'); 74 | return false; 75 | } 76 | } 77 | 78 | function validatePassword(e) { 79 | const text = password.value; 80 | const re = /^[\w]{6,}$/i; 81 | const uppercase = /[A-Z]/; 82 | const number = /[0-9]/; 83 | 84 | if(!re.test(text) || (text.search(uppercase) == '-1') || (text.search(number) == '-1')) { 85 | password.classList.add('is-invalid'); 86 | return true; 87 | } else { 88 | password.classList.remove('is-invalid'); 89 | return false; 90 | } 91 | } 92 | 93 | function validateConfirmPassword(e) { 94 | const text = confirmPassword.value; 95 | 96 | if(text !== password.value) { 97 | confirmPassword.classList.add('is-invalid'); 98 | return true; 99 | } else { 100 | confirmPassword.classList.remove('is-invalid'); 101 | return false; 102 | } 103 | } 104 | 105 | form.addEventListener('submit', (e) => { 106 | 107 | if(validateFirstName() || validateLastName() || validateUsername() || validateEmail() || validateDob() || validatePassword() || validateConfirmPassword()) { 108 | validateFirstName(); 109 | validateLastName(); 110 | validateEmail(); 111 | validateUsername(); 112 | validateDob(); 113 | validatePassword(); 114 | validateConfirmPassword(); 115 | } else { 116 | return true; 117 | } 118 | e.preventDefault(); 119 | }); 120 | 121 | const date = new Date(); 122 | 123 | const monthsShort = 124 | [ 125 | 'Jan', 126 | 'Feb', 127 | 'Mar', 128 | 'Apr', 129 | 'May', 130 | 'Jun', 131 | 'Jul', 132 | 'Aug', 133 | 'Sep', 134 | 'Oct', 135 | 'Nov', 136 | 'Dec' 137 | ]; 138 | 139 | const todaysDate = date.getDate() + ' ' + monthsShort[date.getMonth()] + ', ' + date.getFullYear(); 140 | const currentDate = new Date(todaysDate); 141 | document.addEventListener('DOMContentLoaded', function() { 142 | var elems = document.querySelector('.datepicker'); 143 | var instances = M.Datepicker.init(elems, {maxDate: currentDate}); 144 | dob.addEventListener('focus', () => { 145 | instances.open(); 146 | }) 147 | }); 148 | -------------------------------------------------------------------------------- /app/helpers/Validation.php: -------------------------------------------------------------------------------- 1 | $min; 30 | } 31 | 32 | /* hasLengthLessThan('abcd', 5) 33 | * validate string length 34 | * spaces count towards length 35 | * use trim() if spaces should not count 36 | */ 37 | public static function hasLengthLessThan($value, $max) { 38 | $length = strlen($value); 39 | return $length < $max; 40 | } 41 | 42 | /* hasLengthExactly('abcd', 4) 43 | * validate string length 44 | * spaces count towards length 45 | * use trim() if spaces should not count 46 | */ 47 | public static function hasLengthExactly($value, $exact) { 48 | $length = strlen($value); 49 | return $length == $exact; 50 | } 51 | 52 | /* hasLength('abcd', ['min' => 3, 'max' => 5]) 53 | * validate string length 54 | * combines functions_greaterThan, _lessThan, _exactly 55 | * spaces count towards length 56 | * use trim() if spaces should not count 57 | */ 58 | public static function hasLength($value, $options) { 59 | if(isset($options['min']) && !self::hasLengthGreaterThan($value, $options['min'] - 1)) { 60 | return false; 61 | } elseif(isset($options['max']) && !self::hasLengthLessThan($value, $options['max'] + 1)) { 62 | return false; 63 | } elseif(isset($options['exact']) && !self::hasLengthExactly($value, $options['exact'])) { 64 | return false; 65 | } else { 66 | return true; 67 | } 68 | } 69 | 70 | /* has_inclusion_of( 5, [1,3,5,7,9] ) 71 | * validate inclusion in a set 72 | */ 73 | public static function hasInclusionOf($value, $set) { 74 | return in_array($value, $set); 75 | } 76 | 77 | /* hasExclusionOf( 5, [1,3,5,7,9] ) 78 | * validate exclusion from a set 79 | */ 80 | public static function hasExclusionOf($value, $set) { 81 | return !in_array($value, $set); 82 | } 83 | 84 | /* hasString('nobody@nowhere.com', '.com') 85 | * validate inclusion of character(s) 86 | * strpos returns string start position or false 87 | * uses !== to prevent position 0 from being considered false 88 | * strpos is faster than preg_match() 89 | */ 90 | public static function hasString($value, $required_string) { 91 | return strpos($value, $required_string) !== false; 92 | } 93 | 94 | /* has_valid_email_format('nobody@nowhere.com') 95 | * validate correct format for email addresses 96 | * format: [chars]@[chars].[2+ letters] 97 | * preg_match is helpful, uses a regular expression 98 | returns 1 for a match, 0 for no match 99 | http://php.net/manual/en/function.preg-match.php 100 | */ 101 | public static function hasValidEmailFormat($value) { 102 | $email_regex = '/\A[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\Z/i'; 103 | return preg_match($email_regex, $value) === 1; 104 | } 105 | 106 | /* validataEmail('abc@abc.com') 107 | * validate email using the filter_var functions 108 | */ 109 | public static function validateEmail($value) { 110 | return filter_var($value, FILTER_VALIDATE_EMAIL); 111 | } 112 | 113 | /* hasUppercase('abC') 114 | */ 115 | public static function hasUppercase($value) { 116 | return !preg_match('/[A-Z]/', $value); 117 | } 118 | 119 | /* hasNumber('abc1') 120 | */ 121 | public static function hasNumber($value) { 122 | return !preg_match('/[0-9]/', $value); 123 | } 124 | 125 | public static function hasSymbolsAndNumbers($value) { 126 | return !preg_match('/\W/', $value); 127 | } 128 | } -------------------------------------------------------------------------------- /app/models/FollowSys.php: -------------------------------------------------------------------------------- 1 | db = new Database; 6 | } 7 | 8 | public function follow($follower_id, $following_id) { 9 | $this->db->query('INSERT INTO following_sys(follower_id, following_id) VALUES(:follower_id, :following_id)'); 10 | $this->db->bind('follower_id', $follower_id); 11 | $this->db->bind('following_id', $following_id); 12 | 13 | if($this->db->execute()) { 14 | return true; 15 | } else { 16 | return false; 17 | } 18 | } 19 | 20 | public function unfollow($follower_id, $following_id) { 21 | $this->db->query('DELETE FROM following_sys WHERE follower_id = :follower_id AND following_id = :following_id LIMIT 1'); 22 | $this->db->bind('follower_id', $follower_id); 23 | $this->db->bind('following_id', $following_id); 24 | 25 | if($this->db->execute()) { 26 | return true; 27 | } else { 28 | return false; 29 | } 30 | } 31 | 32 | public function findFollow($follower_id, $following_id) { 33 | $this->db->query('SELECT id FROM following_sys WHERE follower_id = :follower_id AND following_id = :following_id'); 34 | $this->db->bind('follower_id', $follower_id); 35 | $this->db->bind('following_id', $following_id); 36 | $user = $this->db->single(); 37 | if($user) { 38 | return true; 39 | } else { 40 | return false; 41 | } 42 | } 43 | 44 | public function isFollow($follower_id, $following_id) { 45 | $this->db->query('SELECT * FROM following_sys WHERE follower_id = :follower_id AND following_id = :following_id'); 46 | $this->db->bind('follower_id', $follower_id); 47 | $this->db->bind('following_id', $following_id); 48 | $user = $this->db->single(); 49 | if($user) { 50 | return 'following'; 51 | } else { 52 | // return 'unfollow'; 53 | } 54 | } 55 | 56 | public function getTotalFollowing() { 57 | $this->db->query('SELECT id FROM following_sys WHERE follower_id = :user_id'); 58 | $this->db->bind('user_id', $_SESSION['user_id']); 59 | $this->db->execute(); 60 | 61 | return $this->db->rowCount(); 62 | } 63 | 64 | public function getTotalFollower() { 65 | $this->db->query('SELECT id FROM following_sys WHERE following_id = :user_id'); 66 | $this->db->bind('user_id', $_SESSION['user_id']); 67 | $this->db->execute(); 68 | 69 | return $this->db->rowCount(); 70 | } 71 | 72 | public function getTotalFollowingByUserName($username) { 73 | $this->db->query('SELECT users.id FROM following_sys 74 | JOIN users on following_sys.follower_id = users.id 75 | WHERE users.username = :username 76 | '); 77 | $this->db->bind('username', $username); 78 | $this->db->execute(); 79 | 80 | return $this->db->rowCount(); 81 | } 82 | 83 | public function getTotalFollowersByUserName($username) { 84 | $this->db->query('SELECT users.id FROM following_sys 85 | JOIN users on following_sys.following_id = users.id 86 | WHERE users.username = :username 87 | '); 88 | $this->db->bind('username', $username); 89 | $this->db->execute(); 90 | 91 | return $this->db->rowCount(); 92 | } 93 | public function getFollowingByUserName($username) { 94 | $this->db->query('SELECT * FROM users 95 | JOIN following_sys on following_sys.follower_id = users.id 96 | WHERE users.username = :username 97 | ORDER BY following_sys.id DESC 98 | '); 99 | $this->db->bind('username', $username); 100 | return $this->db->resultSet(); 101 | } 102 | 103 | public function getFollow($id) { 104 | $this->db->query('SELECT * FROM users WHERE id = :id'); 105 | $this->db->bind('id', $id); 106 | return $this->db->resultSet(); 107 | } 108 | 109 | public function getFollowersByUserName($username) { 110 | $this->db->query('SELECT * FROM users 111 | JOIN following_sys on following_sys.following_id = users.id 112 | WHERE users.username = :username 113 | ORDER BY following_sys.id DESC 114 | '); 115 | $this->db->bind('username', $username); 116 | return $this->db->resultSet(); 117 | } 118 | } -------------------------------------------------------------------------------- /app/models/Tweet.php: -------------------------------------------------------------------------------- 1 | db = new Database; 6 | } 7 | 8 | public function getAllTweets() { 9 | $this->db->query('SELECT Distinct 10 | t.user_id, 11 | t.body, 12 | t.created_at, 13 | t.id, 14 | users.firstname, 15 | users.lastname, 16 | users.username 17 | FROM 18 | tweets as t 19 | JOIN following_sys ON t.user_id = following_sys.following_id OR t.user_id = :user_id 20 | join users on users.id = t.user_id 21 | where following_sys.follower_id = :user_id 22 | ORDER BY t.id DESC 23 | '); 24 | $this->db->bind('user_id', $_SESSION['user_id']); 25 | $tweets = $this->db->resultSet(); 26 | 27 | return $tweets; 28 | } 29 | 30 | public function getAllTweetsByUserSession() { 31 | $this->db->query('SELECT 32 | t.user_id, 33 | t.body, 34 | t.created_at, 35 | t.id, 36 | user.firstname, 37 | user.lastname, 38 | user.username 39 | FROM tweets as t 40 | JOIN users as user on user.id = t.user_id 41 | WHERE user.id = :user_id ORDER BY t.id DESC'); 42 | $this->db->bind('user_id', $_SESSION['user_id']); 43 | $tweets = $this->db->resultSet(); 44 | 45 | if($tweets) { 46 | return $tweets; 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | public function getAllTweetsByUserName($username) { 53 | $this->db->query('SELECT *, tweets.id as tweet_id FROM tweets 54 | JOIN users as user on user.id = tweets.user_id 55 | WHERE username = :username ORDER BY tweets.id DESC'); 56 | $this->db->bind('username', $username); 57 | $tweets = $this->db->resultSet(); 58 | 59 | if($tweets) { 60 | return $tweets; 61 | } else { 62 | return false; 63 | } 64 | } 65 | 66 | public function postTweet($body) { 67 | $this->db->query('INSERT INTO tweets(user_id, body) VALUES(:user_id, :body)'); 68 | $this->db->bind('user_id', $_SESSION['user_id']); 69 | $this->db->bind('body', $body); 70 | 71 | if($this->db->execute()) { 72 | return true; 73 | } else { 74 | return false; 75 | } 76 | } 77 | 78 | public function getTotalTweets() { 79 | $this->db->query('SELECT id FROM TWEETS WHERE user_id = :user_id'); 80 | $this->db->bind('user_id', $_SESSION['user_id']); 81 | $this->db->execute(); 82 | 83 | return $this->db->rowCount(); 84 | } 85 | 86 | public function getTotalTweetsByUserName($username) { 87 | $this->db->query('SELECT tweets.id FROM TWEETS 88 | JOIN users ON users.id = tweets.user_id 89 | WHERE users.username = :username 90 | '); 91 | $this->db->bind('username', $username); 92 | $this->db->execute(); 93 | 94 | return $this->db->rowCount(); 95 | } 96 | 97 | public function like($user_id, $tweet_id) { 98 | $this->db->query('INSERT INTO likes(user_id, tweet_id) VALUES(:user_id, :tweet_id)'); 99 | $this->db->bind('user_id', $user_id); 100 | $this->db->bind('tweet_id', $tweet_id); 101 | 102 | if($this->db->execute()) { 103 | return true; 104 | } else { 105 | return false; 106 | } 107 | } 108 | 109 | public function unlike($user_id, $tweet_id) { 110 | $this->db->query('DELETE FROM likes WHERE user_id = :user_id AND tweet_id = :tweet_id LIMIT 1'); 111 | $this->db->bind('user_id', $user_id); 112 | $this->db->bind('tweet_id', $tweet_id); 113 | 114 | if($this->db->execute()) { 115 | return true; 116 | } else { 117 | return false; 118 | } 119 | } 120 | 121 | public function isLike($user_id, $tweet_id) { 122 | $this->db->query('SELECT id FROM likes WHERE user_id = :user_id AND tweet_id = :tweet_id'); 123 | $this->db->bind('user_id', $user_id); 124 | $this->db->bind('tweet_id', $tweet_id); 125 | $user = $this->db->single(); 126 | if($user) { 127 | return true; 128 | } else { 129 | return false; 130 | } 131 | } 132 | 133 | public function getTotalLikes($tweet_id) { 134 | $this->db->query('SELECT id FROM likes WHERE tweet_id = :tweet_id'); 135 | $this->db->bind('tweet_id', $tweet_id); 136 | $this->db->execute(); 137 | 138 | return $this->db->rowCount(); 139 | } 140 | 141 | public function deleteTweet($id) { 142 | $this->db->query('DELETE FROM tweets WHERE id = :id LIMIT 1'); 143 | $this->db->bind('id', $id); 144 | if($this->db->execute()) { 145 | return true; 146 | } else { 147 | return false; 148 | } 149 | } 150 | 151 | 152 | } -------------------------------------------------------------------------------- /app/views/users/signup.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 |
10 |

Create an account

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 | Username must be at least three(3) characters long and must can contain one underscore and number. 36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 |
45 |
46 |
47 |
48 | > 49 | 50 | Password must be at least six(6) characters long and must contain one uppercase and number. 51 | 52 |
53 |
54 |
55 |
56 | > 57 | 58 | 59 |
60 |
61 | 62 |
63 |
64 |
65 |

Already using twitter? Login

66 |
67 |
68 |
69 |
70 |
71 |
72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /public/js/profile.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('textarea').characterCounter(); 3 | }); 4 | 5 | const tweetBodyInput = document.querySelector('#tweet-body'); 6 | const postTweetBtn = document.querySelector('#postTweetBtn'); 7 | const postForm = document.querySelector('#postForm'); 8 | const tweetDiv = document.querySelector('#tweets'); 9 | const loader = document.querySelector('#loader'); 10 | let collection = document.querySelectorAll('.collection'); 11 | const followBtn = document.querySelectorAll('.follow-btn'); 12 | const username = document.querySelector('#username'); 13 | 14 | let url = 'http://localhost:8888/twitter/'; 15 | 16 | tweetBodyInput.addEventListener('input', postBtn); 17 | window.addEventListener('DOMContentLoaded', loadAllTweet()); 18 | 19 | postBtn(); 20 | function postBtn() { 21 | let tweet = tweetBodyInput.value; 22 | 23 | if((tweet == '') || (tweet.length > 280) ) { 24 | postTweetBtn.classList.add('disabled'); 25 | } else { 26 | postTweetBtn.classList.remove('disabled'); 27 | } 28 | } 29 | 30 | postForm.addEventListener('submit', (e) => { 31 | loader.classList.remove('hide'); 32 | 33 | fetch(url + 'tweets/postTweet', { 34 | method: 'POST', 35 | headers: { 36 | 'content-type': 'application/x-www-form-urlencoded', 37 | 'X-Requested-With': 'XMLHttpRequest' 38 | }, 39 | body: 'tweet=' + tweetBodyInput.value 40 | }) 41 | .then(res => res.text()) 42 | .then(data => { 43 | // console.log(data); 44 | loader.classList.add('hide'); 45 | tweetBodyInput.value = ''; 46 | loadAllTweet(); 47 | }) 48 | .catch(err => console.log(err)); 49 | e.preventDefault(); 50 | }); 51 | 52 | function populateTweets(tweets) { 53 | tweetDiv.innerHTML = tweets; 54 | } 55 | 56 | function loadAllTweet() { 57 | fetch(url + 'tweets/loadUserTweets/' + username.value, { 58 | method: 'GET', 59 | headers: { 60 | 'content-type': 'application/x-www-form-urlencoded', 61 | 'X-Requested-With': 'XMLHttpRequest' 62 | } 63 | }) 64 | .then(res => res.text()) 65 | .then(data => { 66 | populateTweets(data); 67 | // console.log(data); 68 | }) 69 | .catch(err => console.log(err)); 70 | } 71 | 72 | followBtn.forEach((btn) => { 73 | if(btn.classList.contains('following')) { 74 | btn.innerHTML = 'Following'; 75 | } 76 | }) 77 | collection = Array.from(collection); 78 | 79 | collection.forEach((collection) => { 80 | collection.addEventListener('click', (e) => { 81 | if(e.target.classList.contains('follow-btn')) { 82 | 83 | const followerId = e.target.getAttribute('data-follower-id'); 84 | const followingId = e.target.getAttribute('data-following-id'); 85 | fetch(url + 'followSystem/follow', { 86 | method: 'POST', 87 | headers: { 88 | 'content-type': 'application/x-www-form-urlencoded', 89 | 'X-Requested-With': 'XMLHttpRequest' 90 | }, 91 | body: `followerId=${followerId}&followingId=${followingId}` 92 | }) 93 | .then(res => res.text()) 94 | .then(data => { 95 | if(data == 'follow') { 96 | e.target.classList.add('following'); 97 | e.target.innerHTML = 'Following'; 98 | } else { 99 | e.target.classList.remove('following'); 100 | e.target.innerHTML = 'Follow'; 101 | } 102 | }) 103 | .catch(err => console.log(err)); 104 | e.preventDefault(); 105 | } 106 | 107 | }) 108 | }); 109 | 110 | tweetDiv.addEventListener('click', (e) => { 111 | let tweet = e.target.parentElement; 112 | let userId = tweet.getAttribute('data-user'); 113 | let tweetId = tweet.getAttribute('data-tweet'); 114 | if(e.target.parentElement.classList.contains('likeBtn')) { 115 | fetch(url + 'tweets/likeTweet', { 116 | method: 'POST', 117 | headers: { 118 | 'content-type': 'application/x-www-form-urlencoded', 119 | 'X-Requested-With': 'XMLHttpRequest' 120 | }, 121 | body: `userId=${userId}&tweetId=${tweetId}` 122 | }) 123 | .then(res => res.text()) 124 | .then(data => { 125 | data = JSON.parse(data); 126 | if(data.like == 'yes') { 127 | tweet.classList.add('liked') 128 | tweet.lastElementChild.innerHTML = data.like_number; 129 | } else { 130 | tweet.classList.remove('liked') 131 | tweet.lastElementChild.innerHTML = data.like_number; 132 | } 133 | 134 | }) 135 | .catch(err => err); 136 | e.preventDefault(); 137 | } 138 | 139 | if(e.target.parentElement.classList.contains('deleteBtn')) { 140 | let tweetId = e.target.parentElement.getAttribute('data-id'); 141 | fetch(url + 'tweets/deleteTweet', { 142 | method: 'POST', 143 | headers: { 144 | 'content-type': 'application/x-www-form-urlencoded', 145 | 'X-Requested-With': 'XMLHttpRequest' 146 | }, 147 | body: `tweetId=${tweetId}` 148 | }) 149 | .then(res => res.text()) 150 | .then(data => { 151 | if(data == 'deleted') { 152 | e.target.parentElement.parentElement.parentElement.parentElement.remove(); 153 | } else { 154 | console.log('something happened'); 155 | } 156 | 157 | }) 158 | .catch(err => err); 159 | e.preventDefault(); 160 | } 161 | }); 162 | 163 | document.addEventListener('DOMContentLoaded', function() { 164 | var elems = document.querySelectorAll('.sidenav'); 165 | var instances = M.Sidenav.init(elems); 166 | 167 | var modal = document.querySelector('.modal'); 168 | var modalInst = M.Modal.init(modal); 169 | }); -------------------------------------------------------------------------------- /public/js/editprofile.js: -------------------------------------------------------------------------------- 1 | const firstName = document.querySelector('#firstName'); 2 | const lastName = document.querySelector('#lastName'); 3 | const email = document.querySelector('#email'); 4 | const username = document.querySelector('#username'); 5 | const bio = document.querySelector('#bio'); 6 | const dob = document.querySelector('#dob'); 7 | const curentPassword = document.querySelector('#currentPassword'); 8 | const password = document.querySelector('#password'); 9 | const confirmPassword = document.querySelector('#confirmPassword'); 10 | const personalInfoForm = document.querySelector('#personalInfoForm'); 11 | const changePasswordForm = document.querySelector('#changePasswordForm'); 12 | 13 | firstName.addEventListener('keyup', validateFirstName); 14 | lastName.addEventListener('keyup', validateLastName); 15 | email.addEventListener('keyup', validateEmail); 16 | username.addEventListener('keyup', validateUsername); 17 | bio.addEventListener('keyup', validateBio); 18 | dob.addEventListener('keyup', validateDob); 19 | currentPassword.addEventListener('keyup', validateCurrentPassword); 20 | password.addEventListener('keyup', validatePassword); 21 | confirmPassword.addEventListener('keyup', validateConfirmPassword); 22 | 23 | function validateFirstName() { 24 | validateNames(firstName); 25 | } 26 | 27 | function validateLastName() { 28 | validateNames(lastName); 29 | } 30 | 31 | function validateNames(elem) { 32 | const text = elem.value; 33 | const re = /^[a-z]{3,15}$/i; 34 | 35 | if(!re.test(text) ) { 36 | elem.classList.add('is-invalid'); 37 | return true; 38 | } else { 39 | elem.classList.remove('is-invalid'); 40 | return false; 41 | } 42 | } 43 | 44 | function validateEmail(e) { 45 | const text = email.value; 46 | const re = /^[a-z]([\w\_\-\.])+@([\w\.])+\.(\w){2,5}$/i; 47 | 48 | if(!re.test(text)) { 49 | email.classList.add('is-invalid'); 50 | return true; 51 | } else { 52 | email.classList.remove('is-invalid'); 53 | return false; 54 | } 55 | } 56 | 57 | function validateUsername(e) { 58 | const text = username.value; 59 | const re = /^([\w\_]){3,10}$/i; 60 | 61 | if(!re.test(text)) { 62 | username.classList.add('is-invalid'); 63 | return true; 64 | } else { 65 | username.classList.remove('is-invalid'); 66 | return false; 67 | } 68 | } 69 | 70 | function validateBio() { 71 | const text = bio.value; 72 | const re = /^[\w\W]+$/i; 73 | 74 | if(!re.test(text) ) { 75 | bio.classList.add('is-invalid'); 76 | return true; 77 | } else { 78 | bio.classList.remove('is-invalid'); 79 | return false; 80 | } 81 | } 82 | 83 | function validateDob(e) { 84 | const text = dob.value; 85 | const re = /^([\w\s])+\,(\s)([0-9]){4}$/i; 86 | 87 | if(!re.test(text)) { 88 | dob.classList.add('is-invalid'); 89 | return true; 90 | } else { 91 | dob.classList.remove('is-invalid'); 92 | return false; 93 | } 94 | } 95 | 96 | function validateCurrentPassword(e) { 97 | const text = currentPassword.value; 98 | const re = /^[\w]{6,}$/i; 99 | const uppercase = /[A-Z]/; 100 | const number = /[0-9]/; 101 | 102 | if(!re.test(text) || (text.search(uppercase) == '-1') || (text.search(number) == '-1')) { 103 | currentPassword.classList.add('is-invalid'); 104 | return true; 105 | } else { 106 | currentPassword.classList.remove('is-invalid'); 107 | return false; 108 | } 109 | } 110 | 111 | function validatePassword(e) { 112 | const text = password.value; 113 | const re = /^[\w]{6,}$/i; 114 | const uppercase = /[A-Z]/; 115 | const number = /[0-9]/; 116 | 117 | if(!re.test(text) || (text.search(uppercase) == '-1') || (text.search(number) == '-1')) { 118 | password.classList.add('is-invalid'); 119 | return true; 120 | } else { 121 | password.classList.remove('is-invalid'); 122 | return false; 123 | } 124 | } 125 | 126 | function validateConfirmPassword(e) { 127 | const text = confirmPassword.value; 128 | 129 | if(text !== password.value) { 130 | confirmPassword.classList.add('is-invalid'); 131 | return true; 132 | } else { 133 | confirmPassword.classList.remove('is-invalid'); 134 | return false; 135 | } 136 | } 137 | 138 | personalInfoForm.addEventListener('submit', (e) => { 139 | if(validateFirstName() || validateLastName() || validateUsername() || validateEmail() || validateBio() || validateDob()) { 140 | validateFirstName(); 141 | validateLastName(); 142 | validateEmail(); 143 | validateUsername(); 144 | validateBio(); 145 | validateDob(); 146 | } else { 147 | return true; 148 | } 149 | e.preventDefault(); 150 | }); 151 | 152 | changePasswordForm.addEventListener('submit', (e) => { 153 | 154 | if(validateCurrentPassword() || validatePassword() || validateConfirmPassword()) { 155 | validateCurrentPassword(); 156 | validatePassword(); 157 | validateConfirmPassword(); 158 | } else { 159 | return true; 160 | } 161 | e.preventDefault(); 162 | }); 163 | 164 | const date = new Date(); 165 | 166 | const monthsShort = 167 | [ 168 | 'Jan', 169 | 'Feb', 170 | 'Mar', 171 | 'Apr', 172 | 'May', 173 | 'Jun', 174 | 'Jul', 175 | 'Aug', 176 | 'Sep', 177 | 'Oct', 178 | 'Nov', 179 | 'Dec' 180 | ]; 181 | 182 | const todaysDate = date.getDate() + ' ' + monthsShort[date.getMonth()] + ', ' + date.getFullYear(); 183 | const currentDate = new Date(todaysDate); 184 | 185 | document.addEventListener('DOMContentLoaded', function() { 186 | var elems = document.querySelector('.datepicker'); 187 | var instances = M.Datepicker.init(elems, {maxDate: currentDate}); 188 | dob.addEventListener('focus', () => { 189 | instances.open(); 190 | }); 191 | 192 | var sidenav = document.querySelectorAll('.sidenav'); 193 | var sideNavInstances = M.Sidenav.init(sidenav); 194 | }); -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('textarea').characterCounter(); 3 | }); 4 | 5 | let url = 'http://localhost:8888/twitter/'; 6 | 7 | const tweetBodyInput = document.querySelector('#tweet-body'); 8 | const postTweetBtn = document.querySelector('#postTweetBtn'); 9 | const postForm = document.querySelector('#postForm'); 10 | const followForm = document.querySelector('#followForm'); 11 | const followBtn = document.querySelectorAll('#followBtn'); 12 | let collection = document.querySelectorAll('.collection'); 13 | const tweetDiv = document.querySelector('#tweets'); 14 | const loader = document.querySelector('#loader'); 15 | 16 | tweetBodyInput.addEventListener('input', postBtn); 17 | window.addEventListener('DOMContentLoaded', loadAllTweet()); 18 | 19 | postBtn(); 20 | function postBtn() { 21 | let tweet = tweetBodyInput.value; 22 | 23 | if((tweet == '') || (tweet.length > 280) ) { 24 | postTweetBtn.classList.add('disabled'); 25 | } else { 26 | postTweetBtn.classList.remove('disabled'); 27 | } 28 | } 29 | 30 | postForm.addEventListener('submit', (e) => { 31 | loader.classList.remove('hide'); 32 | 33 | fetch(url + 'tweets/postTweet', { 34 | method: 'POST', 35 | headers: { 36 | 'content-type': 'application/x-www-form-urlencoded', 37 | 'X-Requested-With': 'XMLHttpRequest' 38 | }, 39 | body: 'tweet=' + tweetBodyInput.value 40 | }) 41 | .then(res => res.text()) 42 | .then(data => { 43 | // console.log(data); 44 | loader.classList.add('hide'); 45 | tweetBodyInput.value = ''; 46 | loadAllTweet(); 47 | }) 48 | .catch(err => console.log(err)); 49 | e.preventDefault(); 50 | }); 51 | 52 | function populateTweets(tweets) { 53 | tweetDiv.innerHTML = tweets; 54 | } 55 | 56 | function appendToDiv(div, new_html) { 57 | let temp = document.createElement('div'); 58 | temp.innerHTML = new_html; 59 | 60 | let class_name = temp.firstElementChild.className; 61 | let items = temp.getElementsByClassName(class_name); 62 | 63 | items = Array.from(items); 64 | 65 | items.forEach((item) => { 66 | div.appendChild(item); 67 | }) 68 | } 69 | 70 | function loadAllTweet() { 71 | fetch(url + 'tweets/loadAllTweets', { 72 | method: 'GET', 73 | headers: { 74 | 'content-type': 'application/x-www-form-urlencoded', 75 | 'X-Requested-With': 'XMLHttpRequest' 76 | } 77 | }) 78 | .then(res => res.text()) 79 | .then(data => { 80 | populateTweets(data); 81 | // appendToDiv(tweetDiv, data); 82 | }) 83 | .catch(err => console.log(err)); 84 | } 85 | 86 | // Following system 87 | followBtn.forEach((btn) => { 88 | if(btn.classList.contains('following')) { 89 | btn.innerHTML = 'Following'; 90 | } 91 | }) 92 | 93 | collection = Array.from(collection); 94 | 95 | collection.forEach((collection) => { 96 | collection.addEventListener('click', (e) => { 97 | if(e.target.classList.contains('follow-btn')) { 98 | 99 | const followerId = e.target.getAttribute('data-follower-id'); 100 | const followingId = e.target.getAttribute('data-following-id'); 101 | fetch(url + 'followSystem/follow', { 102 | method: 'POST', 103 | headers: { 104 | 'content-type': 'application/x-www-form-urlencoded', 105 | 'X-Requested-With': 'XMLHttpRequest' 106 | }, 107 | body: `followerId=${followerId}&followingId=${followingId}` 108 | }) 109 | .then(res => res.text()) 110 | .then(data => { 111 | if(data == 'follow') { 112 | e.target.classList.add('following'); 113 | e.target.innerHTML = 'Following'; 114 | } else { 115 | e.target.classList.remove('following'); 116 | e.target.innerHTML = 'Follow'; 117 | } 118 | }) 119 | .catch(err => console.log(err)); 120 | e.preventDefault(); 121 | } 122 | 123 | }) 124 | }); 125 | 126 | tweetDiv.addEventListener('click', (e) => { 127 | let tweet = e.target.parentElement; 128 | let userId = tweet.getAttribute('data-user'); 129 | let tweetId = tweet.getAttribute('data-tweet'); 130 | if(e.target.parentElement.classList.contains('likeBtn')) { 131 | fetch(url + 'tweets/likeTweet', { 132 | method: 'POST', 133 | headers: { 134 | 'content-type': 'application/x-www-form-urlencoded', 135 | 'X-Requested-With': 'XMLHttpRequest' 136 | }, 137 | body: `userId=${userId}&tweetId=${tweetId}` 138 | }) 139 | .then(res => res.text()) 140 | .then(data => { 141 | data = JSON.parse(data); 142 | if(data.like == 'yes') { 143 | tweet.classList.add('liked') 144 | tweet.lastElementChild.innerHTML = data.like_number; 145 | } else { 146 | tweet.classList.remove('liked') 147 | tweet.lastElementChild.innerHTML = data.like_number; 148 | } 149 | 150 | }) 151 | .catch(err => err); 152 | } 153 | 154 | if(e.target.parentElement.classList.contains('deleteBtn')) { 155 | let tweetId = e.target.parentElement.getAttribute('data-id'); 156 | fetch(url + 'tweets/deleteTweet', { 157 | method: 'POST', 158 | headers: { 159 | 'content-type': 'application/x-www-form-urlencoded', 160 | 'X-Requested-With': 'XMLHttpRequest' 161 | }, 162 | body: `tweetId=${tweetId}` 163 | }) 164 | .then(res => res.text()) 165 | .then(data => { 166 | if(data == 'deleted') { 167 | e.target.parentElement.parentElement.parentElement.parentElement.remove(); 168 | } else { 169 | console.log('something happened'); 170 | } 171 | 172 | }) 173 | .catch(err => err); 174 | e.preventDefault(); 175 | } 176 | 177 | }) 178 | 179 | document.addEventListener('DOMContentLoaded', function() { 180 | var elems = document.querySelector('.modal'); 181 | var instances = M.Modal.init(elems); 182 | }); 183 | -------------------------------------------------------------------------------- /app/views/users/followers.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |

17 | firstname)) . ' ' . h(ucwords($user->lastname)); ?> 18 |

19 |

@username); ?>

20 |

bio)) ? $user->bio : "Edit your profile to add your bio"; ?>

21 |
22 |
23 | Tweets 24 | Following 25 | Followers 26 |
27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |

No followers yet.

36 | 37 | 38 | getFollow($follower_users->follower_id) as $user): ?> 39 |
40 |
41 |
42 |
43 | 44 | 45 |
46 |
47 |
48 |

49 | firstname)) . ' ' . h(ucwords($user->lastname)); ?> 50 | username); ?>findFollow($user->id, $_SESSION['user_id']) && $_SESSION['user_id'] != $user->id ? 'Follows you' : '' ?> 51 |

52 | 53 |
54 | id): ?> 55 | 56 | 57 |
58 | 59 |
60 |
61 |
62 | 63 |
64 | 65 |
66 |
67 |
68 |

Who to follow . View all

69 | 70 | 87 |
88 | 89 | 97 | 98 |
99 |
100 |
101 |
102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/views/users/following.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |

17 | firstname)) . ' ' . h(ucwords($user->lastname)); ?> 18 |

19 |

@username); ?>

20 |

bio)) ? $user->bio : "Edit your profile to add your bio"; ?>

21 |
22 |
23 | Tweets 24 | Following 25 | Followers 26 |
27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |

You have not followed anybody yet.

36 | 37 | 38 | getFollow($following_users->following_id) as $user): ?> 39 |
40 |
41 |
42 |
43 | 44 | 45 |
46 |
47 |
48 |

49 | firstname)) . ' ' . h(ucwords($user->lastname)); ?> 50 | username); ?>findFollow($user->id, $_SESSION['user_id']) && ($_SESSION['user_id'] != $user->id) ? 'Follows you' : '' ?> 51 |

52 |
53 | id): ?> 54 | 55 | 56 |
57 |
58 |
59 |
60 | 61 |
62 | 63 |
64 |
65 |
66 |

Who to follow . View all

67 | 68 | 85 |
86 | 87 | 95 | 96 |
97 |
98 |
99 |
100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /app/views/users/profile.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |

17 | firstname)) . ' ' . h(ucwords($user->lastname)); ?> 18 |

19 |

@username); ?>

20 |

bio)) ? $user->bio : "Edit your profile to add your bio"; ?>

21 |
22 |
23 | Tweets 24 | Following 25 | Followers 26 |
27 | 28 |
29 |
30 | id == $_SESSION['user_id']): ?> 31 | Edit Profile 32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 |

Sending Tweet

51 |
52 |
53 |
54 |
55 |
56 | 57 |
58 | 59 |
60 |
61 |
62 |

Who to follow . View all

63 | 64 | 80 | 81 | 99 |
100 | 101 | 109 | 110 |
111 |
112 |
113 |
114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /app/views/tweets/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 13 |
14 |
15 | 21 |
22 | Tweets 23 | Following 24 | Followers 25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |

Sending Tweet

43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |

Who to follow . View all

53 | 69 |
70 | 71 | 79 | 80 |
81 |
82 |
83 |
84 |

Who to follow . View all

85 | 101 |
102 | 103 | 111 | 112 |
113 |
114 |
115 | 116 | 134 |
135 | 136 | 137 | 143 | 144 | -------------------------------------------------------------------------------- /app/views/users/editprofile.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 13 | 14 |
15 |
16 | 22 | 23 |
24 |
25 |
26 |
27 |
28 |
Personal Information
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 | Username must be at least three(3) characters long and must can contain one underscore and number. 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 |
Change Password
79 |
80 |
81 |
82 | > 83 | 84 | 85 | 86 |
87 |
88 |
89 |
90 | > 91 | 92 | Password must be at least six(6) characters long and must contain one uppercase and number. 93 | 94 |
95 |
96 |
97 |
98 | > 99 | 100 | 101 |
102 |
103 | 104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | 112 | 113 | 114 | 115 | 118 | 119 | 120 | 123 | 124 | 125 | 128 | 129 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #E6ECF0; 3 | display: flex; 4 | min-height: 100vh; 5 | flex-direction: column; 6 | color: hsl(0, 1%, 14%); 7 | } 8 | 9 | main { 10 | flex: 1 0 auto; 11 | } 12 | 13 | .color-black { 14 | color: hsl(0, 1%, 14%)!important; 15 | } 16 | 17 | .color-twitter { 18 | color: #2AA3EF!important; 19 | } 20 | 21 | .bg-twitter { 22 | background-color: #2AA3EF!important; 23 | } 24 | 25 | .twitter-hover { 26 | color: #E8F5FD; 27 | } 28 | 29 | .color-grey { 30 | color: #667785 !important; 31 | } 32 | 33 | .red { 34 | color: #e0245e; 35 | } 36 | 37 | .bold { 38 | font-weight: bold!important; 39 | } 40 | 41 | .rounded-border { 42 | border-radius: 110px; 43 | } 44 | 45 | .capitalize { 46 | text-transform: capitalize!important; 47 | } 48 | 49 | main#index { 50 | height: 100%; 51 | } 52 | 53 | main#index .row { 54 | margin-top: 8rem; 55 | } 56 | 57 | main#index h1 { 58 | margin-top: .3rem; 59 | font-size: 1.9rem; 60 | font-weight: bold; 61 | text-align: left 62 | } 63 | 64 | main#index p { 65 | font-weight: bold; 66 | } 67 | 68 | main#index a { 69 | width: 100%!important; 70 | border-radius: 110px; 71 | margin-bottom: 15px; 72 | text-transform: capitalize; 73 | font-weight: bold; 74 | } 75 | 76 | main#index a.login { 77 | background: white; 78 | } 79 | 80 | main#index a.login:hover { 81 | box-shadow: none; 82 | background-color: #EAF5FD; 83 | } 84 | 85 | main#index a.signup:hover { 86 | box-shadow: none; 87 | background: #106FBC!important; 88 | } 89 | 90 | .mt-0 { 91 | margin-top: 0; 92 | } 93 | 94 | .ml-0 { 95 | margin-left: 0; 96 | } 97 | 98 | .mb-0 { 99 | margin-bottom: 0; 100 | } 101 | 102 | .mr-0 { 103 | margin-right: 0; 104 | } 105 | 106 | .m-0 { 107 | margin: 0; 108 | } 109 | 110 | .p-0 { 111 | padding: 0 !important; 112 | } 113 | 114 | .pt-0 { 115 | padding-top: 0!important; 116 | } 117 | 118 | .pl-0 { 119 | padding-left: 0!important; 120 | } 121 | 122 | .pr-0 { 123 | padding-right: 0!important; 124 | } 125 | 126 | .pb-0 { 127 | padding-bottom: 0!important; 128 | } 129 | 130 | .error { 131 | color: red; 132 | } 133 | 134 | .no-shadow { 135 | box-shadow: none; 136 | } 137 | 138 | .progress { 139 | background-color: #E8F5FD; 140 | } 141 | 142 | .progress .indeterminate { 143 | background-color: #2AA3EF; 144 | } 145 | 146 | .auth .card-action { 147 | background: #F5F8FA; 148 | } 149 | 150 | .invalid-feedback { 151 | display: none!important; 152 | } 153 | 154 | .input-field input[type]:focus { 155 | border-bottom: 1px solid #2AA3EF!important; 156 | box-shadow: 0 1px 0 0 #2AA3EF!important; 157 | } 158 | 159 | .input-field input[type].is-invalid:focus { 160 | border-bottom: 1px solid red!important; 161 | box-shadow: 0 1px 0 0 red!important; 162 | } 163 | 164 | .input-field .is-invalid { 165 | border-bottom: 1px solid red!important; 166 | box-shadow: 0 1px 0 0 red!important; 167 | } 168 | 169 | .input-field .is-invalid + .invalid-feedback { 170 | display: block!important; 171 | color: red!important; 172 | } 173 | 174 | .input-field textarea:focus { 175 | border-bottom: 1px solid #2AA3EF!important; 176 | box-shadow: 0 1px 0 0 #2AA3EF!important; 177 | } 178 | 179 | .input-field textarea:focus { 180 | border-bottom: 1px solid #2AA3EF!important; 181 | box-shadow: 0 1px 0 0 #2AA3EF!important; 182 | } 183 | 184 | .datepicker-date-display { 185 | background-color: #2AA3EF!important; 186 | } 187 | 188 | .is-today { 189 | color: #2AA3EF!important; 190 | } 191 | 192 | .datepicker-table td.is-selected { 193 | background: #2AA3EF; 194 | color: #fff!important; 195 | } 196 | 197 | .datepicker-footer button { 198 | color: #2AA3EF!important; 199 | } 200 | 201 | /* Navbar */ 202 | .navbar { 203 | height: 48px; 204 | line-height: 46px; 205 | font-weight: bold 206 | } 207 | 208 | .navbar a, .navbar i { 209 | font-size: 1.1rem!important; 210 | line-height: 48px!important; 211 | } 212 | 213 | .navbar a { 214 | height: 48px; 215 | color: #66757f !important; 216 | padding-left: 1rem; 217 | padding-right: 1rem; 218 | font-size: .91rem!important; 219 | } 220 | 221 | .navbar a.active, .navbar ul a:hover { 222 | color: #2AA3EF !important; 223 | border-bottom: 3px solid #2AA3EF; 224 | } 225 | 226 | .navbar a:hover { 227 | background: transparent; 228 | } 229 | 230 | .navbar i.icon { 231 | margin-right: 5px!important; 232 | } 233 | 234 | .navbar .sidenav-trigger i { 235 | font-size: 1.5rem !important; 236 | } 237 | 238 | nav .container a#menuIcon { 239 | padding-left: 0!important; 240 | margin-left: 0!important; 241 | } 242 | 243 | #menuIcon i { 244 | color: #2AA3EF!important; 245 | } 246 | 247 | .navbar #search { 248 | /* background: #F5F8FA; */ 249 | /* border-radius: 21px; */ 250 | /* border: 1px solid #e6ecf0; */ 251 | /* height: 32px; */ 252 | } 253 | 254 | .navbar #searchBtn { 255 | margin-bottom: 8px; 256 | color: #999999; 257 | } 258 | 259 | .navbar .drop i { 260 | /* background-color: #999999; 261 | /* padding: .5rem .7rem; */ 262 | line-height: 30px!important; 263 | height: 30px !important; 264 | background-color: #999999; 265 | /* padding: .5rem; */ 266 | padding: 0 .5rem; 267 | border-radius: 50%; 268 | } 269 | 270 | .navbar .drop a:hover { 271 | background: transparent; 272 | } 273 | 274 | .navbar #tweetBtn { 275 | background: #2AA3EF; 276 | border-radius: 100px; 277 | text-transform: capitalize; 278 | font-weight: bold; 279 | height: 35px; 280 | color: white!important; 281 | line-height: 35px!important; 282 | } 283 | 284 | .navbar #tweetBtn:hover, .navbar #searchBtn { 285 | box-shadow: none; 286 | } 287 | 288 | #profileNav a { 289 | line-height: 13px !important; 290 | margin-right: 1.5rem; 291 | color: #66757f!important; 292 | font-size: .77rem!important; 293 | padding-top: 5px; 294 | text-align: center; 295 | } 296 | 297 | #profileNav a.active span { 298 | color: #2AA3EF; 299 | } 300 | 301 | #profileNav a:hover { 302 | background: transparent; 303 | } 304 | 305 | #profileNav span { 306 | color: #667785; 307 | font-weight: bold; 308 | font-size: 1.3rem; 309 | display: block; 310 | padding-top: 3px; 311 | } 312 | 313 | #user-profile { 314 | padding-top: 20px !important; 315 | margin-bottom: 20px; 316 | } 317 | 318 | #user-profile .card { 319 | margin: 0; 320 | background: transparent; 321 | } 322 | 323 | #user-profile .card-content { 324 | padding-top: 0; 325 | } 326 | 327 | #user-profile h1 { 328 | margin: 0; 329 | font-size: 1.3rem; 330 | font-weight: bold; 331 | } 332 | 333 | #user-profile h1 a, #user-profile .bio { 334 | color: #14171A; 335 | } 336 | 337 | #user-profile .handle, #user-profile .handle a { 338 | color: #657786; 339 | } 340 | 341 | #user-profile .bio { 342 | margin-top: 1rem; 343 | } 344 | 345 | #user-profile .card .card-action { 346 | border-top: none; 347 | } 348 | 349 | 350 | /* Tweet */ 351 | .main-container { 352 | /* max-width: 1300px; */ 353 | /* padding-right: 1rem; 354 | padding-left: 1rem; */ 355 | /* width: 70%; */ 356 | 357 | } 358 | 359 | @media only screen and (min-width: 993px) { 360 | .main-container { 361 | width: 90%; 362 | } 363 | } 364 | @media only screen and (min-width: 601px) { 365 | .main-container { 366 | /* width: 100%; */ 367 | } 368 | } 369 | 370 | .card-profile .bg-twitter { 371 | height: 6rem; 372 | } 373 | 374 | .img { 375 | padding: .8rem .9rem; 376 | /* padding-top: .6rem; */ 377 | /* background: #e5dede; 378 | */ 379 | background-color: #999999; 380 | display: inline-block; 381 | border-radius: 50%; 382 | border: 4px solid white; 383 | position: relative; 384 | top: 55px; 385 | left: 10px; 386 | } 387 | 388 | .card { 389 | margin-top: 0; 390 | } 391 | 392 | .card-profile .card-content { 393 | padding-top: 4px; 394 | padding-bottom: 10px; 395 | } 396 | 397 | .card-profile .card-content p { 398 | padding-left: 70px; 399 | color: #14171A !important; 400 | font-weight: bold; 401 | font-size: 1.2rem; 402 | } 403 | 404 | .card-profile .card-content a { 405 | color: #14171A; 406 | } 407 | 408 | .card-profile .card-content a:hover { 409 | text-decoration: underline; 410 | } 411 | 412 | .card-profile .card-content span { 413 | color: #667785; 414 | font-weight: normal; 415 | font-size: .9rem; 416 | display: block; 417 | } 418 | 419 | .card-profile .card-action { 420 | padding-top: 0; 421 | padding-left: 12px; 422 | border-top: none; 423 | } 424 | 425 | .card-profile.card .card-action:last-child a, #user-profile .card-action:last-child a { 426 | display: inline-block; 427 | color: #657786!important; 428 | font-weight: bold!important; 429 | font-size: .8rem; 430 | margin-right: 1.4rem!important; 431 | text-transform: capitalize !important; 432 | } 433 | 434 | .card-profile.card .card-action:last-child span, #user-profile .card-action:last-child span { 435 | display: block; 436 | font-size: 1.2rem; 437 | } 438 | 439 | .card-profile.card .isFollow { 440 | background: #E6ECF0; 441 | padding: .2rem .3rem; 442 | /* font-size: .7rem; */ 443 | font-style: normal; 444 | border-radius: 5px; 445 | margin-left: 1rem; 446 | } 447 | 448 | #user-profile .card-action { 449 | padding: 0; 450 | } 451 | 452 | .tweet { 453 | background-color: #E8F5FD; 454 | } 455 | 456 | .tweet .card-content { 457 | padding-bottom: 0; 458 | } 459 | 460 | .tweet .card-action { 461 | border-top: 0; 462 | } 463 | 464 | .tweet input { 465 | border-radius: 20px; 466 | } 467 | 468 | #tweets { 469 | margin-bottom: 10px; 470 | } 471 | 472 | #tweets .horizontal { 473 | border-bottom: 1px solid #e0e0e0; 474 | border-bottom: 1px solid #e6ecf0; 475 | margin-bottom: 0; 476 | } 477 | 478 | #tweets .avatar { 479 | display: inline-block; 480 | background-color: #999999; 481 | padding: .5rem .7rem; 482 | border-radius: 50%; 483 | margin-top: 1.9rem; 484 | margin-left: 1rem; 485 | } 486 | 487 | #tweets .card-content { 488 | padding-left: .9rem; 489 | padding-bottom: 0; 490 | } 491 | 492 | #tweets .card-content a .bold { 493 | color: #14171A; 494 | } 495 | 496 | #tweets .card-content a:hover .bold { 497 | color: #2AA3EF; 498 | text-decoration: underline; 499 | } 500 | 501 | #tweets .card-content small { 502 | position: relative; 503 | top: -4px; 504 | } 505 | 506 | #tweets .card-action { 507 | border-top: none; 508 | padding-left: .9rem; 509 | padding-bottom: 0; 510 | padding-top: 0; 511 | /* padding-bottom: .7rem; */ 512 | } 513 | 514 | #tweets .card-action span { 515 | font-weight: bold; 516 | font-size: .9rem; 517 | } 518 | 519 | #tweets .card-action i { 520 | padding-right: 1rem; 521 | padding-top: 1.2rem; 522 | padding-bottom: 1.2rem; 523 | } 524 | 525 | #tweets .card-action a { 526 | /* padding: 1.2rem; */ 527 | padding-left: 0; 528 | } 529 | 530 | #tweets .liked { 531 | color: rgb(224, 36, 94) !important; 532 | animation-name: grow; 533 | animation-duration: 1s; 534 | animation-iteration-count: 1; 535 | animation-timing-function: ease; 536 | } 537 | 538 | @keyframes grow { 539 | 0% { 540 | transform: scale(0); 541 | } 542 | 100% { 543 | transform: scale(2); 544 | } 545 | } 546 | 547 | .follow-btn { 548 | border: 1px solid #2AA3EF; 549 | border-radius: 100px; 550 | font-size: .7rem; 551 | font-weight: bold; 552 | padding: 0 1.4rem; 553 | height: 28px; 554 | line-height: 28px; 555 | } 556 | 557 | .follow-btn.following { 558 | color: white!important; 559 | background: #2AA3EF!important; 560 | } 561 | 562 | 563 | .follow-btn:hover { 564 | box-shadow: none; 565 | /* background-color: #E8F5FD !important; */ 566 | } 567 | 568 | .collection { 569 | border: none; 570 | } 571 | 572 | .to-follow { 573 | padding: .1rem .7rem; 574 | } 575 | 576 | .to-follow .avatar { 577 | padding-left: 50px !important; 578 | } 579 | 580 | .to-follow .avatar a { 581 | font-size: 1rem; 582 | color: #14171A; 583 | } 584 | 585 | .to-follow .avatar a:hover .bold { 586 | color: #2AA3EF; 587 | text-decoration: underline; 588 | } 589 | 590 | .to-follow .avatar i.circle:first-child { 591 | left: .1rem!important; 592 | } 593 | 594 | .to-follow button { 595 | margin-top: .4rem; 596 | } 597 | 598 | .to-follow a { 599 | font-weight: normal; 600 | font-size: .8rem; 601 | } 602 | 603 | .footer { 604 | margin-top: 1rem; 605 | } 606 | 607 | /* Banner */ 608 | .banner { 609 | height: 200px; 610 | background: #2AA3EF; 611 | background-attachment: fixed; 612 | position: relative; 613 | } 614 | 615 | .banner a { 616 | position: absolute; 617 | top: 90px; 618 | display: block; 619 | z-index: 998; 620 | bottom: -50px; 621 | font-size: 2.8rem; 622 | margin-left: 4rem; 623 | } 624 | 625 | .banner a i { 626 | padding: 1.5rem 2rem; 627 | border-radius: 50%; 628 | border: 5px solid #fff; 629 | display: block; 630 | background-color:#999999; 631 | } 632 | 633 | @media (max-width: 992px) { 634 | .banner a { 635 | font-size: 1.8rem; 636 | } 637 | } 638 | 639 | @media (max-width: 622px) { 640 | .banner { 641 | height: 150px; 642 | } 643 | .banner a { 644 | top: 50px; 645 | margin-left: 1.5rem; 646 | font-size: 1.8rem; 647 | } 648 | } 649 | 650 | .class { 651 | height: 600px; 652 | } 653 | 654 | h5.modal-header { 655 | margin-left: 25px !important; 656 | } 657 | 658 | .personalInfo { 659 | margin-top: 20px !important; 660 | } 661 | 662 | .personalInfo .card-content { 663 | padding-top: 10px; 664 | } 665 | 666 | #editProfile { 667 | margin-top: 10px; 668 | background: transparent ; 669 | } 670 | 671 | #slide-out .name { 672 | font-weight: bold; 673 | } 674 | 675 | #slide-out .card { 676 | background: transparent; 677 | } 678 | 679 | #slide-out .card a { 680 | text-transform: capitalize; 681 | color: rgba(255, 255, 255, .9); 682 | } 683 | 684 | #slide-out .card span { 685 | font-weight: bold; 686 | } 687 | 688 | #about { 689 | padding-top: 4rem; 690 | padding-bottom: 4rem; 691 | font-weight: bold; 692 | } 693 | 694 | #about > p { 695 | color: rgba(255, 255, 255, .5); 696 | font-size: 1.4rem; 697 | margin-bottom: 5px; 698 | } 699 | 700 | #about > p + p { 701 | font-size: 4rem; 702 | line-height: 1; 703 | margin-top: 0; 704 | } 705 | 706 | @media (max-width: 500px) { 707 | #about > p { 708 | font-size: 1rem; 709 | } 710 | #about > p + p { 711 | font-size: 3rem; 712 | } 713 | } -------------------------------------------------------------------------------- /app/controllers/Users.php: -------------------------------------------------------------------------------- 1 | userModel = $this->model('User'); 9 | $this->tweetModel = $this->model('Tweet'); 10 | $this->followModel = $this->model('FollowSys'); 11 | } 12 | 13 | public function index() { 14 | if(Auth::isLoggedIn()) { 15 | redirect('tweets'); 16 | } else { 17 | redirect('/'); 18 | } 19 | } 20 | 21 | public function signup() { 22 | if(Auth::isLoggedIn()) { 23 | redirect('tweets'); 24 | } 25 | 26 | if(is_post_request()) { 27 | $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); 28 | 29 | $data = [ 30 | 'firstName' => trim($_POST['firstName']), 31 | 'lastName' => trim($_POST['lastName']), 32 | 'email' => trim($_POST['email']), 33 | 'username' => trim($_POST['username']), 34 | 'dob' => trim($_POST['dob']), 35 | 'password' => trim($_POST['password']), 36 | 'confirmPassword' => trim($_POST['confirmPassword']), 37 | 'firstName_err' => '', 38 | 'lastName_err' => '', 39 | 'email_err' => '', 40 | 'username_err' => '', 41 | 'dob_err' => '', 42 | 'password_err' => '', 43 | 'confirmPassword_err' => '' 44 | ]; 45 | 46 | if(Validation::isBlank($data['firstName'])) { 47 | $data['firstName_err'] = 'Please enter first name'; 48 | } else if(!Validation::hasLength($data['firstName'], ['min' => '3'])) { 49 | $data['firstName_err'] = 'This must be at least three(3) characters long'; 50 | } else if(!Validation::hasSymbolsAndNumbers($data['firstName'])) { 51 | $data['firstName_err'] = 'Invalid first name'; 52 | } 53 | 54 | if(Validation::isBlank($data['lastName'])) { 55 | $data['lastName_err'] = 'Please enter last name'; 56 | } else if(!Validation::hasLength($data['lastName'], ['min' => '3'])) { 57 | $data['lastName_err'] = 'This must be at least three(3) characters long'; 58 | } else if(!Validation::hasSymbolsAndNumbers($data['lastName'])) { 59 | $data['lastName_err'] = 'Invalid first name'; 60 | } 61 | 62 | if(Validation::isBlank($data['email'])) { 63 | $data['email_err'] = 'Please enter email'; 64 | } else if(!Validation::hasValidEmailFormat($data['email'])) { 65 | $data['email_err'] = 'This is invalid'; 66 | } else if(!$this->userModel->findEmailOrUsername($data['email'])) { 67 | $data['email_err'] = 'Email already been used'; 68 | } 69 | 70 | if(Validation::isBlank($data['username'])) { 71 | $data['username_err'] = 'Please enter username'; 72 | } else if(!Validation::hasLength($data['username'], ['min' => '3'])) { 73 | $data['username_err'] = 'This must be at least three(3) characters long'; 74 | } else if(preg_match('/[-\W]/i', $data['username'])) { 75 | $data['username_err'] = 'Username can contain characters, numbers or underscore(_)'; 76 | } else if(!$this->userModel->findEmailOrUsername($data['username'])) { 77 | $data['username_err'] = 'Username already been used'; 78 | } 79 | 80 | if(Validation::isBlank($data['dob'])) { 81 | $data['dob_err'] = 'Please enter date of birth'; 82 | } 83 | 84 | if(Validation::isBlank($data['password'])) { 85 | $data['password_err'] = 'Please enter password'; 86 | } else if(!Validation::hasLength($data['password'], ['min' => '6'])) { 87 | $data['password_err'] = 'This must be at least six(6) characters long'; 88 | } else if(Validation::hasUppercase($data['password'])) { 89 | $data['password_err'] = 'This must contain at least one uppercase letter'; 90 | } else if(Validation::hasNumber($data['password'])) { 91 | $data['password_err'] = 'This must contain at least one number'; 92 | } 93 | 94 | if(Validation::isBlank($data['confirmPassword'])) { 95 | $data['confirmPassword_err'] = 'Please enter password'; 96 | } else if($data['confirmPassword'] !== $data['password']) { 97 | $data['confirmPassword_err'] = 'Password don\'t match'; 98 | } 99 | 100 | if(empty($data['firstName_err']) && empty($data['lastName_err']) && empty($data['email_err']) && empty($data['username_err']) && empty($data['dob_err']) && empty($data['password_err']) && empty($data['confirmPassword_err'])) { 101 | $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); 102 | 103 | $signup = $this->userModel->signup($data); 104 | if($signup) { 105 | Auth::logIn($signup); 106 | redirect('tweets'); 107 | } else { 108 | die('Something went wrong'); 109 | } 110 | } 111 | 112 | $this->view('users/signup', $data); 113 | } 114 | $data = [ 115 | 'firstName' => '', 116 | 'lastName' => '', 117 | 'email' => '', 118 | 'username' => '', 119 | 'dob' => '', 120 | 'password' => '', 121 | 'confirmPassword' => '', 122 | 'firstName_err' => '', 123 | 'lastName_err' => '', 124 | 'email_err' => '', 125 | 'username_err' => '', 126 | 'dob_err' => '', 127 | 'password_err' => '', 128 | 'confirmPassword_err' => '' 129 | ]; 130 | 131 | $this->view('users/signup', $data); 132 | } 133 | 134 | public function login() { 135 | if(Auth::isLoggedIn()) { 136 | redirect('tweets'); 137 | } 138 | 139 | if(is_post_request()) { 140 | $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); 141 | 142 | $data = [ 143 | 'username' => trim($_POST['username']), 144 | 'password' => trim($_POST['password']), 145 | 'username_err' => '', 146 | 'password_err' => '', 147 | ]; 148 | 149 | if(Validation::isBlank($data['username'])) { 150 | $data['username_err'] = 'Please enter username'; 151 | } 152 | 153 | if(Validation::isBlank($data['password'])) { 154 | $data['password_err'] = 'Please enter password'; 155 | } 156 | 157 | if($this->userModel->findEmailOrUsername($data['username'])) { 158 | $data['username_err'] = 'Sorry we don\'t recognize you'; 159 | $data['password_err'] = 'Please enter password'; 160 | } 161 | 162 | if(empty($data['username_err']) && empty($data['password_err'])) { 163 | $login = $this->userModel->login($data); 164 | if($login) { 165 | Auth::logIn($login); 166 | redirect('tweets'); 167 | } else { 168 | $_SESSION['message'] = 'Email/username or password is invalid'; 169 | } 170 | } 171 | 172 | $this->view('users/login', $data); 173 | } 174 | $data = [ 175 | 'username' => '', 176 | 'password' => '', 177 | 'username_err' => '', 178 | 'password_err' => '', 179 | ]; 180 | 181 | $this->view('users/login', $data); 182 | } 183 | 184 | public function profile($username = '') { 185 | Auth::requireLogIn(); 186 | if(empty($username)) { 187 | redirect('tweets/'); 188 | } 189 | $data = [ 190 | 'user' => $this->userModel->getUserByUserName($username), 191 | 'users' => $this->userModel->getAllUser(4), 192 | 'getAllUsers' => $this->userModel->getAllUser(), 193 | 'tweets' => $this->tweetModel->getAllTweetsByUserName($username), 194 | 'total-tweets' => $this->tweetModel->getTotalTweetsByUserName($username), 195 | 'total-following' => $this->followModel->getTotalFollowingByUserName($username), 196 | 'total-follower' => $this->followModel->getTotalFollowersByUserName($username), 197 | 'follow' => $this->followModel, 198 | 'likes' => $this->tweetModel, 199 | 'username' => $username 200 | ]; 201 | 202 | if(!$data['user']) { 203 | redirect('tweets/'); 204 | } 205 | 206 | $this->view('users/profile', $data); 207 | } 208 | 209 | public function editprofile() { 210 | Auth::requireLogIn(); 211 | if(isset($_POST['changePassword'])) { 212 | $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); 213 | $user = $this->userModel->getUserById(); 214 | $data = [ 215 | 'firstName' => $user->firstname, 216 | 'lastName' => $user->lastname, 217 | 'email' => $user->email, 218 | 'username' => $user->username, 219 | 'bio' => $user->bio, 220 | 'dob' => $user->dob, 221 | 'currentPassword' => trim($_POST['currentPassword']), 222 | 'password' => trim($_POST['password']), 223 | 'confirmPassword' => trim($_POST['confirmPassword']), 224 | 'total-following' => $this->followModel->getTotalFollowing(), 225 | 'total-follower' => $this->followModel->getTotalFollower(), 226 | 'firstName_err' => '', 227 | 'lastName_err' => '', 228 | 'email_err' => '', 229 | 'username_err' => '', 230 | 'bio_err' => '', 231 | 'dob_err' => '', 232 | 'currentPassword_err' => '', 233 | 'password_err' => '', 234 | 'confirmPassword_err' => '', 235 | 'user' => $this->userModel->getUserById() 236 | ]; 237 | if(Validation::isBlank($data['currentPassword'])) { 238 | $data['currentPassword_err'] = 'Please enter current password'; 239 | } else if(!password_verify($data['currentPassword'], $user->password)) { 240 | $data['currentPassword_err'] = 'Password doesn\'t match current password'; 241 | $_SESSION['message'] = 'Failed to change password'; 242 | } else { 243 | 244 | if(Validation::isBlank($data['password'])) { 245 | $data['password_err'] = 'Please enter password'; 246 | } else if(!Validation::hasLength($data['password'], ['min' => '6'])) { 247 | $data['password_err'] = 'This must be at least six(6) characters long'; 248 | } else if(Validation::hasUppercase($data['password'])) { 249 | $data['password_err'] = 'This must contain at least one uppercase letter'; 250 | } else if(Validation::hasNumber($data['password'])) { 251 | $data['password_err'] = 'This must contain at least one number'; 252 | } 253 | 254 | if(Validation::isBlank($data['confirmPassword'])) { 255 | $data['confirmPassword_err'] = 'Please enter password'; 256 | } else if($data['confirmPassword'] !== $data['password']) { 257 | $data['confirmPassword_err'] = 'Password don\'t match'; 258 | } 259 | 260 | if(empty($data['currentPassword_err']) && empty($data['password_err']) && empty($data['confirmPassword_err'])) { 261 | $updatedPassword = $this->userModel->updatePassword($data['password']); 262 | if($updatedPassword) { 263 | $_SESSION['message'] = 'Password Updated Successfully'; 264 | } 265 | } 266 | 267 | } 268 | 269 | $this->view('users/editprofile', $data); 270 | } 271 | 272 | 273 | 274 | if(isset($_POST['update'])) { 275 | $user = $this->userModel->getUserById(); 276 | $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); 277 | $data = [ 278 | 'firstName' => trim($_POST['firstName']), 279 | 'lastName' => trim($_POST['lastName']), 280 | 'email' => trim($_POST['email']), 281 | 'username' => trim($_POST['username']), 282 | 'bio' => trim($_POST['bio']), 283 | 'dob' => trim($_POST['dob']), 284 | 'total-following' => $this->followModel->getTotalFollowing(), 285 | 'total-follower' => $this->followModel->getTotalFollower(), 286 | 'firstName_err' => '', 287 | 'lastName_err' => '', 288 | 'email_err' => '', 289 | 'username_err' => '', 290 | 'bio_err' => '', 291 | 'dob_err' => '', 292 | 'currentPassword_err' => '', 293 | 'password_err' => '', 294 | 'confirmPassword_err' => '' 295 | ]; 296 | if(Validation::isBlank($data['firstName'])) { 297 | $data['firstName_err'] = 'Please enter first name'; 298 | } else if(!Validation::hasLength($data['firstName'], ['min' => '3'])) { 299 | $data['firstName_err'] = 'This must be at least three(3) characters long'; 300 | } else if(!Validation::hasSymbolsAndNumbers($data['firstName'])) { 301 | $data['firstName_err'] = 'Invalid first name'; 302 | } 303 | 304 | if(Validation::isBlank($data['lastName'])) { 305 | $data['lastName_err'] = 'Please enter last name'; 306 | } else if(!Validation::hasLength($data['lastName'], ['min' => '3'])) { 307 | $data['lastName_err'] = 'This must be at least three(3) characters long'; 308 | } else if(!Validation::hasSymbolsAndNumbers($data['lastName'])) { 309 | $data['lastName_err'] = 'Invalid last name'; 310 | } 311 | 312 | if($user->email !== $data['email']) { 313 | if(Validation::isBlank($data['email'])) { 314 | $data['email_err'] = 'Please enter email'; 315 | } else if(!Validation::hasValidEmailFormat($data['email'])) { 316 | $data['email_err'] = 'This is invalid'; 317 | } else if(!$this->userModel->findEmailOrUsername($data['email'])) { 318 | $data['email_err'] = 'Email already been used'; 319 | } 320 | } 321 | 322 | if($user->username !== $data['username']) { 323 | if(Validation::isBlank($data['username'])) { 324 | $data['username_err'] = 'Please enter username'; 325 | } else if(!Validation::hasLength($data['username'], ['min' => '3'])) { 326 | $data['username_err'] = 'This must be at least three(3) characters long'; 327 | } else if(preg_match('/[-\W]/i', $data['username'])) { 328 | $data['username_err'] = 'Username can contain characters, numbers or underscore(_)'; 329 | } else if(!$this->userModel->findEmailOrUsername($data['username'])) { 330 | $data['username_err'] = 'Username already taken'; 331 | } 332 | } 333 | 334 | if($user->bio !== $data['bio']) { 335 | if(Validation::isBlank($data['bio'])) { 336 | $data['bio_err'] = 'Please enter bio'; 337 | } else if(!Validation::hasLength($data['bio'], ['min' => '3'])) { 338 | $data['bio_err'] = 'This must be at least three(3) characters long'; 339 | } 340 | } 341 | 342 | if(Validation::isBlank($data['dob'])) { 343 | $data['username_err'] = 'Please enter date of birth'; 344 | } 345 | 346 | 347 | if(empty($data['firstName_err']) && empty($data['lastName_err']) && empty($data['email_err']) && empty($data['username_err']) && empty($data['bio_err']) && empty($data['dob_err'])) { 348 | $updateInfo = []; 349 | if($data['firstName'] !== $user->firstname) { 350 | $updateInfo['firstname'] = $data['firstName']; 351 | } 352 | 353 | if($data['lastName'] !== $user->lastname) { 354 | $updateInfo['lastname'] = $data['lastName']; 355 | } 356 | 357 | if($data['email'] !== $user->email) { 358 | $updateInfo['email'] = $data['email']; 359 | } 360 | 361 | if($data['username'] !== $user->username) { 362 | $updateInfo['username'] = $data['username']; 363 | } 364 | 365 | if($data['bio'] !== $user->bio) { 366 | $updateInfo['bio'] = $data['bio']; 367 | } 368 | 369 | if($data['dob'] !== $user->dob) { 370 | $updateInfo['dob'] = $data['dob']; 371 | } 372 | 373 | // var_dump($updatedInfo); 374 | 375 | if(!empty($updateInfo)) { 376 | $updatedInfo = $this->userModel->updateInfo($updateInfo); 377 | if($updatedInfo) { 378 | $_SESSION['message'] = 'Personal Information Updated'; 379 | } else { 380 | 381 | } 382 | } 383 | } 384 | $data['user'] = $this->userModel->getUserById(); 385 | $this->view('users/editprofile', $data); 386 | } 387 | 388 | $user = $this->userModel->getUserById(); 389 | $data = [ 390 | 'user' => $this->userModel->getUserById(), 391 | 'firstName' => $user->firstname, 392 | 'lastName' => $user->lastname, 393 | 'email' => $user->email, 394 | 'username' => $user->username, 395 | 'bio' => $user->bio, 396 | 'dob' => $user->dob, 397 | 'total-following' => $this->followModel->getTotalFollowing(), 398 | 'total-follower' => $this->followModel->getTotalFollower(), 399 | 'firstName_err' => '', 400 | 'lastName_err' => '', 401 | 'email_err' => '', 402 | 'username_err' => '', 403 | 'bio_err' => '', 404 | 'dob_err' => '', 405 | 'currentPassword_err' => '', 406 | 'password_err' => '', 407 | 'confirmPassword_err' => '' 408 | ]; 409 | 410 | $this->view('users/editprofile', $data); 411 | } 412 | 413 | public function following($username) { 414 | Auth::requireLogIn(); 415 | if(empty($username)) { 416 | redirect('tweets/'); 417 | } 418 | 419 | $data = [ 420 | 'user' => $this->userModel->getUserByUserName($username), 421 | 'users' => $this->userModel->getAllUser(4), 422 | 'tweets' => $this->tweetModel->getAllTweetsByUserName($username), 423 | 'total-tweets' => $this->tweetModel->getTotalTweetsByUserName($username), 424 | 'total-following' => $this->followModel->getTotalFollowingByUserName($username), 425 | 'total-follower' => $this->followModel->getTotalFollowersByUserName($username), 426 | 'following' => $this->followModel->getFollowingByUserName($username), 427 | 'follow' => $this->followModel, 428 | 'username' => $username 429 | ]; 430 | 431 | if(!$data['user']) { 432 | redirect('tweets/'); 433 | } 434 | 435 | $this->view('users/following', $data); 436 | } 437 | 438 | public function followers($username) { 439 | Auth::requireLogIn(); 440 | if(empty($username)) { 441 | redirect('tweets/'); 442 | } 443 | $data = [ 444 | 'user' => $this->userModel->getUserByUserName($username), 445 | 'users' => $this->userModel->getAllUser(4), 446 | 'tweets' => $this->tweetModel->getAllTweetsByUserName($username), 447 | 'total-tweets' => $this->tweetModel->getTotalTweetsByUserName($username), 448 | 'total-following' => $this->followModel->getTotalFollowingByUserName($username), 449 | 'total-follower' => $this->followModel->getTotalFollowersByUserName($username), 450 | 'followers' => $this->followModel->getFollowersByUserName($username), 451 | 'follow' => $this->followModel, 452 | 'username' => $username 453 | ]; 454 | 455 | if(!$data['user']) { 456 | redirect('tweets/'); 457 | } 458 | 459 | $this->view('users/followers', $data); 460 | } 461 | 462 | public function search() { 463 | Auth::requireLogIn(); 464 | if(isset($_GET['user']) && !empty($_GET['user'])) { 465 | $user = trim($_GET['user']); 466 | $data = [ 467 | 'user' => $this->userModel->getUserById(), 468 | 'searchResults' => $this->userModel->searchForUser($user), 469 | 'total-tweets' => $this->tweetModel->getTotalTweets(), 470 | 'total-following' => $this->followModel->getTotalFollowing(), 471 | 'total-follower' => $this->followModel->getTotalFollower(), 472 | 'follow' => $this->followModel, 473 | ]; 474 | 475 | } else { 476 | 477 | $data = [ 478 | 'user' => $this->userModel->getUserById(), 479 | 'searchResults' => '', 480 | 'total-tweets' => $this->tweetModel->getTotalTweets(), 481 | 'total-following' => $this->followModel->getTotalFollowing(), 482 | 'total-follower' => $this->followModel->getTotalFollower(), 483 | 'follow' => $this->followModel, 484 | ]; 485 | } 486 | 487 | $this->view('users/search', $data); 488 | } 489 | 490 | public function logout() { 491 | Auth::logOut(); 492 | redirect('/'); 493 | } 494 | 495 | } -------------------------------------------------------------------------------- /public/js/jquery.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.3.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,u=n.push,s=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,d=f.toString,p=d.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},v=function e(t){return null!=t&&t===t.window},y={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in y)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function b(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var x="3.3.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector",w=function(e,t){return new w.fn.init(e,t)},C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:x,constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,u,s,l,c,f,d,p,h,g,v,y,m,b,x="sizzle"+1*new Date,w=e.document,C=0,T=0,E=ae(),N=ae(),k=ae(),A=function(e,t){return e===t&&(f=!0),0},D={}.hasOwnProperty,S=[],L=S.pop,j=S.push,q=S.push,O=S.slice,P=function(e,t){for(var n=0,r=e.length;n+~]|"+I+")"+I+"*"),_=new RegExp("="+I+"*([^\\]'\"]*?)"+I+"*\\]","g"),U=new RegExp(M),V=new RegExp("^"+R+"$"),X={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+B),PSEUDO:new RegExp("^"+M),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+I+"*(even|odd|(([+-]|)(\\d*)n|)"+I+"*(?:([+-]|)"+I+"*(\\d+)|))"+I+"*\\)|)","i"),bool:new RegExp("^(?:"+H+")$","i"),needsContext:new RegExp("^"+I+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+I+"*((?:-\\d)?\\d*)"+I+"*\\)|)(?=[^-]|$)","i")},Q=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,G=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,J=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+I+"?|("+I+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){d()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{q.apply(S=O.call(w.childNodes),w.childNodes),S[w.childNodes.length].nodeType}catch(e){q={apply:S.length?function(e,t){j.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,u,l,c,f,h,y,m=t&&t.ownerDocument,C=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==C&&9!==C&&11!==C)return r;if(!i&&((t?t.ownerDocument||t:w)!==p&&d(t),t=t||p,g)){if(11!==C&&(f=K.exec(e)))if(o=f[1]){if(9===C){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&b(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return q.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return q.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!k[e+" "]&&(!v||!v.test(e))){if(1!==C)m=t,y=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=x),u=(h=a(e)).length;while(u--)h[u]="#"+c+" "+ye(h[u]);y=h.join(","),m=J.test(e)&&ge(t.parentNode)||t}if(y)try{return q.apply(r,m.querySelectorAll(y)),r}catch(e){}finally{c===x&&t.removeAttribute("id")}}}return s(e.replace($,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function ue(e){return e[x]=!0,e}function se(e){var t=p.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function de(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pe(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return ue(function(t){return t=+t,ue(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},d=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==p&&9===a.nodeType&&a.documentElement?(p=a,h=p.documentElement,g=!o(p),w!==p&&(i=p.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=se(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=se(function(e){return e.appendChild(p.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=G.test(p.getElementsByClassName),n.getById=se(function(e){return h.appendChild(e).id=x,!p.getElementsByName||!p.getElementsByName(x).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},y=[],v=[],(n.qsa=G.test(p.querySelectorAll))&&(se(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+I+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+I+"*(?:value|"+H+")"),e.querySelectorAll("[id~="+x+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+x+"+*").length||v.push(".#.+[+~]")}),se(function(e){e.innerHTML="";var t=p.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+I+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(n.matchesSelector=G.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&se(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),y.push("!=",M)}),v=v.length&&new RegExp(v.join("|")),y=y.length&&new RegExp(y.join("|")),t=G.test(h.compareDocumentPosition),b=t||G.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===p||e.ownerDocument===w&&b(w,e)?-1:t===p||t.ownerDocument===w&&b(w,t)?1:c?P(c,e)-P(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],u=[t];if(!i||!o)return e===p?-1:t===p?1:i?-1:o?1:c?P(c,e)-P(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)u.unshift(n);while(a[r]===u[r])r++;return r?ce(a[r],u[r]):a[r]===w?-1:u[r]===w?1:0},p):p},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&d(e),t=t.replace(_,"='$1']"),n.matchesSelector&&g&&!k[t+" "]&&(!y||!y.test(t))&&(!v||!v.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,p,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==p&&d(e),b(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==p&&d(e);var i=r.attrHandle[t.toLowerCase()],o=i&&D.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(A),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:ue,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return X.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&U.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+I+")"+e+"("+I+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace(W," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),u="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,s){var l,c,f,d,p,h,g=o!==a?"nextSibling":"previousSibling",v=t.parentNode,y=u&&t.nodeName.toLowerCase(),m=!s&&!u,b=!1;if(v){if(o){while(g){d=t;while(d=d[g])if(u?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?v.firstChild:v.lastChild],a&&m){b=(p=(l=(c=(f=(d=v)[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===C&&l[1])&&l[2],d=p&&v.childNodes[p];while(d=++p&&d&&d[g]||(b=p=0)||h.pop())if(1===d.nodeType&&++b&&d===t){c[e]=[C,p,b];break}}else if(m&&(b=p=(l=(c=(f=(d=t)[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===C&&l[1]),!1===b)while(d=++p&&d&&d[g]||(b=p=0)||h.pop())if((u?d.nodeName.toLowerCase()===y:1===d.nodeType)&&++b&&(m&&((c=(f=d[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]=[C,b]),d===t))break;return(b-=i)===r||b%r==0&&b/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[x]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?ue(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=P(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:ue(function(e){var t=[],n=[],r=u(e.replace($,"$1"));return r[x]?ue(function(e,t,n,i){var o,a=r(e,null,i,[]),u=e.length;while(u--)(o=a[u])&&(e[u]=!(t[u]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:ue(function(e){return function(t){return oe(e,t).length>0}}),contains:ue(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:ue(function(e){return V.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:pe(!1),disabled:pe(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xe(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else y=we(y===a?y.splice(h,y.length):y),i?i(null,a,y,s):q.apply(a,y)})}function Te(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],u=a||r.relative[" "],s=a?1:0,c=me(function(e){return e===t},u,!0),f=me(function(e){return P(t,e)>-1},u,!0),d=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];s1&&be(d),s>1&&ye(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),n,s0,i=e.length>0,o=function(o,a,u,s,c){var f,h,v,y=0,m="0",b=o&&[],x=[],w=l,T=o||i&&r.find.TAG("*",c),E=C+=null==w?1:Math.random()||.1,N=T.length;for(c&&(l=a===p||a||c);m!==N&&null!=(f=T[m]);m++){if(i&&f){h=0,a||f.ownerDocument===p||(d(f),u=!g);while(v=e[h++])if(v(f,a||p,u)){s.push(f);break}c&&(C=E)}n&&((f=!v&&f)&&y--,o&&b.push(f))}if(y+=m,n&&m!==y){h=0;while(v=t[h++])v(b,x,a,u);if(o){if(y>0)while(m--)b[m]||x[m]||(x[m]=L.call(s));x=we(x)}q.apply(s,x),c&&!o&&x.length>0&&y+t.length>1&&oe.uniqueSort(s)}return c&&(C=E,l=w),b};return n?ue(o):o}return u=oe.compile=function(e,t){var n,r=[],i=[],o=k[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Te(t[n]))[x]?r.push(o):i.push(o);(o=k(e,Ee(i,r))).selector=e}return o},s=oe.select=function(e,t,n,i){var o,s,l,c,f,d="function"==typeof e&&e,p=!i&&a(e=d.selector||e);if(n=n||[],1===p.length){if((s=p[0]=p[0].slice(0)).length>2&&"ID"===(l=s[0]).type&&9===t.nodeType&&g&&r.relative[s[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;d&&(t=t.parentNode),e=e.slice(s.shift().value.length)}o=X.needsContext.test(e)?0:s.length;while(o--){if(l=s[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),J.test(s[0].type)&&ge(t.parentNode)||t))){if(s.splice(o,1),!(e=i.length&&ye(s)))return q.apply(n,i),n;break}}}return(d||u(e,p))(i,t,!g,n,!t||J.test(e)&&ge(t.parentNode)||t),n},n.sortStable=x.split("").sort(A).join("")===x,n.detectDuplicates=!!f,d(),n.sortDetached=se(function(e){return 1&e.compareDocumentPosition(p.createElement("fieldset"))}),se(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&se(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),se(function(e){return null==e.getAttribute("disabled")})||le(H,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var N=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},k=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},A=w.expr.match.needsContext;function D(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var S=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function L(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return s.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(L(this,e||[],!1))},not:function(e){return this.pushStack(L(this,e||[],!0))},is:function(e){return!!L(this,"string"==typeof e&&A.test(e)?w(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:q.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),S.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,j=w(r);var O=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?s.call(w(e),this[0]):s.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function H(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return N(e,"parentNode")},parentsUntil:function(e,t,n){return N(e,"parentNode",n)},next:function(e){return H(e,"nextSibling")},prev:function(e){return H(e,"previousSibling")},nextAll:function(e){return N(e,"nextSibling")},prevAll:function(e){return N(e,"previousSibling")},nextUntil:function(e,t,n){return N(e,"nextSibling",n)},prevUntil:function(e,t,n){return N(e,"previousSibling",n)},siblings:function(e){return k((e.parentNode||{}).firstChild,e)},children:function(e){return k(e.firstChild)},contents:function(e){return D(e,"iframe")?e.contentDocument:(D(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(P[e]||w.uniqueSort(i),O.test(e)&&i.reverse()),this.pushStack(i)}});var I=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(I)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],u=-1,s=function(){for(i=i||e.once,r=t=!0;a.length;u=-1){n=a.shift();while(++u-1)o.splice(n,1),n<=u&&u--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||s()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function B(e){return e}function M(e){throw e}function W(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var u=this,s=arguments,l=function(){var e,l;if(!(t=o&&(r!==M&&(u=void 0,s=[e]),n.rejectWith(u,s))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:B,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:B)),n[2][3].add(a(0,e,g(r)?r:M))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],u=t[5];i[t[1]]=a.add,u&&a.add(function(){r=u},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),u=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&(W(e,a.done(u(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)W(i[n],u(n),a.reject);return a.promise()}});var $=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&$.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function z(){r.removeEventListener("DOMContentLoaded",z),e.removeEventListener("load",z),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",z),e.addEventListener("load",z));var _=function(e,t,n,r,i,o,a){var u=0,s=e.length,l=null==n;if("object"===b(n)){i=!0;for(u in n)_(e,t,u,n[u],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;u1,null,!0)},removeData:function(e){return this.each(function(){J.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=K.get(e,t),n&&(!r||Array.isArray(n)?r=K.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return K.get(e,n)||K.access(e,n,{empty:w.Callbacks("once memory").add(function(){K.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&D(e,t)?w.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var xe=r.documentElement,we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ne(){return!1}function ke(){try{return r.activeElement}catch(e){}}function Ae(e,t,n,r,i,o){var a,u;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(u in t)Ae(e,u,n,r,t[u],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ne;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,u,s,l,c,f,d,p,h,g,v=K.get(e);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(xe,i),n.guid||(n.guid=w.guid++),(s=v.events)||(s=v.events={}),(a=v.handle)||(a=v.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(I)||[""]).length;while(l--)p=g=(u=Te.exec(t[l])||[])[1],h=(u[2]||"").split(".").sort(),p&&(f=w.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=w.event.special[p]||{},c=w.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=s[p])||((d=s[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(p,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),w.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,u,s,l,c,f,d,p,h,g,v=K.hasData(e)&&K.get(e);if(v&&(s=v.events)){l=(t=(t||"").match(I)||[""]).length;while(l--)if(u=Te.exec(t[l])||[],p=g=u[1],h=(u[2]||"").split(".").sort(),p){f=w.event.special[p]||{},d=s[p=(r?f.delegateType:f.bindType)||p]||[],u=u[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||u&&!u.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||w.removeEvent(e,p,v.handle),delete s[p])}else for(p in s)w.event.remove(e,p+t[l],n,r,!0);w.isEmptyObject(s)&&K.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,u,s=new Array(arguments.length),l=(K.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(s[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&u.push({elem:l,handlers:o})}return l=this,s\x20\t\r\n\f]*)[^>]*)\/>/gi,Se=/\s*$/g;function qe(e,t){return D(e,"table")&&D(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function Oe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Pe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function He(e,t){var n,r,i,o,a,u,s,l;if(1===t.nodeType){if(K.hasData(e)&&(o=K.access(e),a=K.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof v&&!h.checkClone&&Le.test(v))return e.each(function(i){var o=e.eq(i);y&&(t[0]=v.call(this,i,o.html())),Re(o,t,n,r)});if(d&&(i=be(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(u=w.map(ve(i,"script"),Oe)).length;f")},clone:function(e,t,n){var r,i,o,a,u=e.cloneNode(!0),s=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ve(u),r=0,i=(o=ve(e)).length;r0&&ye(a,!s&&ve(e,"script")),u},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[K.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[K.expando]=void 0}n[J.expando]&&(n[J.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Be(this,e,!0)},remove:function(e){return Be(this,e)},text:function(e){return _(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return _(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Se.test(e)&&!ge[(pe.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(s+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-s-u-.5))),s}function et(e,t,n){var r=We(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(Me.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,u=Q(t),s=Ue.test(t),l=e.style;if(s||(t=Ke(u)),a=w.cssHooks[t]||w.cssHooks[u],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[u]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(s?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,u=Q(t);return Ue.test(t)||(t=Ke(u)),(a=w.cssHooks[t]||w.cssHooks[u])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Xe&&(i=Xe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!_e.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):ue(e,Ve,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=We(e),a="border-box"===w.css(e,"boxSizing",!1,o),u=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(u-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),u&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Je(e,n,u)}}}),w.cssHooks.marginLeft=ze(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Je)}),w.fn.extend({css:function(e,t){return _(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=We(e),i=t.length;a1)}}),w.fn.delay=function(t,n){return t=w.fx?w.fx.speeds[t]||t:t,n=n||"fx",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=r.createElement("input"),t=r.createElement("select").appendChild(r.createElement("option"));e.type="checkbox",h.checkOn=""!==e.value,h.optSelected=t.selected,(e=r.createElement("input")).value="t",e.type="radio",h.radioValue="t"===e.value}();var tt,nt=w.expr.attrHandle;w.fn.extend({attr:function(e,t){return _(this,w.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?tt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&D(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(I);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),tt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=nt[t]||w.find.attr;nt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=nt[a],nt[a]=i,i=null!=n(e,t,r)?a:null,nt[a]=o),i}});var rt=/^(?:input|select|textarea|button)$/i,it=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return _(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):rt.test(e.nodeName)||it.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function ot(e){return(e.match(I)||[]).join(" ")}function at(e){return e.getAttribute&&e.getAttribute("class")||""}function ut(e){return Array.isArray(e)?e:"string"==typeof e?e.match(I)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,u,s=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,at(this)))});if((t=ut(e)).length)while(n=this[s++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(u=ot(r))&&n.setAttribute("class",u)}return this},removeClass:function(e){var t,n,r,i,o,a,u,s=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,at(this)))});if(!arguments.length)return this.attr("class","");if((t=ut(e)).length)while(n=this[s++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(u=ot(r))&&n.setAttribute("class",u)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,at(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=ut(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=at(this))&&K.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":K.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+ot(at(n))+" ").indexOf(t)>-1)return!0;return!1}});var st=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(st,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:ot(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,u=a?null:[],s=a?o+1:i.length;for(r=o<0?s:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var lt=/^(?:focusinfocus|focusoutblur)$/,ct=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,u,s,l,c,d,p,h,y=[i||r],m=f.call(t,"type")?t.type:t,b=f.call(t,"namespace")?t.namespace.split("."):[];if(u=h=s=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!lt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(b=m.split(".")).shift(),b.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=b.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),p=w.event.special[m]||{},o||!p.trigger||!1!==p.trigger.apply(i,n))){if(!o&&!p.noBubble&&!v(i)){for(l=p.delegateType||m,lt.test(l+m)||(u=u.parentNode);u;u=u.parentNode)y.push(u),s=u;s===(i.ownerDocument||r)&&y.push(s.defaultView||s.parentWindow||e)}a=0;while((u=y[a++])&&!t.isPropagationStopped())h=u,t.type=a>1?l:p.bindType||m,(d=(K.get(u,"events")||{})[t.type]&&K.get(u,"handle"))&&d.apply(u,n),(d=c&&u[c])&&d.apply&&Y(u)&&(t.result=d.apply(u,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(y.pop(),n)||!Y(i)||c&&g(i[m])&&!v(i)&&((s=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,ct),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,ct),w.event.triggered=void 0,s&&(i[c]=s)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=K.access(r,t);i||r.addEventListener(e,n,!0),K.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=K.access(r,t)-1;i?K.access(r,t,i):(r.removeEventListener(e,n,!0),K.remove(r,t))}}});var ft=/\[\]$/,dt=/\r?\n/g,pt=/^(?:submit|button|image|reset|file)$/i,ht=/^(?:input|select|textarea|keygen)/i;function gt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||ft.test(e)?r(e,i):gt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==b(t))r(e,t);else for(i in t)gt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)gt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&ht.test(this.nodeName)&&!pt.test(e)&&(this.checked||!de.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(dt,"\r\n")}}):{name:t.name,value:n.replace(dt,"\r\n")}}).get()}}),w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},h.createHTMLDocument=function(){var e=r.implementation.createHTMLDocument("").body;return e.innerHTML="
",2===e.childNodes.length}(),w.parseHTML=function(e,t,n){if("string"!=typeof e)return[];"boolean"==typeof t&&(n=t,t=!1);var i,o,a;return t||(h.createHTMLDocument?((i=(t=r.implementation.createHTMLDocument("")).createElement("base")).href=r.location.href,t.head.appendChild(i)):t=r),o=S.exec(e),a=!n&&[],o?[t.createElement(o[1])]:(o=be([e],t,a),a&&a.length&&w(a).remove(),w.merge([],o.childNodes))},w.offset={setOffset:function(e,t,n){var r,i,o,a,u,s,l,c=w.css(e,"position"),f=w(e),d={};"static"===c&&(e.style.position="relative"),u=f.offset(),o=w.css(e,"top"),s=w.css(e,"left"),(l=("absolute"===c||"fixed"===c)&&(o+s).indexOf("auto")>-1)?(a=(r=f.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(s)||0),g(t)&&(t=t.call(e,n,w.extend({},u))),null!=t.top&&(d.top=t.top-u.top+a),null!=t.left&&(d.left=t.left-u.left+i),"using"in t?t.using.call(e,d):f.css(d)}},w.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){w.offset.setOffset(this,e,t)});var t,n,r=this[0];if(r)return r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===w.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===w.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=w(e).offset()).top+=w.css(e,"borderTopWidth",!0),i.left+=w.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-w.css(r,"marginTop",!0),left:t.left-i.left-w.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===w.css(e,"position"))e=e.offsetParent;return e||xe})}}),w.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n="pageYOffset"===t;w.fn[e]=function(r){return _(this,function(e,r,i){var o;if(v(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),w.each(["top","left"],function(e,t){w.cssHooks[t]=ze(h.pixelPosition,function(e,n){if(n)return n=Fe(e,t),Me.test(n)?w(e).position()[t]+"px":n})}),w.each({Height:"height",Width:"width"},function(e,t){w.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){w.fn[r]=function(i,o){var a=arguments.length&&(n||"boolean"!=typeof i),u=n||(!0===i||!0===o?"margin":"border");return _(this,function(t,n,i){var o;return v(t)?0===r.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body["scroll"+e],o["scroll"+e],t.body["offset"+e],o["offset"+e],o["client"+e])):void 0===i?w.css(t,n,u):w.style(t,n,i,u)},t,a?i:void 0,a)}})}),w.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){w.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),w.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=D,w.isFunction=g,w.isWindow=v,w.camelCase=Q,w.type=b,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},"function"==typeof define&&define.amd&&define("jquery",[],function(){return w});var vt=e.jQuery,yt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=yt),t&&e.jQuery===w&&(e.jQuery=vt),w},t||(e.jQuery=e.$=w),w}); --------------------------------------------------------------------------------