├── .gitignore ├── app ├── public │ ├── media │ │ ├── uploads │ │ │ └── .gitignore │ │ ├── avatars │ │ │ ├── .gitignore │ │ │ └── default.jpg │ │ ├── covers │ │ │ ├── .gitignore │ │ │ └── default.jpg │ │ └── .DS_Store │ ├── assets │ │ ├── images │ │ │ ├── php.jpg │ │ │ ├── pin.png │ │ │ ├── more.png │ │ │ ├── photo.png │ │ │ ├── power.png │ │ │ ├── send.png │ │ │ ├── user.png │ │ │ ├── work.png │ │ │ ├── calendar.png │ │ │ ├── comment.png │ │ │ ├── friends.png │ │ │ ├── heart-on.png │ │ │ ├── home-run.png │ │ │ ├── laravel.jpg │ │ │ ├── search.png │ │ │ ├── settings.png │ │ │ ├── down-arrow.png │ │ │ ├── heart-off.png │ │ │ ├── devsbook_logo.png │ │ │ └── power_white.png │ │ ├── css │ │ │ ├── defaults.css │ │ │ ├── login.css │ │ │ ├── modal.css │ │ │ ├── feed-item.css │ │ │ └── style.css │ │ └── js │ │ │ ├── script.js │ │ │ └── vanillaModal.js │ ├── logout.php │ ├── ajax_like.php │ ├── excluir_post_action.php │ ├── login_action.php │ ├── ajax_post.php │ ├── follow_action.php │ ├── ajax_comment.php │ ├── login.php │ ├── signup.php │ ├── signup_action.php │ ├── search.php │ ├── ajax_upload.php │ ├── configuracoes.php │ ├── index.php │ ├── fotos.php │ ├── amigos.php │ ├── configuracoes_action.php │ └── perfil.php ├── config.php ├── partials │ ├── footer.php │ ├── header.php │ ├── menu.php │ ├── feed-editor.php │ ├── feed-item-script.php │ └── feed-item.php ├── models │ ├── PostComment.php │ ├── PostLike.php │ ├── UserRelation.php │ ├── Post.php │ ├── User.php │ └── Auth.php └── dao │ ├── PostCommentDaoMysql.php │ ├── PostLikeDaoMysql.php │ ├── UserRelationDaoMysql.php │ ├── UserDaoMysql.php │ └── PostDaoMysql.php ├── .env.example ├── services └── nginx │ ├── nginx.conf │ └── default.conf ├── docker-compose.yaml ├── init.sql └── Dockerfile /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /app/public/media/uploads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /app/public/media/avatars/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | !default.jpg 5 | -------------------------------------------------------------------------------- /app/public/media/covers/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | !default.jpg 5 | -------------------------------------------------------------------------------- /app/public/media/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/media/.DS_Store -------------------------------------------------------------------------------- /app/public/assets/images/php.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/php.jpg -------------------------------------------------------------------------------- /app/public/assets/images/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/pin.png -------------------------------------------------------------------------------- /app/public/assets/images/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/more.png -------------------------------------------------------------------------------- /app/public/assets/images/photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/photo.png -------------------------------------------------------------------------------- /app/public/assets/images/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/power.png -------------------------------------------------------------------------------- /app/public/assets/images/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/send.png -------------------------------------------------------------------------------- /app/public/assets/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/user.png -------------------------------------------------------------------------------- /app/public/assets/images/work.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/work.png -------------------------------------------------------------------------------- /app/public/assets/images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/calendar.png -------------------------------------------------------------------------------- /app/public/assets/images/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/comment.png -------------------------------------------------------------------------------- /app/public/assets/images/friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/friends.png -------------------------------------------------------------------------------- /app/public/assets/images/heart-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/heart-on.png -------------------------------------------------------------------------------- /app/public/assets/images/home-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/home-run.png -------------------------------------------------------------------------------- /app/public/assets/images/laravel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/laravel.jpg -------------------------------------------------------------------------------- /app/public/assets/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/search.png -------------------------------------------------------------------------------- /app/public/assets/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/settings.png -------------------------------------------------------------------------------- /app/public/media/avatars/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/media/avatars/default.jpg -------------------------------------------------------------------------------- /app/public/media/covers/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/media/covers/default.jpg -------------------------------------------------------------------------------- /app/public/assets/images/down-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/down-arrow.png -------------------------------------------------------------------------------- /app/public/assets/images/heart-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/heart-off.png -------------------------------------------------------------------------------- /app/public/assets/images/devsbook_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/devsbook_logo.png -------------------------------------------------------------------------------- /app/public/assets/images/power_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christopherldo/devsbook/HEAD/app/public/assets/images/power_white.png -------------------------------------------------------------------------------- /app/public/logout.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /services/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include mime.types; 10 | default_type application/octet-stream; 11 | 12 | sendfile on; 13 | tcp_nopush on; 14 | tcp_nodelay on; 15 | keepalive_timeout 65; 16 | 17 | include /etc/nginx/conf.d/*.conf; 18 | } 19 | -------------------------------------------------------------------------------- /app/models/PostComment.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | 9 | $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_SPECIAL_CHARS); 10 | 11 | if (empty($id) === false) { 12 | $postLikeDao = new PostLikeDaoMysql($pdo); 13 | $postLikeDao->likeToggle($id, $userInfo->publicId); 14 | } 15 | -------------------------------------------------------------------------------- /app/models/PostLike.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | 9 | $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_SPECIAL_CHARS); 10 | 11 | if ($id) { 12 | $postDao = new PostDaoMysql($pdo); 13 | 14 | $postDao->delete($id, $userInfo->publicId); 15 | } 16 | 17 | header("Location: $base"); 18 | exit; 19 | -------------------------------------------------------------------------------- /services/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080 default_server; 3 | root /app/public; 4 | 5 | index index.php index.html; 6 | 7 | client_max_body_size 20M; 8 | 9 | location / { 10 | try_files $uri $uri/ $uri.html $uri.php$is_args$query_string; 11 | } 12 | 13 | location ~ \.php$ { 14 | try_files $uri =404; 15 | fastcgi_pass 127.0.0.1:9000; 16 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 17 | include fastcgi_params; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/public/login_action.php: -------------------------------------------------------------------------------- 1 | validateLogin($email, $password)){ 13 | header("Location: $base"); 14 | exit; 15 | } 16 | } 17 | 18 | $_SESSION['flash'] = 'E-mail e/ou senha incorretos'; 19 | header("Location: $base/login"); 20 | exit; -------------------------------------------------------------------------------- /app/models/Post.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | 9 | $txt = filter_input(INPUT_GET, 'txt', FILTER_SANITIZE_SPECIAL_CHARS); 10 | 11 | $array = []; 12 | 13 | if ($txt) { 14 | $postDao = new PostDaoMysql($pdo); 15 | 16 | $newPost = new Post(); 17 | $newPost->publicId = $postDao->generateUuid(); 18 | $newPost->idUser = $userInfo->publicId; 19 | $newPost->type = 'text'; 20 | $newPost->createdAt = gmdate('Y-m-d H:i:s'); 21 | $newPost->body = $txt; 22 | 23 | $postDao->insert($newPost); 24 | 25 | $array = [ 26 | 'error' => '', 27 | ]; 28 | } 29 | 30 | header("Content-Type: application/json"); 31 | echo json_encode($array); 32 | exit; 33 | -------------------------------------------------------------------------------- /app/public/follow_action.php: -------------------------------------------------------------------------------- 1 | checkToken(); 9 | 10 | $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_SPECIAL_CHARS); 11 | 12 | if ($id) { 13 | $userRelationDao = new UserRelationDaoMysql($pdo); 14 | $userDao = new UserDaoMysql($pdo); 15 | 16 | if ($userDao->findById($id)) { 17 | 18 | $relation = new UserRelation(); 19 | $relation->userFrom = $userInfo->publicId; 20 | $relation->userTo = $id; 21 | 22 | if ($userRelationDao->isFollowing($userInfo->publicId, $id)) { 23 | $userRelationDao->delete($relation); 24 | } else { 25 | $userRelationDao->insert($relation); 26 | } 27 | 28 | header("Location: $base/perfil?id=$id"); 29 | exit; 30 | } 31 | } 32 | 33 | header("Location: $base"); 34 | exit; 35 | -------------------------------------------------------------------------------- /app/public/ajax_comment.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | 9 | $id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_SPECIAL_CHARS); 10 | $txt = filter_input(INPUT_POST, 'txt', FILTER_SANITIZE_SPECIAL_CHARS); 11 | 12 | $array = []; 13 | 14 | if ($id && $txt) { 15 | $postCommentDao = new PostCommentDaoMysql($pdo); 16 | 17 | $newComment = new PostComment(); 18 | $newComment->idPost = $id; 19 | $newComment->idUser = $userInfo->publicId; 20 | $newComment->body = $txt; 21 | $newComment->createdAt = gmdate('Y-m-d H:i:s'); 22 | 23 | $postCommentDao->addComment($newComment); 24 | 25 | $array = [ 26 | 'error' => '', 27 | 'link' => "$base/perfil?id=$userInfo->publicId", 28 | 'avatar' => "$base/media/avatars/$userInfo->avatar", 29 | 'name' => $userInfo->name, 30 | 'body' => $txt 31 | ]; 32 | } 33 | 34 | header("Content-Type: application/json"); 35 | echo json_encode($array); 36 | exit; 37 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | - "$PORT:80" 10 | environment: 11 | BASE_URL: $BASE_URL 12 | PORT: $PORT 13 | TOKEN_SECRET: $TOKEN_SECRET 14 | DATABASE_NAME: $DATABASE_NAME 15 | DATABASE_HOST: $DATABASE_HOST 16 | DATABASE_PORT: $DATABASE_PORT 17 | DATABASE_USER: $DATABASE_USER 18 | DATABASE_PASSWORD: $DATABASE_PASSWORD 19 | PHP_UPLOAD_MAX_FILESIZE: 20M 20 | PHP_POST_MAX_SIZE: 25M 21 | volumes: 22 | - ./app:/app 23 | - ./services/nginx/nginx.conf:/etc/nginx/nginx.conf 24 | - ./services/nginx/default.conf:/etc/nginx/conf.d/default.conf 25 | 26 | db: 27 | image: postgres:latest 28 | environment: 29 | POSTGRES_USER: $DATABASE_USER 30 | POSTGRES_PASSWORD: $DATABASE_PASSWORD 31 | volumes: 32 | - postgresdata:/var/lib/postgresql/data 33 | - ./init.sql:/docker-entrypoint-initdb.d/init.sql 34 | 35 | adminer: 36 | image: adminer:latest 37 | ports: 38 | - $ADMINER_PORT:8080 39 | depends_on: 40 | - db 41 | 42 | volumes: 43 | postgresdata: {} 44 | -------------------------------------------------------------------------------- /app/public/login.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Login - Devsbook 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Ainda não tem uma conta? Cadastre-se 35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE posts ( 2 | id SERIAL PRIMARY KEY, 3 | public_id varchar(36) UNIQUE NOT NULL, 4 | id_user varchar(36) NOT NULL, 5 | type varchar(25) NOT NULL, 6 | created_at timestamp NOT NULL, 7 | body text NOT NULL 8 | ); 9 | 10 | CREATE TABLE post_comments ( 11 | id SERIAL PRIMARY KEY, 12 | id_post varchar(36) NOT NULL, 13 | id_user varchar(36) NOT NULL, 14 | created_at timestamp NOT NULL, 15 | body text NOT NULL 16 | ); 17 | 18 | CREATE TABLE post_likes ( 19 | id SERIAL PRIMARY KEY, 20 | id_post varchar(36) NOT NULL, 21 | id_user varchar(36) NOT NULL, 22 | created_at timestamp NOT NULL 23 | ); 24 | 25 | CREATE TABLE users ( 26 | id SERIAL PRIMARY KEY, 27 | public_id varchar(36) UNIQUE NOT NULL, 28 | email varchar(50) UNIQUE NOT NULL, 29 | password varchar(64) NOT NULL, 30 | salt varchar(64) UNIQUE NOT NULL, 31 | name varchar(50) NOT NULL, 32 | birthdate date NOT NULL, 33 | city varchar(50) DEFAULT NULL, 34 | work varchar(50) DEFAULT NULL, 35 | avatar varchar(100) NOT NULL DEFAULT 'default.jpg', 36 | cover varchar(100) NOT NULL DEFAULT 'default.jpg' 37 | ); 38 | 39 | CREATE TABLE user_relations ( 40 | id SERIAL PRIMARY KEY, 41 | user_from varchar(36) NOT NULL, 42 | user_to varchar(36) NOT NULL 43 | ); 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use uma imagem base para PHP-FPM 2 | FROM php:7.4.0-fpm-alpine 3 | 4 | # Instale dependências e extensões necessárias 5 | RUN set -ex \ 6 | && apk --no-cache add \ 7 | postgresql-dev \ 8 | freetype-dev \ 9 | libjpeg-turbo-dev \ 10 | libpng-dev \ 11 | libwebp-dev \ 12 | && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ 13 | && docker-php-ext-install gd pdo pdo_pgsql 14 | 15 | # Configure parâmetros do PHP 16 | ENV PHP_UPLOAD_MAX_FILESIZE=20M 17 | ENV PHP_POST_MAX_SIZE=25M 18 | RUN echo "upload_max_filesize = ${PHP_UPLOAD_MAX_FILESIZE}" > /usr/local/etc/php/conf.d/uploads.ini \ 19 | && echo "post_max_size = ${PHP_POST_MAX_SIZE}" >> /usr/local/etc/php/conf.d/uploads.ini \ 20 | && echo "display_errors = Off" >> /usr/local/etc/php/conf.d/errors.ini 21 | 22 | # Instale o NGINX 23 | RUN apk add --no-cache nginx 24 | 25 | # Configure o NGINX 26 | COPY services/nginx/nginx.conf /etc/nginx/nginx.conf 27 | COPY services/nginx/default.conf /etc/nginx/conf.d/default.conf 28 | COPY app /app 29 | 30 | # Configure o NGINX para rodar com o PHP-FPM 31 | RUN mkdir -p /run/nginx 32 | 33 | # Exponha a porta 8080 34 | EXPOSE 8080 35 | 36 | # Comando para iniciar NGINX e PHP-FPM 37 | CMD ["sh", "-c", "php-fpm & nginx -g 'daemon off;'"] 38 | -------------------------------------------------------------------------------- /app/partials/header.php: -------------------------------------------------------------------------------- 1 | name)); ?> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 | 39 |
40 |
41 |
42 |
-------------------------------------------------------------------------------- /app/public/assets/css/login.css: -------------------------------------------------------------------------------- 1 | @import 'defaults.css'; 2 | 3 | * { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | body { 9 | font-family: Helvetica; 10 | font-size:13px; 11 | background-color:#edeef0; 12 | margin:0; 13 | padding-bottom:50px; 14 | } 15 | .container { 16 | max-width:1040px; 17 | margin:auto; 18 | } 19 | header { 20 | background-color:#4a76a8; 21 | height:45px; 22 | box-shadow: 0px 0px 4px #333; 23 | position:relative; 24 | z-index:99; 25 | } 26 | header .container { 27 | display:flex; 28 | height:inherit; 29 | justify-content: center; 30 | align-items: center; 31 | } 32 | header img { 33 | height:20px; 34 | } 35 | header a { 36 | display: flex; 37 | } 38 | 39 | form { 40 | max-width:400px; 41 | margin: auto; 42 | background-color: #FFF; 43 | border:1px solid #c9cacc; 44 | border-radius:5px; 45 | padding:20px; 46 | margin-top:50px; 47 | } 48 | form label { 49 | display: block; 50 | } 51 | form input { 52 | display: block; 53 | } 54 | form .input { 55 | font-size:15px; 56 | height:40px; 57 | width: 100%; 58 | outline: 0; 59 | border:0; 60 | background-color:#EEE; 61 | border-radius:10px; 62 | padding:15px; 63 | margin-bottom:10px; 64 | } 65 | form .input:focus { 66 | background-color:#DDD; 67 | } 68 | form .button { 69 | border:0; 70 | padding:10px 20px; 71 | background-color:#4a76a8; 72 | border-radius:10px; 73 | color:#FFF; 74 | font-size:15px; 75 | margin-bottom:10px; 76 | cursor:pointer; 77 | box-shadow:0px 0px 3px #999; 78 | } 79 | form .button:hover { 80 | background-color:#477dbb; 81 | } 82 | form a { 83 | text-decoration:none; 84 | color:#4a76a8; 85 | } -------------------------------------------------------------------------------- /app/public/assets/css/modal.css: -------------------------------------------------------------------------------- 1 | .modal { 2 | display: none; 3 | } 4 | 5 | .vanilla-modal .modal { 6 | display: block; 7 | position: fixed; 8 | content: ""; 9 | top: 0; 10 | left: 0; 11 | right: 0; 12 | bottom: 0; 13 | background: rgba(0, 0, 0, 0.6); 14 | z-index: -1; 15 | opacity: 0; 16 | transition: opacity 0.2s, z-index 0s 0.2s; 17 | text-align: center; 18 | overflow: hidden; 19 | overflow-y: auto; 20 | white-space: nowrap; 21 | -webkit-overflow-scrolling: touch; 22 | } 23 | 24 | .vanilla-modal .modal>* { 25 | display: inline-block; 26 | white-space: normal; 27 | vertical-align: middle; 28 | text-align: left; 29 | } 30 | 31 | .vanilla-modal .modal:before { 32 | display: inline-block; 33 | overflow: hidden; 34 | width: 0; 35 | height: 100%; 36 | vertical-align: middle; 37 | content: ""; 38 | } 39 | 40 | .vanilla-modal.modal-visible .modal { 41 | z-index: 99; 42 | opacity: 1; 43 | transition: opacity 0.2s; 44 | } 45 | 46 | .modal-inner { 47 | position: relative; 48 | overflow: hidden; 49 | max-width: 90%; 50 | max-height: 90%; 51 | overflow-x: hidden; 52 | overflow-y: auto; 53 | background: #fff; 54 | z-index: -1; 55 | opacity: 0; 56 | transform: scale(0); 57 | transition: opacity 0.2s, transform 0.2s, z-index 0s 0.2s; 58 | } 59 | 60 | .modal-visible .modal-inner { 61 | z-index: 100; 62 | opacity: 1; 63 | transform: scale(1); 64 | transition: opacity 0.2s, transform 0.2s; 65 | } 66 | 67 | [data-modal-close] { 68 | position: absolute; 69 | z-index: 2; 70 | right: 0; 71 | top: 0; 72 | width: 25px; 73 | height: 25px; 74 | line-height: 25px; 75 | font-size: 13px; 76 | cursor: pointer; 77 | text-align: center; 78 | background: #fff; 79 | box-shadow: -1px 1px 2px rgba(0, 0, 0, 0.2); 80 | } -------------------------------------------------------------------------------- /app/public/assets/js/script.js: -------------------------------------------------------------------------------- 1 | function setActiveTab(tab) { 2 | document.querySelectorAll('.tab-item').forEach(function(e){ 3 | if(e.getAttribute('data-for') == tab) { 4 | e.classList.add('active'); 5 | } else { 6 | e.classList.remove('active'); 7 | } 8 | }); 9 | } 10 | function showTab() { 11 | if(document.querySelector('.tab-item.active')) { 12 | let activeTab = document.querySelector('.tab-item.active').getAttribute('data-for'); 13 | document.querySelectorAll('.tab-body').forEach(function(e){ 14 | if(e.getAttribute('data-item') == activeTab) { 15 | e.style.display = 'block'; 16 | } else { 17 | e.style.display = 'none'; 18 | } 19 | }); 20 | } 21 | } 22 | 23 | if(document.querySelector('.tab-item')) { 24 | showTab(); 25 | document.querySelectorAll('.tab-item').forEach(function(e){ 26 | e.addEventListener('click', function(r) { 27 | setActiveTab( r.target.getAttribute('data-for') ); 28 | showTab(); 29 | }); 30 | }); 31 | } 32 | 33 | document.querySelector('.feed-new-input-placeholder').addEventListener('click', function(obj){ 34 | obj.target.style.display = 'none'; 35 | document.querySelector('.feed-new-input').style.display = 'block'; 36 | document.querySelector('.feed-new-input').focus(); 37 | document.querySelector('.feed-new-input').innerText = ''; 38 | }); 39 | 40 | document.querySelector('.feed-new-input').addEventListener('blur', function(obj) { 41 | let value = obj.target.innerText.trim(); 42 | if(value == '') { 43 | obj.target.style.display = 'none'; 44 | document.querySelector('.feed-new-input-placeholder').style.display = 'block'; 45 | } 46 | }); -------------------------------------------------------------------------------- /app/public/signup.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Login - Devsbook 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Já possui uma conta? Faça login 40 |
41 |
42 | 43 | 44 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/dao/PostCommentDaoMysql.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 13 | } 14 | 15 | public function addComment(PostComment $postComment) 16 | { 17 | $sql = $this->pdo->prepare("INSERT INTO post_comments ( 18 | id_post, id_user, body, created_at 19 | ) VALUES ( 20 | :id_post, :id_user, :body, :created_at 21 | )"); 22 | $sql->bindValue(':id_post', $postComment->idPost); 23 | $sql->bindValue(':id_user', $postComment->idUser); 24 | $sql->bindValue(':body', $postComment->body); 25 | $sql->bindValue(':created_at', $postComment->createdAt); 26 | $sql->execute(); 27 | } 28 | 29 | public function getComments(string $idPost) 30 | { 31 | $array = []; 32 | 33 | $sql = $this->pdo->prepare("SELECT * FROM post_comments WHERE id_post = :id_post"); 34 | $sql->bindValue(':id_post', $idPost); 35 | $sql->execute(); 36 | 37 | if ($sql->rowCount() > 0) { 38 | $data = $sql->fetchAll(PDO::FETCH_ASSOC); 39 | 40 | $userDao = new UserDaoMysql($this->pdo); 41 | 42 | foreach ($data as $item) { 43 | $commentItem = new PostComment(); 44 | $commentItem->id = $item['id']; 45 | $commentItem->idPost = $item['id_post']; 46 | $commentItem->idUser = $item['id_user']; 47 | $commentItem->body = $item['body']; 48 | $commentItem->createdAt = $item['created_at']; 49 | $commentItem->user = $userDao->findById($commentItem->idUser); 50 | 51 | $array[] = $commentItem; 52 | } 53 | } 54 | 55 | return $array; 56 | } 57 | 58 | public function deleteFromPost(string $idPost) 59 | { 60 | $sql = $this->pdo->prepare("DELETE FROM post_comments WHERE id_post = :id_post"); 61 | $sql->bindValue(':id_post', $idPost); 62 | $sql->execute(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/dao/PostLikeDaoMysql.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 12 | } 13 | 14 | public function getLikeCount(string $idPost) 15 | { 16 | $sql = $this->pdo->prepare("SELECT COUNT(*) as c FROM post_likes WHERE id_post = :id_post"); 17 | $sql->bindValue(':id_post', $idPost); 18 | $sql->execute(); 19 | 20 | $data = $sql->fetch(); 21 | 22 | return $data['c']; 23 | } 24 | 25 | public function isLiked(string $idPost, string $loggedUser) 26 | { 27 | 28 | $sql = $this->pdo->prepare( 29 | "SELECT * FROM post_likes WHERE id_post = :id_post AND id_user = :id_user" 30 | ); 31 | $sql->bindValue('id_post', $idPost); 32 | $sql->bindValue('id_user', $loggedUser); 33 | $sql->execute(); 34 | 35 | if ($sql->rowCount() > 0) { 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | public function likeToggle(string $idPost, string $idUser) 43 | { 44 | if ($this->isLiked($idPost, $idUser)) { 45 | $sql = $this->pdo->prepare( 46 | "DELETE FROM post_likes WHERE id_post = :id_post AND id_user = :id_user" 47 | ); 48 | $sql->bindValue(':id_post', $idPost); 49 | $sql->bindValue(':id_user', $idUser); 50 | $sql->execute(); 51 | } else { 52 | $sql = $this->pdo->prepare( 53 | "INSERT INTO post_likes ( 54 | id_post, id_user, created_at 55 | ) VALUES ( 56 | :id_post, :id_user, :created_at 57 | )" 58 | ); 59 | $sql->bindValue(':created_at', gmdate('Y-m-d H:i:s')); 60 | } 61 | 62 | $sql->bindValue(':id_post', $idPost); 63 | $sql->bindValue(':id_user', $idUser); 64 | $sql->execute(); 65 | } 66 | 67 | public function deleteFromPost(string $idPost) 68 | { 69 | $sql = $this->pdo->prepare("DELETE FROM post_likes WHERE id_post = :id_post"); 70 | $sql->bindValue(':id_post', $idPost); 71 | $sql->execute(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/public/signup_action.php: -------------------------------------------------------------------------------- 1 | = (new DateTime())->modify('-13 years')->getTimestamp()) { 33 | $_SESSION['flash'] = 'Você precisa ter pelo menos 13 anos para se inscrever'; 34 | 35 | header("Location: $base/signup"); 36 | exit; 37 | } 38 | 39 | if (strlen($password) < 8) { 40 | $_SESSION['flash'] = 'Sua senha precisa conter pelo menos 8 caracteres'; 41 | 42 | header("Location: $base/signup"); 43 | exit; 44 | } 45 | 46 | if ($password !== $passwordConfirmation) { 47 | $_SESSION['flash'] = 'As senhas não coincidem'; 48 | 49 | header("Location: $base/signup"); 50 | exit; 51 | } 52 | 53 | if ($auth->emailExists($email) === false) { 54 | $auth->registerUser($name, $email, $password, $birthdate); 55 | 56 | header("Location: $base"); 57 | exit; 58 | } else { 59 | $_SESSION['flash'] = 'E-mail já cadastrado'; 60 | 61 | header("Location: $base/signup"); 62 | exit; 63 | } 64 | } 65 | 66 | $_SESSION['flash'] = 'Preencha todos os campos corretamente'; 67 | header("Location: $base/signup"); 68 | exit; 69 | -------------------------------------------------------------------------------- /app/public/search.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | $activeMenu = ''; 9 | 10 | $userDao = new UserDaoMysql($pdo); 11 | 12 | $searchTerm = filter_input(INPUT_GET, 's', FILTER_SANITIZE_SPECIAL_CHARS); 13 | 14 | if (empty($searchTerm)) { 15 | header("Location: $base"); 16 | exit; 17 | } 18 | 19 | $userList = $userDao->findByName($searchTerm); 20 | 21 | require_once('../partials/header.php'); 22 | require_once('../partials/menu.php'); 23 | ?> 24 | 25 |
26 |
27 |
28 |
29 | 30 | name)[0]; ?> 31 | 41 | 42 |
43 |
44 |
45 |
46 |
47 |
Patrocínios
48 |
49 |
50 |
51 |
52 | 53 | 54 |
55 |
56 |
57 |
58 | Criado com ❤️ por @christopherldo 59 |
60 |
61 |
62 |
63 |
64 | 65 | -------------------------------------------------------------------------------- /app/public/ajax_upload.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | 9 | $maxWidth = 600; 10 | $maxHeight = 600; 11 | 12 | $array = ['error' => '']; 13 | 14 | $postDao = new PostDaoMysql($pdo); 15 | 16 | if (isset($_FILES['photo']) && empty($_FILES['photo']['tmp_name']) === false) { 17 | $photo = $_FILES['photo']; 18 | 19 | $acceptable = [ 20 | 'image/jpeg', 21 | 'image/jpg', 22 | 'image/png' 23 | ]; 24 | 25 | if (in_array($photo['type'], $acceptable)) { 26 | list($widthOrig, $heightOrig) = getimagesize($photo['tmp_name']); 27 | 28 | $ratio = $widthOrig / $heightOrig; 29 | $ratioMax = $maxWidth / $maxHeight; 30 | 31 | $newWidth = $maxWidth; 32 | $newHeight = $maxHeight; 33 | 34 | if($ratioMax > $ratio){ 35 | $newWidth = $newHeight * $ratio; 36 | } else { 37 | $newHeight = $newWidth / $ratio; 38 | } 39 | 40 | $finalImage = imagecreatetruecolor($newWidth, $newHeight); 41 | 42 | switch ($photo['type']) { 43 | case 'image/jpeg': 44 | case 'image/jpg': 45 | $image = imagecreatefromjpeg($photo['tmp_name']); 46 | break; 47 | case 'image/png': 48 | $image = imagecreatefrompng($photo['tmp_name']); 49 | break; 50 | } 51 | 52 | imagecopyresampled( 53 | $finalImage, 54 | $image, 55 | 0, 56 | 0, 57 | 0, 58 | 0, 59 | $newWidth, 60 | $newHeight, 61 | $widthOrig, 62 | $heightOrig 63 | ); 64 | 65 | $postId = $postDao->generateUuid(); 66 | $photoName = $postId . '.webp'; 67 | 68 | imagewebp($finalImage, "./media/uploads/$photoName"); 69 | 70 | $newPost = new Post(); 71 | $newPost->publicId = $postId; 72 | $newPost->idUser = $userInfo->publicId; 73 | $newPost->type = 'photo'; 74 | $newPost->createdAt = gmdate('Y-m-d H:i:s'); 75 | $newPost->body = $photoName; 76 | 77 | $postDao->insert($newPost); 78 | } else { 79 | $array['error'] = 'Extensão de arquivo não suportada.'; 80 | } 81 | } else { 82 | $array['error'] = 'A imagem não foi enviada.'; 83 | } 84 | 85 | header("Content-Type: application/json"); 86 | echo json_encode($array); 87 | exit; 88 | -------------------------------------------------------------------------------- /app/partials/menu.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/partials/feed-editor.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
O que você está pensando, ?
8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /app/dao/UserRelationDaoMysql.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 12 | } 13 | 14 | public function insert(UserRelation $userRelation) 15 | { 16 | $sql = $this->pdo->prepare("INSERT INTO user_relations ( 17 | user_from, user_to 18 | ) VALUES ( 19 | :user_from, :user_to 20 | )"); 21 | $sql->bindValue(':user_from', $userRelation->userFrom); 22 | $sql->bindValue(':user_to', $userRelation->userTo); 23 | $sql->execute(); 24 | } 25 | 26 | public function delete(UserRelation $userRelation) 27 | { 28 | $sql = $this->pdo->prepare( 29 | "DELETE FROM user_relations WHERE user_from = :user_from AND user_to = :user_to" 30 | ); 31 | $sql->bindValue(':user_from', $userRelation->userFrom); 32 | $sql->bindValue(':user_to', $userRelation->userTo); 33 | $sql->execute(); 34 | } 35 | 36 | public function getFollowing(string $publicId) 37 | { 38 | $users = []; 39 | 40 | $sql = $this->pdo->prepare( 41 | "SELECT user_to FROM user_relations WHERE user_from = :user_from" 42 | ); 43 | $sql->bindValue(':user_from', $publicId); 44 | $sql->execute(); 45 | 46 | if ($sql->rowCount() > 0) { 47 | $data = $sql->fetchAll(); 48 | 49 | foreach ($data as $item) { 50 | $users[] = $item['user_to']; 51 | } 52 | } 53 | 54 | return $users; 55 | } 56 | 57 | public function getFollowers(string $publicId) 58 | { 59 | $users = []; 60 | 61 | $sql = $this->pdo->prepare( 62 | "SELECT user_from FROM user_relations WHERE user_to = :user_to" 63 | ); 64 | $sql->bindValue(':user_to', $publicId); 65 | $sql->execute(); 66 | 67 | if ($sql->rowCount() > 0) { 68 | $data = $sql->fetchAll(); 69 | 70 | foreach ($data as $item) { 71 | $users[] = $item['user_from']; 72 | } 73 | } 74 | 75 | return $users; 76 | } 77 | 78 | public function isFollowing(string $idOne, string $idTwo) 79 | { 80 | $sql = $this->pdo->prepare( 81 | "SELECT * FROM user_relations WHERE user_from = :user_from AND user_to = :user_to" 82 | ); 83 | $sql->bindValue(':user_from', $idOne); 84 | $sql->bindValue(':user_to', $idTwo); 85 | $sql->execute(); 86 | 87 | return $sql->rowCount() > 0 ? true : false; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/partials/feed-item-script.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/public/assets/css/feed-item.css: -------------------------------------------------------------------------------- 1 | .feed-item-head { 2 | align-items: center; 3 | } 4 | .feed-item-head-photo { 5 | margin-right:15px; 6 | } 7 | .feed-item-head-photo img { 8 | width:50px; 9 | height:50px; 10 | border-radius:50%; 11 | } 12 | .fidi-name { 13 | color:#224b7a; 14 | font-size:16px; 15 | display:inline-block; 16 | margin-right:5px; 17 | margin-bottom:5px; 18 | } 19 | .fidi-action, 20 | .fidi-date { 21 | color:#999; 22 | font-size:14px; 23 | } 24 | 25 | .feed-item-head-info { 26 | flex:1; 27 | } 28 | .feed-item-head-btn { 29 | margin-right:5px; 30 | cursor: pointer; 31 | } 32 | .feed-item-head-btn img { 33 | width:20px; 34 | height:20px; 35 | } 36 | 37 | .feed-item-more-window{ 38 | position: absolute; 39 | background-color: #fff; 40 | width: 150px; 41 | padding: 5px; 42 | border: 1px solid #eee; 43 | border-radius: 3px; 44 | margin-left: -130px; 45 | display: none; 46 | } 47 | 48 | .feed-item-more-window a{ 49 | display: block; 50 | padding: 7px; 51 | text-decoration: none; 52 | color: #000; 53 | transition: all 0.2s ease-in-out 0s; 54 | } 55 | 56 | .feed-item-more-window a:hover{ 57 | background-color: #ccc; 58 | } 59 | 60 | .feed-item-body { 61 | color:#333; 62 | line-height: 20px; 63 | font-size:14px; 64 | } 65 | .feed-item-body img { 66 | max-width:100%; 67 | } 68 | 69 | .feed-item-buttons { 70 | border-top:1px solid #EEE; 71 | padding:10px 0; 72 | } 73 | 74 | .like-btn, 75 | .msg-btn { 76 | display: flex; 77 | align-items: center; 78 | cursor: pointer; 79 | height:25px; 80 | background-size: 25px; 81 | background-position: left; 82 | background-repeat: no-repeat; 83 | padding-left:35px; 84 | margin-right:20px; 85 | font-size:15px; 86 | } 87 | .like-btn { 88 | background-image: url('../images/heart-off.png'); 89 | color:#5F7D95; 90 | } 91 | .like-btn.on { 92 | background-image: url('../images/heart-on.png'); 93 | color:#FB5252; 94 | } 95 | 96 | .msg-btn { 97 | background-image: url('../images/comment.png'); 98 | color:#5F7D95; 99 | } 100 | 101 | .feed-item-comments { 102 | border-top:1px solid #EEE; 103 | } 104 | 105 | .fic-item-photo { 106 | margin-right: 10px; 107 | } 108 | .fic-item-photo img { 109 | width:35px; 110 | height:35px; 111 | border-radius:50%; 112 | } 113 | .fic-item-info a { 114 | display:block; 115 | margin-bottom:5px; 116 | color:#224b7a; 117 | font-size:14px; 118 | text-decoration: none; 119 | } 120 | .fic-item-info { 121 | color:#333; 122 | font-size:13px; 123 | } 124 | .fic-item-field { 125 | flex:1; 126 | height:35px; 127 | border-radius:20px; 128 | border:0; 129 | outline:0; 130 | background-color:#eee; 131 | font-size:13px; 132 | padding:0 15px; 133 | } -------------------------------------------------------------------------------- /app/public/configuracoes.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | $activeMenu = 'config'; 9 | 10 | $userDao = new UserDaoMysql($pdo); 11 | 12 | require_once('../partials/header.php'); 13 | require_once('../partials/menu.php'); 14 | ?> 15 | 16 |
17 |

Configurações

18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 31 | 37 | 38 |
39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 59 | 60 | 64 | 65 |
66 | 70 | 71 | 72 | 76 | 77 | 81 | 82 | *Campos obrigatórios
83 | 84 |
85 | 86 | 87 |
88 |
89 | 90 | 91 | 98 | 99 | -------------------------------------------------------------------------------- /app/public/index.php: -------------------------------------------------------------------------------- 1 | checkToken(); 9 | 10 | $page = filter_input(INPUT_GET, 'p', FILTER_VALIDATE_INT); 11 | 12 | if ($page === null || $page < 1) { 13 | $page = 1; 14 | } 15 | 16 | $activeMenu = 'home'; 17 | 18 | $postDao = new PostDaoMysql($pdo); 19 | 20 | $info = $postDao->getHomeFeed($userInfo->publicId, $page); 21 | 22 | $feed = $info['feed'] ?? null; 23 | $pages = $info['pages']; 24 | $currentPage = $info['currentPage']; 25 | 26 | require_once('../partials/header.php'); 27 | require_once('../partials/menu.php'); 28 | ?> 29 | 30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 1) : ?> 42 |
43 | 44 | 45 | 46 |
47 | 48 | 49 |
50 |
51 |
52 |
53 |
Dica:
54 |
55 |
56 |
57 | 60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/partials/feed-item.php: -------------------------------------------------------------------------------- 1 | type) { 9 | case 'text': 10 | $actionPhrase = 'fez um post'; 11 | $body = str_replace(' ', '
', $item->body); 12 | break; 13 | case 'photo': 14 | $actionPhrase = 'postou uma foto'; 15 | $body = '' . $item->body . ''; 16 | break; 17 | } 18 | 19 | $createdAt = date('n/d/Y H:i:m', strtotime($item->createdAt)); 20 | ?> 21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 | user->name ?> 33 | 34 | 35 |
36 | 37 |
38 | mine) : ?> 39 |
40 | 41 |
42 | Excluir Post 43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 |
comments) ?>
53 |
54 |
55 |
56 | comments as $comment) : ?> 57 |
58 |
59 | 60 | avatar 61 | 62 |
63 |
64 | 65 | user->name ?> 66 | 67 | body ?> 68 |
69 |
70 | 71 |
72 |
73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 |
-------------------------------------------------------------------------------- /app/public/fotos.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | $activeMenu = 'photos'; 9 | 10 | $publicId = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_SPECIAL_CHARS); 11 | 12 | if ($publicId === null) { 13 | $publicId = $userInfo->publicId; 14 | } 15 | 16 | if ($publicId !== $userInfo->publicId) { 17 | $activeMenu = ''; 18 | } 19 | 20 | $postDao = new PostDaoMysql($pdo); 21 | $userDao = new UserDaoMysql($pdo); 22 | 23 | $user = $userDao->findById($publicId, true); 24 | 25 | if ($user === false) { 26 | header("Location: $base"); 27 | exit; 28 | } 29 | 30 | $datefrom = new DateTime($user->birthdate); 31 | $dateTo = new DateTime('today'); 32 | 33 | $user->ageYears = $datefrom->diff($dateTo)->y; 34 | 35 | require_once('../partials/header.php'); 36 | require_once('../partials/menu.php'); 37 | ?> 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
name ?>
51 | city) === false) : ?> 52 |
city ?>
53 | 54 |
55 |
56 |
57 |
followers) ?>
58 |
Seguidores
59 |
60 |
61 |
following) ?>
62 |
Seguindo
63 |
64 |
65 |
photos) ?>
66 |
Fotos
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | photos as $key => $item) : ?> 79 |
80 | 81 | 82 | 83 | 86 |
87 | 88 | photos) === 0) : ?> 89 | Esse usuário ainda não postou uma foto :/ 90 | 91 |
92 |
93 |
94 |
95 |
96 |
97 | 98 | 103 | 104 | -------------------------------------------------------------------------------- /app/dao/UserDaoMysql.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 13 | } 14 | 15 | private function generateUser(array $array, bool $full = false) 16 | { 17 | $user = new User(); 18 | 19 | $user->publicId = $array['public_id'] ?? ''; 20 | $user->email = $array['email'] ?? ''; 21 | $user->password = $array['password'] ?? ''; 22 | $user->salt = $array['salt'] ?? ''; 23 | $user->name = $array['name'] ?? ''; 24 | $user->birthdate = $array['birthdate'] ?? ''; 25 | $user->city = $array['city'] ?? ''; 26 | $user->work = $array['work'] ?? ''; 27 | $user->avatar = $array['avatar'] ?? ''; 28 | $user->cover = $array['cover'] ?? ''; 29 | 30 | if ($full) { 31 | $userRelationDaoMysql = new UserRelationDaoMysql($this->pdo); 32 | $postDaoMysql = new PostDaoMysql($this->pdo); 33 | 34 | $user->followers = $userRelationDaoMysql->getFollowers($user->publicId); 35 | foreach ($user->followers as $key => $followerId) { 36 | $newUser = $this->findById($followerId); 37 | $user->followers[$key] = $newUser; 38 | } 39 | 40 | $user->following = $userRelationDaoMysql->getFollowing($user->publicId); 41 | foreach ($user->following as $key => $followerId) { 42 | $newUser = $this->findById($followerId); 43 | $user->following[$key] = $newUser; 44 | } 45 | 46 | $user->photos = $postDaoMysql->getPhotosFrom($user->publicId); 47 | } 48 | 49 | return $user; 50 | } 51 | 52 | public function findById(string $publicId, bool $full = false) 53 | { 54 | if (empty($publicId) === false) { 55 | $sql = $this->pdo->prepare("SELECT * FROM users WHERE public_id = :public_id"); 56 | $sql->bindValue(':public_id', $publicId); 57 | $sql->execute(); 58 | 59 | if ($sql->rowCount() > 0) { 60 | $data = $sql->fetch(PDO::FETCH_ASSOC); 61 | 62 | $user = $this->generateUser($data, $full); 63 | 64 | return $user; 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | public function findByEmail(string $email) 71 | { 72 | if (empty($email) === false) { 73 | $sql = $this->pdo->prepare("SELECT * FROM users WHERE email = :email"); 74 | $sql->bindValue(':email', $email); 75 | $sql->execute(); 76 | 77 | if ($sql->rowCount() > 0) { 78 | $data = $sql->fetch(PDO::FETCH_ASSOC); 79 | 80 | $user = $this->generateUser($data); 81 | 82 | return $user; 83 | } 84 | } 85 | return false; 86 | } 87 | 88 | public function findBySalt(string $salt) 89 | { 90 | if (empty($salt) === false) { 91 | $sql = $this->pdo->prepare("SELECT * FROM users WHERE salt = :salt"); 92 | $sql->bindValue(':salt', $salt); 93 | $sql->execute(); 94 | 95 | return $sql->rowCount() > 0 ? true : false; 96 | } 97 | } 98 | 99 | public function findByName(string $name) 100 | { 101 | $array = []; 102 | 103 | if (empty($name) === false) { 104 | $sql = $this->pdo->prepare("SELECT * FROM users WHERE name LIKE :name"); 105 | $sql->bindValue(':name', '%' . $name . '%'); 106 | $sql->execute(); 107 | 108 | if ($sql->rowCount() > 0) { 109 | $data = $sql->fetchAll(PDO::FETCH_ASSOC); 110 | 111 | foreach ($data as $item) { 112 | $array[] = $this->generateUser($item); 113 | } 114 | } 115 | } 116 | return $array; 117 | } 118 | 119 | public function insert(User $user) 120 | { 121 | $sql = $this->pdo->prepare( 122 | "INSERT INTO users ( 123 | public_id, email, password, salt, name, birthdate 124 | ) VALUES ( 125 | :public_id, :email, :password, :salt, :name, :birthdate 126 | )" 127 | ); 128 | $sql->bindValue(':public_id', $user->publicId); 129 | $sql->bindValue(':email', $user->email); 130 | $sql->bindValue(':password', $user->password); 131 | $sql->bindValue(':salt', $user->salt); 132 | $sql->bindValue(':name', $user->name); 133 | $sql->bindValue(':birthdate', $user->birthdate); 134 | $sql->execute(); 135 | } 136 | 137 | public function update(User $user) 138 | { 139 | $sql = $this->pdo->prepare( 140 | "UPDATE users SET email = :email, password = :password, name = :name, 141 | birthdate = :birthdate, city = :city, work = :work, avatar = :avatar, cover = :cover 142 | WHERE public_id = :public_id" 143 | ); 144 | $sql->bindValue(':email', $user->email); 145 | $sql->bindValue(':password', $user->password); 146 | $sql->bindValue(':name', $user->name); 147 | $sql->bindValue(':birthdate', $user->birthdate); 148 | $sql->bindValue(':city', $user->city); 149 | $sql->bindValue(':work', $user->work); 150 | $sql->bindValue(':avatar', $user->avatar); 151 | $sql->bindValue(':cover', $user->cover); 152 | $sql->bindValue(':public_id', $user->publicId); 153 | $sql->execute(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /app/public/amigos.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | $activeMenu = 'friends'; 9 | 10 | $publicId = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_SPECIAL_CHARS); 11 | 12 | if ($publicId === null) { 13 | $publicId = $userInfo->publicId; 14 | } 15 | 16 | if ($publicId !== $userInfo->publicId) { 17 | $activeMenu = ''; 18 | } 19 | 20 | $postDao = new PostDaoMysql($pdo); 21 | $userDao = new UserDaoMysql($pdo); 22 | 23 | $user = $userDao->findById($publicId, true); 24 | 25 | if ($user === false) { 26 | header("Location: $base"); 27 | exit; 28 | } 29 | 30 | $datefrom = new DateTime($user->birthdate); 31 | $dateTo = new DateTime('today'); 32 | 33 | $user->ageYears = $datefrom->diff($dateTo)->y; 34 | 35 | require_once('../partials/header.php'); 36 | require_once('../partials/menu.php'); 37 | ?> 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
name ?>
51 | city) === false) : ?> 52 |
city ?>
53 | 54 |
55 |
56 |
57 |
followers) ?>
58 |
Seguidores
59 |
60 |
61 |
following) ?>
62 |
Seguindo
63 |
64 |
65 |
photos) ?>
66 |
Fotos
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | followers) > 0) : ?> 79 |
80 | Seguidores 81 |
82 | 83 | following) > 0) : ?> 84 |
85 | Seguindo 86 |
87 | 88 |
89 |
90 |
91 |
92 | followers as $item) : ?> 93 | name)[0]; ?> 94 | 104 | 105 |
106 |
107 |
108 |
109 | following as $item) : ?> 110 | name)[0]; ?> 111 | 121 | 122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | 131 | -------------------------------------------------------------------------------- /app/models/Auth.php: -------------------------------------------------------------------------------- 1 | secret = getenv("TOKEN_SECRET"); 20 | 21 | $this->pdo = $pdo; 22 | $this->base = $base; 23 | $this->dao = new UserDaoMysql($this->pdo); 24 | } 25 | 26 | public function checkToken() 27 | { 28 | if (isset($_SESSION['token'])) { 29 | $token = $_SESSION['token']; 30 | 31 | $tokenParts = explode('.', $token); 32 | 33 | if (count($tokenParts) === 3) { 34 | if ($this->validateToken($token) === self::TOKEN_VALID) { 35 | $payload = json_decode($this->getPayload($token)); 36 | 37 | $publicId = $payload->publicId ?? null; 38 | 39 | if ($publicId) { 40 | $user = $this->dao->findById($publicId); 41 | 42 | if ($user) { 43 | return $user; 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | header("Location: $this->base/login"); 51 | exit; 52 | } 53 | 54 | public function validateToken(string $token) 55 | { 56 | $tokenParts = explode('.', $token); 57 | 58 | $tokenHeader = base64_decode($tokenParts[0]); 59 | $tokenPayload = base64_decode($tokenParts[1]); 60 | $signatureProvided = $tokenParts[2]; 61 | $expiration = json_decode($tokenPayload)->exp ?? null; 62 | $publicId = json_decode($tokenPayload)->publicId ?? null; 63 | 64 | if ($expiration && $publicId) { 65 | if ($this->isTokenExpired(json_decode($tokenPayload)->exp)) { 66 | return self::TOKEN_EXPIRED; 67 | } 68 | } else { 69 | return self::TOKEN_EXPIRED; 70 | } 71 | 72 | $base64UrlHeader = $this->base64UrlEncode($tokenHeader); 73 | $base64UrlPayload = $this->base64UrlEncode($tokenPayload); 74 | $signature = $this->getSignature($base64UrlHeader, $base64UrlPayload); 75 | $base64UrlSignature = $this->base64UrlEncode($signature); 76 | 77 | if (($base64UrlSignature === $signatureProvided) === false) { 78 | return self::TOKEN_INVALID_SIGNATURE; 79 | } 80 | 81 | return self::TOKEN_VALID; 82 | } 83 | 84 | protected function isTokenExpired(int $expireTime = 0) 85 | { 86 | $now = (new DateTime('now'))->getTimestamp(); 87 | return ($expireTime - $now < 0); 88 | } 89 | 90 | protected function getSecret() 91 | { 92 | return $this->secret; 93 | } 94 | 95 | protected function getSignature(string $header, string $payload) 96 | { 97 | return hash_hmac( 98 | 'sha256', 99 | "{$header}.{$payload}", 100 | $this->getSecret(), 101 | true 102 | ); 103 | } 104 | 105 | public function getPayload(string $token) 106 | { 107 | $tokenParts = explode('.', $token); 108 | $tokenPayload = base64_decode($tokenParts[1]); 109 | 110 | return $tokenPayload; 111 | } 112 | 113 | public function base64UrlEncode(string $text) 114 | { 115 | return str_replace( 116 | ['+', '/', '='], 117 | ['-', '_', ''], 118 | base64_encode($text) 119 | ); 120 | } 121 | 122 | public function validateLogin(string $email, string $password) 123 | { 124 | $user = $this->dao->findByEmail($email); 125 | 126 | if ($user) { 127 | $salt = $user->salt; 128 | $hash = hash('sha256', $password . $salt); 129 | 130 | if ($hash === $user->password) { 131 | $token = $this->generateToken($user->publicId); 132 | 133 | $_SESSION['token'] = $token; 134 | 135 | return true; 136 | } 137 | } 138 | 139 | return false; 140 | } 141 | 142 | private function generateToken(string $publicId) 143 | { 144 | $payload = [ 145 | 'publicId' => $publicId, 146 | 'exp' => ((new DateTime())->modify('+1 week')->getTimestamp()) 147 | ]; 148 | 149 | $base64UrlHeader = $this->base64UrlEncode($this->setHeader()); 150 | $base64UrlPayload = $this->base64UrlEncode($this->setPayload($payload)); 151 | $base64UrlSignature = $this->base64UrlEncode($this->getSignature($base64UrlHeader, $base64UrlPayload)); 152 | 153 | $token = "{$base64UrlHeader}.{$base64UrlPayload}.{$base64UrlSignature}"; 154 | return $token; 155 | } 156 | 157 | protected function setHeader() 158 | { 159 | return json_encode([ 160 | 'typ' => 'JWT', 161 | 'alg' => 'HS256' 162 | ]); 163 | } 164 | 165 | protected function setPayload(array $payload) 166 | { 167 | return json_encode($payload); 168 | } 169 | 170 | public function emailExists(string $email) 171 | { 172 | return $this->dao->findByEmail($email) ? true : false; 173 | } 174 | 175 | public function registerUser(string $name, string $email, string $password, string $birthdate) 176 | { 177 | do { 178 | $salt = $this->generateSalt(); 179 | } while ($this->dao->findBySalt($salt)); 180 | 181 | do { 182 | $publicId = $this->generateUuid(); 183 | } while ($this->dao->findById($publicId)); 184 | 185 | $hash = hash('sha256', $password . $salt); 186 | 187 | $newUser = new User(); 188 | $newUser->publicId = $publicId; 189 | $newUser->email = $email; 190 | $newUser->password = $hash; 191 | $newUser->salt = $salt; 192 | $newUser->name = $name; 193 | $newUser->birthdate = $birthdate; 194 | 195 | $this->dao->insert($newUser); 196 | 197 | $token = $this->generateToken($publicId); 198 | 199 | $_SESSION['token'] = $token; 200 | } 201 | 202 | private function generateSalt(int $length = 64) 203 | { 204 | $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . 'abcdefghijklmnopqrstuvwxyz' . 205 | '0123456789' . '`-=~!@#$%^&*()_+,./<>?;:[]{}\|'; 206 | $salt = ''; 207 | 208 | $max = strlen($chars) - 1; 209 | 210 | for ($i = 0; $i < $length; $i++) { 211 | $salt .= $chars[random_int(0, $max)]; 212 | } 213 | 214 | return $salt; 215 | } 216 | 217 | private function generateUuid() 218 | { 219 | return sprintf( 220 | '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', 221 | mt_rand(0, 0xffff), 222 | mt_rand(0, 0xffff), 223 | mt_rand(0, 0xffff), 224 | mt_rand(0, 0x0fff) | 0x4000, 225 | mt_rand(0, 0x3fff) | 0x8000, 226 | mt_rand(0, 0xffff), 227 | mt_rand(0, 0xffff), 228 | mt_rand(0, 0xffff) 229 | ); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /app/public/configuracoes_action.php: -------------------------------------------------------------------------------- 1 | checkToken(); 8 | 9 | $userDao = new UserDaoMysql($pdo); 10 | 11 | $name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS); 12 | $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); 13 | $birthdate = filter_input(INPUT_POST, 'birthdate'); 14 | $city = filter_input(INPUT_POST, 'city', FILTER_SANITIZE_SPECIAL_CHARS); 15 | $work = filter_input(INPUT_POST, 'work', FILTER_SANITIZE_SPECIAL_CHARS); 16 | $lastPassword = filter_input(INPUT_POST, 'last-password'); 17 | $password = filter_input(INPUT_POST, 'password'); 18 | $passwordConfirmation = filter_input(INPUT_POST, 'password-confirmation'); 19 | 20 | if ($name && $email && $birthdate) { 21 | $userInfo->name = $name; 22 | $userInfo->city = $city; 23 | $userInfo->work = $work; 24 | 25 | // E-MAIL 26 | if ($userInfo->email !== $email) { 27 | if ($userDao->findByEmail($email) === false) { 28 | $userInfo->email = $email; 29 | } else { 30 | $_SESSION['flash'] = 'E-mail já cadastrado'; 31 | 32 | header("Location: $base/configuracoes"); 33 | exit; 34 | } 35 | } 36 | 37 | // BIRTHDATE 38 | $birthdate = explode('/', $birthdate); 39 | 40 | if (count($birthdate) !== 3) { 41 | $_SESSION['flash'] = 'Data de nascimento inválida'; 42 | header("Location: $base/configuracoes"); 43 | exit; 44 | } 45 | 46 | $birthdate = $birthdate[2] . '-' . $birthdate[1] . '-' . $birthdate[0]; 47 | 48 | if (strtotime($birthdate) === false) { 49 | $_SESSION['flash'] = 'Data de nascimento inválida'; 50 | 51 | header("Location: $base/configuracoes"); 52 | exit; 53 | } 54 | 55 | if (strtotime($birthdate) >= (new DateTime())->modify('-13 years')->getTimestamp()) { 56 | $_SESSION['flash'] = 'Você precisa ter pelo menos 13 anos'; 57 | 58 | header("Location: $base/configuracoes"); 59 | exit; 60 | } 61 | 62 | $userInfo->birthdate = $birthdate; 63 | 64 | // PASSWORD 65 | if (empty($password) === false) { 66 | if (empty($lastPassword) === false) { 67 | $lasthash = hash('sha256', $lastPassword . $userInfo->salt); 68 | 69 | if ($lasthash === $userInfo->password) { 70 | if ($password === $passwordConfirmation) { 71 | $hash = hash('sha256', $password . $userInfo->salt); 72 | $userInfo->password = $hash; 73 | } else { 74 | $_SESSION['flash'] = 'Senhas não coincidem'; 75 | 76 | header("Location: $base/configuracoes"); 77 | exit; 78 | } 79 | } else { 80 | $_SESSION['flash'] = 'Senha antiga não coincide'; 81 | 82 | header("Location: $base/configuracoes"); 83 | exit; 84 | } 85 | } else { 86 | $_SESSION['flash'] = 'Preencha a senha antiga se quiser alterar a senha'; 87 | 88 | header("Location: $base/configuracoes"); 89 | exit; 90 | } 91 | } 92 | 93 | // AVATAR 94 | if (isset($_FILES['avatar']) && empty($_FILES['avatar']['tmp_name'] === false)) { 95 | $newAvatar = $_FILES['avatar']; 96 | 97 | if ($_FILES['avatar']['error'] === 0) { 98 | $acceptable = [ 99 | 'image/jpeg', 100 | 'image/jpg', 101 | 'image/png' 102 | ]; 103 | 104 | if (in_array($newAvatar['type'], $acceptable)) { 105 | $avatarWidth = 200; 106 | $avatarHeight = 200; 107 | 108 | list($witdhOrig, $heightOrig) = getimagesize($newAvatar['tmp_name']); 109 | $ratio = $witdhOrig / $heightOrig; 110 | 111 | $newWidth = $avatarWidth; 112 | $newHeight = $newWidth / $ratio; 113 | 114 | if ($newHeight < $avatarHeight) { 115 | $newHeight = $avatarHeight; 116 | $newWidth = $newHeight * $ratio; 117 | } 118 | 119 | $x = ($avatarWidth - $newWidth) / 2; 120 | $y = ($avatarHeight - $newHeight) / 2; 121 | 122 | $finalImage = imagecreatetruecolor($avatarWidth, $avatarHeight); 123 | 124 | switch ($newAvatar['type']) { 125 | case 'image/jpeg': 126 | case 'image/jpg': 127 | $image = imagecreatefromjpeg($newAvatar['tmp_name']); 128 | break; 129 | case 'image/png': 130 | $image = imagecreatefrompng($newAvatar['tmp_name']); 131 | break; 132 | } 133 | 134 | imagecopyresampled( 135 | $finalImage, 136 | $image, 137 | $x, 138 | $y, 139 | 0, 140 | 0, 141 | $newWidth, 142 | $newHeight, 143 | $witdhOrig, 144 | $heightOrig 145 | ); 146 | 147 | $avatarName = $userInfo->publicId . '.webp'; 148 | 149 | imagewebp($finalImage, './media/avatars/' . $avatarName, 100); 150 | 151 | $userInfo->avatar = $avatarName; 152 | } else { 153 | $_SESSION['flash'] = 'Formato de imagem não aceita'; 154 | 155 | header("Location: $base/configuracoes"); 156 | } 157 | } 158 | } 159 | 160 | // COVER 161 | if (isset($_FILES['cover']) && empty($_FILES['cover']['tmp_name'] === false)) { 162 | $newCover = $_FILES['cover']; 163 | 164 | if ($_FILES['cover']['error'] === 0) { 165 | $acceptable = [ 166 | 'image/jpeg', 167 | 'image/jpg', 168 | 'image/png' 169 | ]; 170 | 171 | if (in_array($newCover['type'], $acceptable)) { 172 | $coverWidth = 850; 173 | $coverHeight = 313; 174 | 175 | list($witdhOrig, $heightOrig) = getimagesize($newCover['tmp_name']); 176 | $ratio = $witdhOrig / $heightOrig; 177 | 178 | $newWidth = $coverWidth; 179 | $newHeight = $newWidth / $ratio; 180 | 181 | if ($newHeight < $coverHeight) { 182 | $newHeight = $coverHeight; 183 | $newWidth = $newHeight * $ratio; 184 | } 185 | 186 | $x = ($coverWidth - $newWidth) / 2; 187 | $y = ($coverHeight - $newHeight) / 2; 188 | 189 | $finalImage = imagecreatetruecolor($coverWidth, $coverHeight); 190 | 191 | switch ($newCover['type']) { 192 | case 'image/jpeg': 193 | case 'image/jpg': 194 | $image = imagecreatefromjpeg($newCover['tmp_name']); 195 | break; 196 | case 'image/png': 197 | $image = imagecreatefrompng($newCover['tmp_name']); 198 | break; 199 | } 200 | 201 | imagecopyresampled( 202 | $finalImage, 203 | $image, 204 | $x, 205 | $y, 206 | 0, 207 | 0, 208 | $newWidth, 209 | $newHeight, 210 | $witdhOrig, 211 | $heightOrig 212 | ); 213 | 214 | $coverName = $userInfo->publicId . '.webp'; 215 | 216 | imagewebp($finalImage, './media/covers/' . $coverName, 100); 217 | 218 | $userInfo->cover = $coverName; 219 | } else { 220 | $_SESSION['flash'] = 'Formato de imagem não aceita'; 221 | 222 | header("Location: $base/configuracoes"); 223 | } 224 | } 225 | } 226 | $userDao->update($userInfo); 227 | 228 | $_SESSION['flash'] = 'Configurações atualizadas com sucesso'; 229 | } 230 | 231 | header("Location: $base/configuracoes"); 232 | exit; 233 | -------------------------------------------------------------------------------- /app/dao/PostDaoMysql.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 16 | } 17 | 18 | public function insert(Post $post) 19 | { 20 | $sql = $this->pdo->prepare( 21 | "INSERT INTO posts ( 22 | public_id, id_user, type, created_at, body 23 | ) VALUES ( 24 | :public_id, :id_user, :type, :created_at, :body 25 | )" 26 | ); 27 | $sql->bindValue(':public_id', $post->publicId); 28 | $sql->bindValue(':id_user', $post->idUser); 29 | $sql->bindValue(':type', $post->type); 30 | $sql->bindValue(':created_at', $post->createdAt); 31 | $sql->bindValue(':body', $post->body); 32 | $sql->execute(); 33 | } 34 | 35 | public function delete(string $postId, string $userId) 36 | { 37 | $postLikeDao = new PostLikeDaoMysql($this->pdo); 38 | $postCommentDao = new PostCommentDaoMysql($this->pdo); 39 | 40 | $post = $this->findById($postId); 41 | 42 | if ($post && $post[0]->user->publicId === $userId) { 43 | $sql = $this->pdo->prepare( 44 | "DELETE FROM posts WHERE public_id = :public_id AND id_user = :id_user" 45 | ); 46 | $sql->bindValue(':public_id', $postId); 47 | $sql->bindValue(':id_user', $userId); 48 | $sql->execute(); 49 | 50 | $postLikeDao->deleteFromPost($postId); 51 | $postCommentDao->deleteFromPost($postId); 52 | 53 | if ($post[0]->type === 'photo') { 54 | unlink('./media/uploads/' . $post[0]->body); 55 | } 56 | } 57 | } 58 | 59 | public function getHomeFeed(string $publicId, int $page = 1) 60 | { 61 | $array = []; 62 | $perPage = 10; 63 | 64 | $offset = ($page - 1) * $perPage; 65 | 66 | $userRelationDao = new UserRelationDaoMysql($this->pdo); 67 | $userList = $userRelationDao->getFollowing($publicId); 68 | 69 | $userList[] = $publicId; 70 | 71 | $sqlString = "SELECT * FROM posts WHERE id_user IN ("; 72 | $counter = 1; 73 | $userListLenght = count($userList); 74 | 75 | foreach ($userList as $user) { 76 | if ($counter < $userListLenght) { 77 | $sqlString .= "'$user', "; 78 | } else { 79 | $sqlString .= "'$user'"; 80 | } 81 | $counter++; 82 | } 83 | 84 | $sqlString .= ") ORDER BY created_at DESC LIMIT $perPage OFFSET $offset"; 85 | 86 | $sql = $this->pdo->query($sqlString); 87 | 88 | if ($sql && $sql->rowCount() > 0) { 89 | $data = $sql->fetchAll(PDO::FETCH_ASSOC); 90 | 91 | $array['feed'] = $this->postListToObject($data, $publicId, $publicId); 92 | } 93 | 94 | $sqlString = "SELECT COUNT(*) as c FROM posts WHERE id_user IN ("; 95 | $counter = 1; 96 | $userListLenght = count($userList); 97 | 98 | foreach ($userList as $user) { 99 | if ($counter < $userListLenght) { 100 | $sqlString .= "'$user', "; 101 | } else { 102 | $sqlString .= "'$user'"; 103 | } 104 | $counter++; 105 | } 106 | 107 | $sqlString .= ')'; 108 | 109 | $sql = $this->pdo->query($sqlString); 110 | $totalData = $sql->fetch(); 111 | $total = $totalData['c']; 112 | 113 | $array['pages'] = ceil($total / $perPage); 114 | $array['currentPage'] = $page; 115 | 116 | return $array; 117 | } 118 | 119 | public function getUserFeed(string $publicId, string $loggedUser, int $page = 1) 120 | { 121 | $array = ['feed' => []]; 122 | $perPage = 10; 123 | 124 | $offset = ($page - 1) * $perPage; 125 | 126 | $sql = $this->pdo->prepare( 127 | "SELECT * FROM posts WHERE id_user = :id_user ORDER BY created_at DESC 128 | LIMIT $perPage OFFSET $offset" 129 | ); 130 | $sql->bindValue(':id_user', $publicId); 131 | $sql->execute(); 132 | 133 | if ($sql && $sql->rowCount() > 0) { 134 | $data = $sql->fetchAll(PDO::FETCH_ASSOC); 135 | 136 | $array['feed'] = $this->postListToObject($data, $publicId, $loggedUser); 137 | } 138 | 139 | $sql = $this->pdo->prepare("SELECT COUNT(*) as c FROM posts WHERE id_user = :id_user"); 140 | $sql->bindValue(':id_user', $publicId); 141 | $sql->execute(); 142 | 143 | $totalData = $sql->fetch(); 144 | $total = $totalData['c']; 145 | 146 | $array['pages'] = ceil($total / $perPage); 147 | $array['currentPage'] = $page; 148 | 149 | return $array; 150 | } 151 | 152 | public function getPhotosFrom(string $publicId) 153 | { 154 | $array = []; 155 | 156 | $sql = $this->pdo->prepare( 157 | "SELECT * FROM posts where id_user = :id_user AND type = 'photo' 158 | ORDER BY created_at DESC" 159 | ); 160 | $sql->bindValue(':id_user', $publicId); 161 | $sql->execute(); 162 | 163 | if ($sql->rowCount() > 0) { 164 | $data = $sql->fetchAll(PDO::FETCH_ASSOC); 165 | 166 | $array = $this->postListToObject($data, $publicId, $publicId); 167 | } 168 | 169 | return $array; 170 | } 171 | 172 | private function postListToObject(array $postList, string $publicId, string $loggedUser) 173 | { 174 | $posts = []; 175 | $userDao = new UserDaoMysql($this->pdo); 176 | $postLikeDao = new PostLikeDaoMysql($this->pdo); 177 | $postCommentDaoMysql = new PostCommentDaoMysql($this->pdo); 178 | 179 | foreach ($postList as $postItem) { 180 | $newPost = new Post(); 181 | $newPost->publicId = $postItem['public_id']; 182 | $newPost->type = $postItem['type']; 183 | $newPost->createdAt = $postItem['created_at']; 184 | $newPost->body = $postItem['body']; 185 | $newPost->mine = false; 186 | 187 | if ($postItem['id_user'] === $loggedUser) { 188 | $newPost->mine = true; 189 | } 190 | 191 | $newPost->user = $userDao->findById($postItem['id_user']); 192 | 193 | $newPost->likeCount = $postLikeDao->getLikeCount($newPost->publicId); 194 | $newPost->liked = $postLikeDao->isLiked($newPost->publicId, $loggedUser); 195 | 196 | $newPost->comments = $postCommentDaoMysql->getComments($newPost->publicId); 197 | 198 | $posts[] = $newPost; 199 | } 200 | 201 | return $posts; 202 | } 203 | 204 | public function findById(string $publicId) 205 | { 206 | if (empty($publicId) === false) { 207 | $sql = $this->pdo->prepare("SELECT * FROM posts WHERE public_id = :public_id"); 208 | $sql->bindValue(':public_id', $publicId); 209 | $sql->execute(); 210 | 211 | if ($sql->rowCount() > 0) { 212 | $data = $sql->fetchAll(PDO::FETCH_ASSOC); 213 | 214 | $array = $this->postListToObject($data, $publicId, $publicId); 215 | 216 | return $array; 217 | } 218 | } 219 | return false; 220 | } 221 | 222 | public function generateUuid() 223 | { 224 | do { 225 | $uuid = sprintf( 226 | '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', 227 | mt_rand(0, 0xffff), 228 | mt_rand(0, 0xffff), 229 | mt_rand(0, 0xffff), 230 | mt_rand(0, 0x0fff) | 0x4000, 231 | mt_rand(0, 0x3fff) | 0x8000, 232 | mt_rand(0, 0xffff), 233 | mt_rand(0, 0xffff), 234 | mt_rand(0, 0xffff) 235 | ); 236 | } while ($this->findById($uuid)); 237 | 238 | return $uuid; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /app/public/perfil.php: -------------------------------------------------------------------------------- 1 | checkToken(); 9 | $activeMenu = 'profile'; 10 | 11 | $publicId = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_SPECIAL_CHARS); 12 | $page = filter_input(INPUT_GET, 'p', FILTER_VALIDATE_INT); 13 | 14 | if ($publicId === $userInfo->publicId) { 15 | header("Location: $base/perfil"); 16 | exit; 17 | } 18 | 19 | if ($publicId === null) { 20 | $publicId = $userInfo->publicId; 21 | } 22 | 23 | if ($publicId !== $userInfo->publicId) { 24 | $activeMenu = ''; 25 | } 26 | 27 | if ($page === null || $page < 1) { 28 | $page = 1; 29 | } 30 | 31 | $postDao = new PostDaoMysql($pdo); 32 | $userDao = new UserDaoMysql($pdo); 33 | $userRelationDao = new UserRelationDaoMysql($pdo); 34 | 35 | $user = $userDao->findById($publicId, true); 36 | 37 | if ($user === false) { 38 | header("Location: $base"); 39 | exit; 40 | } 41 | 42 | $datefrom = new DateTime($user->birthdate); 43 | $dateTo = new DateTime('today'); 44 | 45 | $user->ageYears = $datefrom->diff($dateTo)->y; 46 | 47 | $info = $postDao->getUserFeed($publicId, $userInfo->publicId, $page); 48 | $feed = $info['feed']; 49 | $pages = $info['pages']; 50 | $currentPage = $info['currentPage']; 51 | 52 | $isFollowing = $userRelationDao->isFollowing($userInfo->publicId, $publicId); 53 | 54 | require_once('../partials/header.php'); 55 | require_once('../partials/menu.php'); 56 | ?> 57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
name ?>
70 | city) === false) : ?> 71 |
city ?>
72 | 73 |
74 |
75 | publicId) : ?> 76 |
77 | 78 | 79 | 80 |
81 | 82 |
83 |
followers) ?>
84 |
Seguidores
85 |
86 |
87 |
following) ?>
88 |
Seguindo
89 |
90 |
91 |
photos) ?>
92 |
Fotos
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 | 102 |
103 | 104 |
105 |
106 | 107 | 111 | 112 | city) === false) : ?> 113 | 117 | 118 | 119 | work) === false) : ?> 120 | 124 | 125 | 126 |
127 |
128 | 129 | following) > 0) : ?> 130 |
131 |
132 |
133 | Seguindo 134 | (following) ?>) 135 |
136 |
137 | ver todos 138 |
139 |
140 |
141 | following) > 0) : ?> 142 | following as $item) : ?> 143 | name)[0]; ?> 144 | 154 | 155 | 156 |
157 |
158 | 159 | 160 |
161 |
162 | photos) > 0) : ?> 163 |
164 |
165 |
166 | Fotos 167 | (photos) ?>) 168 |
169 |
170 | ver todos 171 |
172 |
173 |
174 | 175 | photos as $key => $item) : ?> 176 | 177 |
178 | 179 | 180 | 181 | 184 |
185 | 186 | 187 | 188 |
189 |
190 | 191 | 192 | publicId) : ?> 193 | 194 | 195 | 196 | 0) : ?> 197 | 198 | 199 | 200 | 201 | 202 | 1) : ?> 203 |
204 | 205 | publicId) { 208 | $pageString = "$base/perfil"; 209 | 210 | if ($q > 1) { 211 | $pageString .= "?p=$q"; 212 | } 213 | } else { 214 | $pageString = "$base/perfil?id=$publicId"; 215 | 216 | if ($q > 1) { 217 | $pageString .= "&p=$q"; 218 | } 219 | } 220 | ?> 221 | 222 | 223 | 224 | 225 |
226 | 227 | 228 |
229 |
230 |
231 | 232 | 237 | 238 | -------------------------------------------------------------------------------- /app/public/assets/css/style.css: -------------------------------------------------------------------------------- 1 | @import 'defaults.css'; 2 | @import 'feed-item.css'; 3 | @import 'modal.css'; 4 | 5 | * { 6 | box-sizing: border-box; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | body { 12 | font-family: Helvetica; 13 | font-size: 13px; 14 | background-color: #edeef0; 15 | margin: 0; 16 | padding-bottom: 50px; 17 | } 18 | 19 | .container { 20 | max-width: 1040px; 21 | margin: auto; 22 | } 23 | 24 | header { 25 | background-color: #4a76a8; 26 | height: 45px; 27 | box-shadow: 0px 0px 4px #333; 28 | position: relative; 29 | z-index: 99; 30 | } 31 | 32 | header .container { 33 | display: flex; 34 | height: inherit; 35 | } 36 | 37 | header .logo { 38 | width: 200px; 39 | display: flex; 40 | align-items: center; 41 | } 42 | 43 | header .logo a { 44 | display: flex; 45 | } 46 | 47 | header .logo img { 48 | height: 20px; 49 | } 50 | 51 | header .head-side { 52 | flex: 1; 53 | display: flex; 54 | justify-content: space-between; 55 | align-items: center; 56 | } 57 | 58 | .head-side .head-side-left { 59 | display: flex; 60 | } 61 | 62 | .head-side .head-side-right { 63 | display: flex; 64 | align-items: center; 65 | } 66 | 67 | .search-area input { 68 | background-color: #224b7a; 69 | background-image: url('../images/search.png'); 70 | background-size: 13px; 71 | background-position-x: 10px; 72 | background-position-y: center; 73 | background-repeat: no-repeat; 74 | width: 200px; 75 | height: 25px; 76 | border-radius: 15px; 77 | border: 0; 78 | outline: 0; 79 | padding-left: 30px; 80 | transition: all .3s; 81 | } 82 | 83 | ::placeholder { 84 | color: #A2B4C9; 85 | } 86 | 87 | .search-area input:focus { 88 | width: 300px; 89 | background-color: #FFF; 90 | } 91 | 92 | .user-area { 93 | display: flex; 94 | align-items: center; 95 | cursor: pointer; 96 | text-decoration: none; 97 | } 98 | 99 | .user-area-text { 100 | color: #FFF; 101 | font-size: 12px; 102 | font-weight: bold; 103 | margin-right: 10px; 104 | } 105 | 106 | .user-area-icon { 107 | margin-right: 5px; 108 | } 109 | 110 | .user-area-icon img { 111 | width: 24px; 112 | height: 24px; 113 | border-radius: 50%; 114 | } 115 | 116 | .user-area-box { 117 | width: 100px; 118 | height: 100px; 119 | position: absolute; 120 | background-color: #fff; 121 | top: 45px; 122 | } 123 | 124 | .user-logout { 125 | display: block; 126 | margin-left: 15px; 127 | } 128 | 129 | .user-logout img { 130 | width: 18px; 131 | height: 18px; 132 | } 133 | 134 | .main { 135 | display: flex; 136 | } 137 | 138 | .main aside { 139 | width: 190px; 140 | margin-right: 10px; 141 | } 142 | 143 | .main aside nav a { 144 | text-decoration: none; 145 | display: block; 146 | color: #224b7a; 147 | margin-bottom: 5px; 148 | } 149 | 150 | .menu-splitter { 151 | border-top: 1px solid #ccc; 152 | margin-bottom: 5px; 153 | } 154 | 155 | .menu-item { 156 | display: flex; 157 | align-items: center; 158 | height: 32px; 159 | border: 1px solid #edeef0; 160 | } 161 | 162 | .menu-item:hover, 163 | .menu-item.active { 164 | background-color: #d1d9e0; 165 | border: 1px solid #CCC; 166 | } 167 | 168 | .menu-item-icon { 169 | width: 16px; 170 | margin-right: 10px; 171 | margin-left: 5px; 172 | } 173 | 174 | .menu-item-text { 175 | flex: 1; 176 | font-size: 13px; 177 | } 178 | 179 | .menu-item-badge { 180 | background-color: #d1d9e0; 181 | padding: 5px; 182 | border-radius: 3px; 183 | margin-right: 5px; 184 | } 185 | 186 | .main .feed { 187 | flex: 1; 188 | } 189 | 190 | .feed .box { 191 | background-color: #FFF; 192 | border: 1px solid #c9cacc; 193 | border-radius: 5px; 194 | margin-bottom: 10px; 195 | } 196 | 197 | .box .box-header { 198 | padding: 10px; 199 | display: flex; 200 | align-items: center; 201 | justify-content: space-between; 202 | } 203 | 204 | .box .box-header-text { 205 | flex: 1; 206 | } 207 | 208 | .box .box-header-text span { 209 | color: #999; 210 | } 211 | 212 | .box .box-header-buttons a { 213 | font-size: 13px; 214 | color: #999; 215 | text-decoration: none; 216 | } 217 | 218 | .box .box-body img { 219 | max-width: 100%; 220 | } 221 | 222 | .feed .border-top-flat { 223 | border-top: 0; 224 | border-top-left-radius: 0; 225 | border-top-right-radius: 0; 226 | } 227 | 228 | .feed .row { 229 | display: flex; 230 | } 231 | 232 | .feed .column { 233 | display: flex; 234 | flex-direction: column; 235 | justify-content: flex-start; 236 | flex: 1; 237 | } 238 | 239 | .feed .side { 240 | flex: none; 241 | width: 250px; 242 | } 243 | 244 | 245 | .feed-new-editor { 246 | align-items: center; 247 | } 248 | 249 | .feed-new-avatar { 250 | margin-right: 10px; 251 | } 252 | 253 | .feed-new-avatar img { 254 | width: 50px; 255 | height: 50px; 256 | border-radius: 50%; 257 | } 258 | 259 | .feed-new-input-placeholder { 260 | flex: 1; 261 | font-size: 14px; 262 | color: #333; 263 | } 264 | 265 | .feed-new-input { 266 | flex: 1; 267 | outline: 0; 268 | border: 0; 269 | font-size: 14px; 270 | display: none; 271 | color: #333; 272 | } 273 | 274 | .feed-new-send { 275 | cursor: pointer; 276 | margin-right: 5px; 277 | } 278 | 279 | .feed-new-send img { 280 | width: 25px; 281 | height: 25px; 282 | } 283 | 284 | .feed-new-photo { 285 | cursor: pointer; 286 | margin-right: 5px; 287 | } 288 | 289 | .feed-new-photo img { 290 | width: 20px; 291 | height: 20px; 292 | } 293 | 294 | .feed-new-file { 295 | display: none; 296 | } 297 | 298 | .feed-pagination a { 299 | display: inline-block; 300 | padding: 7px 10px; 301 | border: 1px solid #ccc; 302 | margin: 5px; 303 | text-decoration: none; 304 | color: #000; 305 | border-radius: 3px; 306 | transition: all 0.2s ease-in-out 0s; 307 | } 308 | 309 | .feed-pagination a:hover { 310 | background-color: #ddd; 311 | } 312 | 313 | .feed-pagination a.active { 314 | background-color: #fff; 315 | } 316 | 317 | .banners .box-body { 318 | margin: 0 10px 10px 10px; 319 | } 320 | 321 | .banners .box-body a { 322 | display: block; 323 | margin-bottom: 5px; 324 | } 325 | 326 | .profile-cover { 327 | height: 250px; 328 | background-size: cover; 329 | background-position: center; 330 | } 331 | 332 | .profile-info { 333 | align-items: center; 334 | } 335 | 336 | .profile-info-avatar { 337 | margin-right: 15px; 338 | } 339 | 340 | .profile-info-avatar img { 341 | width: 50px; 342 | height: 50px; 343 | border-radius: 50%; 344 | } 345 | 346 | .profile-info-name { 347 | flex: 1; 348 | } 349 | 350 | .profile-info-name-text { 351 | font-size: 20px; 352 | margin-bottom: 5px; 353 | } 354 | 355 | .profile-info-name-text a { 356 | color: #000; 357 | text-decoration: none; 358 | } 359 | 360 | .profile-info-location { 361 | color: #819db9; 362 | font-size: 13px; 363 | text-transform: uppercase; 364 | background-image: url('../images/pin.png'); 365 | background-size: auto 15px; 366 | background-position: left center; 367 | background-repeat: no-repeat; 368 | padding-left: 20px; 369 | } 370 | 371 | .profile-info-item { 372 | display: flex; 373 | flex-flow: column; 374 | justify-content: center; 375 | align-items: center; 376 | } 377 | 378 | .profile-info-item-n { 379 | font-weight: bold; 380 | font-size: 20px; 381 | color: #224b7a; 382 | } 383 | 384 | .profile-info-item-s { 385 | font-size: 14px; 386 | color: #999; 387 | } 388 | 389 | .user-info-mini { 390 | display: flex; 391 | align-items: center; 392 | margin: 20px; 393 | color: #819db9; 394 | font-size: 13px; 395 | } 396 | 397 | .user-info-mini img { 398 | width: 20px; 399 | height: 20px; 400 | margin-right: 15px; 401 | } 402 | 403 | .friend-list { 404 | display: grid; 405 | grid-template-columns: repeat(3, 1fr); 406 | } 407 | 408 | .full-friend-list { 409 | display: grid; 410 | grid-template-columns: repeat(7, 1fr); 411 | } 412 | 413 | .friend-icon { 414 | text-align: center; 415 | margin: 10px 0; 416 | } 417 | 418 | .friend-icon a { 419 | text-decoration: none; 420 | } 421 | 422 | .friend-icon-avatar { 423 | margin-bottom: 5px; 424 | } 425 | 426 | .friend-icon-avatar img { 427 | width: 55px; 428 | height: 55px; 429 | border-radius: 50%; 430 | } 431 | 432 | .friend-icon-name { 433 | width: 60px; 434 | font-size: 14px; 435 | margin: auto; 436 | color: #224b7a; 437 | white-space: nowrap; 438 | overflow: hidden; 439 | text-overflow: ellipsis; 440 | } 441 | 442 | .user-photo-item { 443 | margin-right: 10px; 444 | max-width: 25%; 445 | } 446 | 447 | .user-photo-item:last-child { 448 | margin-right: 0; 449 | } 450 | 451 | .user-photo-item img { 452 | max-width: 100%; 453 | } 454 | 455 | .full-user-photos .user-photo-item { 456 | max-width: 100%; 457 | } 458 | 459 | .full-user-photos .user-photo-item img { 460 | max-width: 192px; 461 | max-height: 192px; 462 | } 463 | 464 | .tabs { 465 | border-bottom: 1px solid #c9cacc; 466 | display: flex; 467 | } 468 | 469 | .tab-item { 470 | padding: 20px; 471 | font-size: 15px; 472 | color: #999; 473 | border-bottom: 2px solid #FFF; 474 | cursor: pointer; 475 | } 476 | 477 | .tab-item:hover { 478 | color: #000; 479 | } 480 | 481 | .tab-item.active { 482 | color: #000; 483 | border-bottom: 2px solid #224b7a; 484 | } 485 | 486 | .tab-body { 487 | display: none; 488 | padding: 10px 0; 489 | } 490 | 491 | .full-user-photos { 492 | display: grid; 493 | grid-template-columns: repeat(4, 1fr); 494 | gap: 10px; 495 | margin: 20px; 496 | } 497 | 498 | .full-user-photos .user-photo-item { 499 | margin: 0; 500 | } 501 | 502 | .config-form { 503 | margin-top: 10px; 504 | } 505 | 506 | .config-form label { 507 | display: block; 508 | padding: 10px 0; 509 | } 510 | 511 | .config-form input { 512 | font-size: 14px; 513 | width: 100%; 514 | max-width: 500px; 515 | padding: 10px 15px; 516 | } 517 | 518 | .config-form .mini { 519 | max-height: 100px; 520 | } 521 | 522 | .button { 523 | border: 0; 524 | padding: 10px 20px; 525 | background-color: #4a76a8; 526 | border-radius: 10px; 527 | color: #fff; 528 | font-size: 15px; 529 | box-shadow: 0px 0px 3px #999; 530 | cursor: pointer; 531 | text-decoration: none; 532 | } 533 | 534 | @media (max-width: 992px) { 535 | header .head-side-right { 536 | display: none !important; 537 | } 538 | 539 | header .logo { 540 | margin-left: 10px; 541 | } 542 | 543 | .search-area input { 544 | width: 35px; 545 | } 546 | 547 | .search-area input:focus { 548 | width: 150px; 549 | } 550 | 551 | header .head-side { 552 | justify-content: flex-end; 553 | } 554 | 555 | .head-side-left { 556 | margin-right: 10px; 557 | } 558 | 559 | .main { 560 | flex-direction: column; 561 | } 562 | 563 | .main aside { 564 | width: 100%; 565 | } 566 | 567 | aside nav { 568 | display: flex; 569 | width: 100%; 570 | overflow-x: auto; 571 | padding: 0 10px; 572 | } 573 | 574 | .main aside nav a { 575 | margin-right: 15px; 576 | } 577 | 578 | .menu-splitter { 579 | display: none; 580 | } 581 | 582 | .menu-item-badge { 583 | display: none; 584 | } 585 | 586 | .menu-item-text { 587 | margin-right: 5px; 588 | } 589 | 590 | .feed .side { 591 | display: none; 592 | } 593 | 594 | .feed>.row>.column { 595 | padding: 0 10px; 596 | } 597 | 598 | .profile-cover { 599 | height: 150px; 600 | } 601 | 602 | .profile-info { 603 | flex-direction: column; 604 | } 605 | 606 | .profile-info-name { 607 | margin-top: 10px; 608 | } 609 | 610 | .profile-info-data { 611 | margin-top: 30px; 612 | } 613 | 614 | .full-friend-list { 615 | grid-template-columns: repeat(3, 1fr); 616 | } 617 | 618 | .full-user-photos { 619 | grid-template-columns: repeat(1, 1fr); 620 | } 621 | 622 | } -------------------------------------------------------------------------------- /app/public/assets/js/vanillaModal.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define(['exports'], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(exports); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod.exports); 11 | global.VanillaModal = mod.exports; 12 | } 13 | })(this, function (exports) { 14 | 'use strict'; 15 | 16 | Object.defineProperty(exports, "__esModule", { 17 | value: true 18 | }); 19 | 20 | function _classCallCheck(instance, Constructor) { 21 | if (!(instance instanceof Constructor)) { 22 | throw new TypeError("Cannot call a class as a function"); 23 | } 24 | } 25 | 26 | var _createClass = function () { 27 | function defineProperties(target, props) { 28 | for (var i = 0; i < props.length; i++) { 29 | var descriptor = props[i]; 30 | descriptor.enumerable = descriptor.enumerable || false; 31 | descriptor.configurable = true; 32 | if ("value" in descriptor) descriptor.writable = true; 33 | Object.defineProperty(target, descriptor.key, descriptor); 34 | } 35 | } 36 | 37 | return function (Constructor, protoProps, staticProps) { 38 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 39 | if (staticProps) defineProperties(Constructor, staticProps); 40 | return Constructor; 41 | }; 42 | }(); 43 | 44 | var _extends = Object.assign || function (target) { 45 | for (var i = 1; i < arguments.length; i++) { 46 | var source = arguments[i]; 47 | 48 | for (var key in source) { 49 | if (Object.prototype.hasOwnProperty.call(source, key)) { 50 | target[key] = source[key]; 51 | } 52 | } 53 | } 54 | 55 | return target; 56 | }; 57 | 58 | var defaults = { 59 | modal: '.modal', 60 | modalInner: '.modal-inner', 61 | modalContent: '.modal-content', 62 | open: '[data-modal-open]', 63 | close: '[data-modal-close]', 64 | page: 'body', 65 | class: 'modal-visible', 66 | loadClass: 'vanilla-modal', 67 | clickOutside: true, 68 | closeKeys: [27], 69 | transitions: true, 70 | transitionEnd: null, 71 | onBeforeOpen: null, 72 | onBeforeClose: null, 73 | onOpen: null, 74 | onClose: null 75 | }; 76 | 77 | function throwError(message) { 78 | // eslint-disable-next-line no-console 79 | console.error('VanillaModal: ' + message); 80 | } 81 | 82 | function find(arr, callback) { 83 | return function (key) { 84 | var filteredArray = arr.filter(callback); 85 | return filteredArray[0] ? filteredArray[0][key] : undefined; 86 | }; 87 | } 88 | 89 | function transitionEndVendorSniff() { 90 | var el = document.createElement('div'); 91 | var transitions = [{ 92 | key: 'transition', 93 | value: 'transitionend' 94 | }, { 95 | key: 'OTransition', 96 | value: 'otransitionend' 97 | }, { 98 | key: 'MozTransition', 99 | value: 'transitionend' 100 | }, { 101 | key: 'WebkitTransition', 102 | value: 'webkitTransitionEnd' 103 | }]; 104 | return find(transitions, function (_ref) { 105 | var key = _ref.key; 106 | return typeof el.style[key] !== 'undefined'; 107 | })('value'); 108 | } 109 | 110 | function isPopulatedArray(arr) { 111 | return Object.prototype.toString.call(arr) === '[object Array]' && arr.length; 112 | } 113 | 114 | function getNode(selector, parent) { 115 | var targetNode = parent || document; 116 | var node = targetNode.querySelector(selector); 117 | if (!node) { 118 | throwError(selector + ' not found in document.'); 119 | } 120 | return node; 121 | } 122 | 123 | function addClass(el, className) { 124 | if (!(el instanceof HTMLElement)) { 125 | throwError('Not a valid HTML element.'); 126 | } 127 | el.setAttribute('class', el.className.split(' ').filter(function (cn) { 128 | return cn !== className; 129 | }).concat(className).join(' ')); 130 | } 131 | 132 | function removeClass(el, className) { 133 | if (!(el instanceof HTMLElement)) { 134 | throwError('Not a valid HTML element.'); 135 | } 136 | el.setAttribute('class', el.className.split(' ').filter(function (cn) { 137 | return cn !== className; 138 | }).join(' ')); 139 | } 140 | 141 | function getElementContext(e) { 142 | if (e && typeof e.hash === 'string') { 143 | return document.querySelector(e.hash); 144 | } else if (typeof e === 'string') { 145 | return document.querySelector(e); 146 | } 147 | throwError('No selector supplied to open()'); 148 | return null; 149 | } 150 | 151 | function applyUserSettings(settings) { 152 | return _extends({}, defaults, settings, { 153 | transitionEnd: transitionEndVendorSniff() 154 | }); 155 | } 156 | 157 | function matches(e, selector) { 158 | var allMatches = (e.target.document || e.target.ownerDocument).querySelectorAll(selector); 159 | for (var i = 0; i < allMatches.length; i += 1) { 160 | var node = e.target; 161 | while (node && node !== document.body) { 162 | if (node === allMatches[i]) { 163 | return node; 164 | } 165 | node = node.parentNode; 166 | } 167 | } 168 | return null; 169 | } 170 | 171 | var VanillaModal = function () { 172 | function VanillaModal(settings) { 173 | _classCallCheck(this, VanillaModal); 174 | 175 | this.isOpen = false; 176 | this.current = null; 177 | this.isListening = false; 178 | 179 | this.settings = applyUserSettings(settings); 180 | this.dom = this.getDomNodes(); 181 | 182 | this.open = this.open.bind(this); 183 | this.close = this.close.bind(this); 184 | this.closeKeyHandler = this.closeKeyHandler.bind(this); 185 | this.outsideClickHandler = this.outsideClickHandler.bind(this); 186 | this.delegateOpen = this.delegateOpen.bind(this); 187 | this.delegateClose = this.delegateClose.bind(this); 188 | this.listen = this.listen.bind(this); 189 | this.destroy = this.destroy.bind(this); 190 | 191 | this.addLoadedCssClass(); 192 | this.listen(); 193 | } 194 | 195 | _createClass(VanillaModal, [{ 196 | key: 'getDomNodes', 197 | value: function getDomNodes() { 198 | var _settings = this.settings, 199 | modal = _settings.modal, 200 | page = _settings.page, 201 | modalInner = _settings.modalInner, 202 | modalContent = _settings.modalContent; 203 | 204 | return { 205 | modal: getNode(modal), 206 | page: getNode(page), 207 | modalInner: getNode(modalInner, getNode(modal)), 208 | modalContent: getNode(modalContent, getNode(modal)) 209 | }; 210 | } 211 | }, { 212 | key: 'addLoadedCssClass', 213 | value: function addLoadedCssClass() { 214 | addClass(this.dom.page, this.settings.loadClass); 215 | } 216 | }, { 217 | key: 'setOpenId', 218 | value: function setOpenId(id) { 219 | var page = this.dom.page; 220 | 221 | page.setAttribute('data-current-modal', id || 'anonymous'); 222 | } 223 | }, { 224 | key: 'removeOpenId', 225 | value: function removeOpenId() { 226 | var page = this.dom.page; 227 | 228 | page.removeAttribute('data-current-modal'); 229 | } 230 | }, { 231 | key: 'open', 232 | value: function open(allMatches, e) { 233 | var page = this.dom.page; 234 | var _settings2 = this.settings, 235 | onBeforeOpen = _settings2.onBeforeOpen, 236 | onOpen = _settings2.onOpen; 237 | 238 | if (!(this.current instanceof HTMLElement === false)) { 239 | throwError('VanillaModal target must exist on page.'); 240 | return; 241 | } 242 | this.releaseNode(this.current); 243 | this.current = getElementContext(allMatches); 244 | if (typeof onBeforeOpen === 'function') { 245 | onBeforeOpen.call(this, e); 246 | } 247 | this.captureNode(this.current); 248 | addClass(page, this.settings.class); 249 | this.setOpenId(this.current.id); 250 | this.isOpen = true; 251 | if (typeof onOpen === 'function') { 252 | onOpen.call(this, e); 253 | } 254 | } 255 | }, { 256 | key: 'detectTransition', 257 | value: function detectTransition() { 258 | var modal = this.dom.modal; 259 | 260 | var css = window.getComputedStyle(modal, null); 261 | return Boolean(['transitionDuration', 'oTransitionDuration', 'MozTransitionDuration', 'webkitTransitionDuration'].filter(function (i) { 262 | return typeof css[i] === 'string' && parseFloat(css[i]) > 0; 263 | }).length); 264 | } 265 | }, { 266 | key: 'close', 267 | value: function close(e) { 268 | var _settings3 = this.settings, 269 | transitions = _settings3.transitions, 270 | transitionEnd = _settings3.transitionEnd, 271 | onBeforeClose = _settings3.onBeforeClose; 272 | 273 | var hasTransition = this.detectTransition(); 274 | if (this.isOpen) { 275 | this.isOpen = false; 276 | if (typeof onBeforeClose === 'function') { 277 | onBeforeClose.call(this, e); 278 | } 279 | removeClass(this.dom.page, this.settings.class); 280 | if (transitions && transitionEnd && hasTransition) { 281 | this.closeModalWithTransition(e); 282 | } else { 283 | this.closeModal(e); 284 | } 285 | } 286 | } 287 | }, { 288 | key: 'closeModal', 289 | value: function closeModal(e) { 290 | var onClose = this.settings.onClose; 291 | 292 | this.removeOpenId(this.dom.page); 293 | this.releaseNode(this.current); 294 | this.isOpen = false; 295 | this.current = null; 296 | if (typeof onClose === 'function') { 297 | onClose.call(this, e); 298 | } 299 | } 300 | }, { 301 | key: 'closeModalWithTransition', 302 | value: function closeModalWithTransition(e) { 303 | var _this = this; 304 | 305 | var modal = this.dom.modal; 306 | var transitionEnd = this.settings.transitionEnd; 307 | 308 | var closeTransitionHandler = function closeTransitionHandler() { 309 | modal.removeEventListener(transitionEnd, closeTransitionHandler); 310 | _this.closeModal(e); 311 | }; 312 | modal.addEventListener(transitionEnd, closeTransitionHandler); 313 | } 314 | }, { 315 | key: 'captureNode', 316 | value: function captureNode(node) { 317 | var modalContent = this.dom.modalContent; 318 | 319 | while (node.childNodes.length) { 320 | modalContent.appendChild(node.childNodes[0]); 321 | } 322 | } 323 | }, { 324 | key: 'releaseNode', 325 | value: function releaseNode(node) { 326 | var modalContent = this.dom.modalContent; 327 | 328 | while (modalContent.childNodes.length) { 329 | node.appendChild(modalContent.childNodes[0]); 330 | } 331 | } 332 | }, { 333 | key: 'closeKeyHandler', 334 | value: function closeKeyHandler(e) { 335 | var closeKeys = this.settings.closeKeys; 336 | 337 | if (isPopulatedArray(closeKeys) && closeKeys.indexOf(e.which) > -1 && this.isOpen === true) { 338 | e.preventDefault(); 339 | this.close(e); 340 | } 341 | } 342 | }, { 343 | key: 'outsideClickHandler', 344 | value: function outsideClickHandler(e) { 345 | var clickOutside = this.settings.clickOutside; 346 | var modalInner = this.dom.modalInner; 347 | 348 | if (clickOutside) { 349 | var node = e.target; 350 | while (node && node !== document.body) { 351 | if (node === modalInner) { 352 | return; 353 | } 354 | node = node.parentNode; 355 | } 356 | this.close(e); 357 | } 358 | } 359 | }, { 360 | key: 'delegateOpen', 361 | value: function delegateOpen(e) { 362 | var open = this.settings.open; 363 | 364 | var matchedNode = matches(e, open); 365 | if (matchedNode) { 366 | e.preventDefault(); 367 | this.open(matchedNode, e); 368 | } 369 | } 370 | }, { 371 | key: 'delegateClose', 372 | value: function delegateClose(e) { 373 | var close = this.settings.close; 374 | 375 | if (matches(e, close)) { 376 | e.preventDefault(); 377 | this.close(e); 378 | } 379 | } 380 | }, { 381 | key: 'listen', 382 | value: function listen() { 383 | var modal = this.dom.modal; 384 | 385 | if (!this.isListening) { 386 | modal.addEventListener('click', this.outsideClickHandler, false); 387 | document.addEventListener('keydown', this.closeKeyHandler, false); 388 | document.addEventListener('click', this.delegateOpen, false); 389 | document.addEventListener('click', this.delegateClose, false); 390 | this.isListening = true; 391 | } else { 392 | throwError('Event listeners already applied.'); 393 | } 394 | } 395 | }, { 396 | key: 'destroy', 397 | value: function destroy() { 398 | var modal = this.dom.modal; 399 | 400 | if (this.isListening) { 401 | this.close(); 402 | modal.removeEventListener('click', this.outsideClickHandler); 403 | document.removeEventListener('keydown', this.closeKeyHandler); 404 | document.removeEventListener('click', this.delegateOpen); 405 | document.removeEventListener('click', this.delegateClose); 406 | this.isListening = false; 407 | } else { 408 | throwError('Event listeners already removed.'); 409 | } 410 | } 411 | }]); 412 | 413 | return VanillaModal; 414 | }(); 415 | 416 | exports.default = VanillaModal; 417 | }); --------------------------------------------------------------------------------