diff --git a/.gitignore b/.gitignore index f6a681e..8dfef9f 100755 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ composer.lock /vendor/ profile_pictures +/data diff --git a/components/foot.php b/components/foot.php index 60f907c..90a546a 100644 --- a/components/foot.php +++ b/components/foot.php @@ -1,7 +1,33 @@ diff --git a/css/base.css b/css/base.css index 0855bcc..5ce35e6 100644 --- a/css/base.css +++ b/css/base.css @@ -24,7 +24,8 @@ small, label, input, textarea, -a { +a, +.modal_button { font-family: 'HackNerdFontMono', Hack, sans-serif; padding: 0; margin: 0; @@ -52,15 +53,23 @@ h5 { p, a, -label { +label, +.modal_button { font-size: 1vw; } +p::after { + content: ""; + display: inline-block; + width: 100%; +} + small { font-size: .75vw; } -a { +a, +.modal_button { color: orange; transition-duration: 1s; } @@ -297,7 +306,9 @@ textarea { .checkboxes {} .form_checkbox_container { - display: block; + display: flex; + align-items: center; + justify-content: center; position: relative; padding-left: 35px; margin: 15px 15px 15px 0px; @@ -309,6 +320,7 @@ textarea { height: 29px; width: max-content; + } .checkmark { @@ -458,3 +470,66 @@ input[type="file"] { color: orange; cursor: pointer; } + +.modal_button { + background-color: transparent; + border: none; +} + +#img_to_crop { + width: 70%; + height: auto; + max-width: 512px; + max-height: 512px; + + user-select: none; +} + +#img_crop_selection { + pointer-events: none; + + height: 128px; + width: 128px; + + outline: dashed 4px orange; + + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + +.corner { + position: absolute; + width: 10px; + height: 10px; + background-color: orange; + border-radius: 50%; + cursor: nwse-resize; + /* Default cursor for resizing */ + pointer-events: all; +} + +#corner_nw { + top: -5px; + left: -5px; + cursor: nw-resize; +} + +#corner_ne { + top: -5px; + right: -5px; + cursor: ne-resize; +} + +#corner_sw { + bottom: -5px; + left: -5px; + cursor: sw-resize; +} + +#corner_se { + bottom: -5px; + right: -5px; + cursor: se-resize; +} diff --git a/css/dark.css b/css/dark.css index e0a786a..7255432 100644 --- a/css/dark.css +++ b/css/dark.css @@ -17,7 +17,8 @@ label { color: white; } -a:hover { +a:hover, +.modal_button:hover { color: white; } diff --git a/css/light.css b/css/light.css index a2a4f88..7a8f482 100644 --- a/css/light.css +++ b/css/light.css @@ -17,7 +17,8 @@ label { color: black; } -a:hover { +a:hover, +.modal_button:hover { color: black; } diff --git a/functions/account_functions.php b/functions/account_functions.php index a146176..a2b8b91 100644 --- a/functions/account_functions.php +++ b/functions/account_functions.php @@ -29,35 +29,52 @@ function delete_self() return -1; } -function create_account($username, $email, $password, $verify_password, $role_id, $profile_picture) +function cookies($user, $time) { - if ($password != $verify_password) { - $error_msg = ' -
- Passwords do not match! -
- '; + setcookie('user_id', $user['user_id'], $time); + setcookie('username', $user['username'], $time); + setcookie('email', $user['email'], $time); + setcookie('role_id', $user['role_id'], $time); + setcookie('profile_picture', $user['profile_picture'], $time); +} - return $error_msg; +function logout() +{ + foreach (array_keys($_COOKIE) as $key) { + echo 'unsetting ' . $key . ' now.'; + setcookie($key, '', time() - 3600, '/'); + unset($_COOKIE[$key]); } - $password_hash = hash('sha256', $password); + header('Location: articles.php'); +} - $id = rand(1000, 999999999); - while (user_id_exists($id)) - $id = rand(1000, 999999999); +function login_form($error_msg = '') +{ + $form = ' +
+

Log In

+
+ - if (!empty($profile_picture)) { - $filetype = explode('.', $profile_picture); - $pic_path = $id . $filetype[count($filetype) - 1]; - } + + - $stmt = 'INSERT INTO user (user_id, username, email, password_hash, role_id' . (!empty($profile_picture) ? ', profile_picture' : '') . ') VALUES (' . $id . ', "' . $username . '", "' . $email . '", "' . $password_hash . '", ' . $role_id . (!empty($profile_picture) ? '", "' . $pic_path . '"' : '') . ');'; - exec_statement($stmt, 0); + + - login($username, $password, false); + - return true; + + ' . $error_msg . ' +
+
+ '; + + return $form; } function login($username, $password, $stay_logged_in) @@ -86,58 +103,17 @@ function login($username, $password, $stay_logged_in) return true; } -function cookies($user, $time) -{ - setcookie('user_id', $user['user_id'], $time); - setcookie('username', $user['username'], $time); - setcookie('email', $user['email'], $time); - setcookie('role_id', $user['role_id'], $time); - setcookie('profile_picture', $user['profile_picture'], $time); -} - -function logout() -{ - foreach (array_keys($_COOKIE) as $key) { - echo 'unsetting ' . $key . ' now.'; - setcookie($key, '', time() - 3600, '/'); - unset($_COOKIE[$key]); - } - - header('Location: articles.php'); -} - -function login_form($error_msg = '') -{ - $form = ' -
-

Log In

-
- - - - - - - - - - ' . $error_msg . ' -
-
- '; - - return $form; -} - function signup_form($error_msg = '') { $form = '

Sign Up

-
+ + + + + + @@ -150,8 +126,9 @@ function signup_form($error_msg = '') - - + + + No file selected.
'; return $form; } +function signup($username, $email, $password, $verify_password, $role_id, $upload = null, $x = null, $y = null, $crop_width = null) +{ + if ($password != $verify_password) { + $error_msg = ' +
+ Passwords do not match! +
+ '; + + return $error_msg; + } + + $password_hash = hash('sha256', $password); + + $id = rand(1000, 999999999); + while (user_id_exists($id)) + $id = rand(1000, 999999999); + + if (!empty($upload)) { + $orig_size = getimagesize($upload['profile_picture']['tmp_name']); + $orig_width = $orig_size[0]; + $orig_height = $orig_size[1]; + + $crop_size = min($orig_width, $orig_height) * .56; + + crop_image($upload['profile_picture']['tmp_name'], $x, $y, $crop_size, $crop_size); + $target = upload($upload['profile_picture'], $_ENV['PROFILE_IMAGES_FQ_PATH'], $id); + } + + $stmt = 'INSERT INTO user (user_id, username, email, password_hash, role_id' . (!empty($target) ? ', profile_picture' : '') . ') VALUES (' . $id . ', "' . $username . '", "' . $email . '", "' . $password_hash . '", ' . $role_id . (!empty($target) ? ', "' . $target . '"' : '') . ');'; + exec_statement($stmt, 0); + + login($username, $password, false); + + return true; +} + +function profile_cropper() +{ + $display = ' +
+ +
+
+
+
+
+
+ +
+ + +
+
+ '; + + return $display; +} + function reset_pw_form() { $form = ' @@ -413,7 +458,7 @@ function user_view($user_id)

' . $user['username'] . '

- +
@@ -425,15 +470,14 @@ function user_view($user_id) // $view .= '
- Reset Password - Update Email - Update Username - Delete Account + + + +
diff --git a/functions/functions.php b/functions/functions.php index dabe532..468a3b1 100644 --- a/functions/functions.php +++ b/functions/functions.php @@ -1,5 +1,85 @@ safeLoad(); + upload($_FILES['upload'], $_ENV['UPLOAD_TMP_FQ_PATH'], isset($_POST['filename']) ? $_POST['filename'] : null); +} + +function upload($input, $dest_dir, $filename = null) +{ + $filetype = strtolower(pathinfo($input['name'], PATHINFO_EXTENSION)); + + switch ($filetype) { + case 'png': + case 'jpg': + case 'jpeg': + if (check_image_upload($dest_dir, $filetype, $input)) { + move_uploaded_file($input['tmp_name'], $dest_dir . ($filename ? $filename . '.' . $filetype : $input['name'])); + if ($filename) + return basename($filename . '.' . $filetype); + else + return basename($input['name'] . '.' . $filetype); + } else + return ''; + break; + default: + # code... + break; + } +} + +function crop_image($img_path, $x, $y, $height, $width) +{ + //$dest = $_ENV['UPLOAD_TMP_FQ_PATH']; + $dest = '/tmp/'; + // Get the image type + $imageType = exif_imagetype($img_path); + + // Create an image resource from the source image + switch ($imageType) { + case IMAGETYPE_JPEG: + $sourceImage = imagecreatefromjpeg($img_path); + break; + case IMAGETYPE_PNG: + $sourceImage = imagecreatefrompng($img_path); + break; + default: + return false; // Unsupported image type + } + + if (!$sourceImage) + return false; // Failed to create image resource + + // Create a new image resource for the cropped image + $croppedImage = imagecreatetruecolor($width, $height); + + // Copy the cropped portion from the source image to the destination image + imagecopy($croppedImage, $sourceImage, 0, 0, $x, $y, $width, $height); + + // Save the cropped image to the destination path + switch ($imageType) { + case IMAGETYPE_JPEG: + imagejpeg($croppedImage, $img_path); + break; + case IMAGETYPE_PNG: + imagepng($croppedImage, $dest); + break; + case IMAGETYPE_GIF: + imagegif($croppedImage, $dest); + break; + } + + // Free up memory + imagedestroy($sourceImage); + imagedestroy($croppedImage); + + return true; // Cropping successful +} + /* * Reads in file contents and stores as a single string. */ @@ -10,9 +90,14 @@ function read_file_one_string($file_path) return $string; } +function check_image_upload($target, $filetype, $image) +{ + return true; +} + function pretty_dump($arr) { - return ' + echo '
         ' . print_r($arr) . '
     
diff --git a/functions/profile.jpg b/functions/profile.jpg new file mode 100644 index 0000000..8dc6d4f Binary files /dev/null and b/functions/profile.jpg differ diff --git a/index.php b/index.php index d214aec..108b79f 100644 --- a/index.php +++ b/index.php @@ -5,6 +5,19 @@ require_once 'functions/init.php'; require_once 'functions/functions.php'; include_once 'components/head.php'; -echo '

Home

'; + +echo home_content(); include_once 'components/foot.php'; + +function home_content() +{ + $content = ' +
+

This site is intended to be a platform for exposing, celebrating, and sharing the style of Vintage Coding. Topics from hardware to software and kernel to web, this site aims to cover it all.

+

Vintage Coding is as much a mindset as it is a style. Vibe Coding is to Vintage Coding as Yin is to Yang. Vintage is relating to or denoting something of high quality. For example, vintage clothes from decades ago are more durable than the clothes of today which rip and tear after little use.

+
+ '; + + return $content; +} diff --git a/js/profile_picture_cropper_modal.js b/js/profile_picture_cropper_modal.js new file mode 100644 index 0000000..d43fd8a --- /dev/null +++ b/js/profile_picture_cropper_modal.js @@ -0,0 +1,168 @@ +document.addEventListener('DOMContentLoaded', function() { + var user_actions_modal = document.getElementById("user_actions_modal"); + var modal_content = document.getElementById("modal_content"); + var profile_picture_input = document.getElementById("profile_picture"); + + // Event listener on the file input + profile_picture_input.addEventListener('change', function() { + if (profile_picture_input.files && profile_picture_input.files[0]) { + var file = profile_picture_input.files[0]; + var form_data = new FormData(); + form_data.append('upload', file); + form_data.append('test_key', 'test_value'); + + + var xhr = new XMLHttpRequest(); + xhr.open('POST', 'http://localhost:2025/functions/functions.php', true); + xhr.setRequestHeader('enctype', 'multipart/form-data'); + + xhr.onload = function() { + if (xhr.status >= 200 && xhr.status < 300) { + filepath = '/data/tmp/' + profile_picture_input.files[0].name; + openModal('profile_cropper', filepath); + } else { + console.error('Error uploading file:', xhr.status, xhr.statusText); + } + + xhr.onerror = function() { + console.error('Network error occurred.'); + }; + }; + + xhr.send(form_data); + } + }); + + // Open modal function + function openModal(formId, filePath = null) { + let content = document.getElementById(formId).innerHTML; + if (filePath) { + content = content.replace('IMG_UPLOAD_PATH', filePath); + } + modal_content.innerHTML = content; + user_actions_modal.style.display = "block"; + + cropper(); + } + + function cropper() { + let img_to_crop = document.getElementById('img_to_crop'); + let selection_box = document.getElementById('img_crop_selection'); + let crop_button = document.getElementById('crop_button'); + let corners = document.querySelectorAll('.corner'); + + let selection = { + x: 0, + y: 0, + width: 100, + height: 100, + is_resizing: false, + is_dragging: false, + resize_corner: null, + }; + + function updateSelection() { + selection_box.style.left = `${selection.x}px`; + selection_box.style.top = `${selection.y}px`; + selection_box.style.width = `${selection.width}px`; + selection_box.style.height = `${selection.height}px`; + } + + img_to_crop.addEventListener('mousemove', (e) => { + const { clientX, clientY } = e; + if (!selection_box.classList.contains('stop')) { + + // const img_bounds = img_to_crop.getBoundingClientRect(); + // const box_bounds = selection_box.getBoundingClientRect(); + // + // console.log('img_bounds: ' + img_bounds.top); + // console.log('box_bounds: ' + box_bounds.top); + // if (box_bounds.left > img_bounds.left || box_bounds.right < img_bounds.right) + selection.x = clientX; + // if (box_bounds.top > img_bounds.top || box_bounds.bottom > img_bounds.bottom) + selection.y = clientY; + + updateSelection(); + } else if (selection.is_resizing) { + resizing(e); + } + }); + + img_to_crop.addEventListener('click', function(event) { + if (selection_box.classList.contains('stop')) { + selection.is_resizing = false; + selection_box.classList.remove('stop'); + } + else { + selection_box.classList.add('stop'); + } + }) + + corners.forEach((corner) => { + corner.addEventListener('mousedown', (e) => { + console.log('resizing'); + selection.resize_corner = corner.id; + selection.is_resizing = true; + e.stopPropagation(); // Prevent image click event + }); + + corner.addEventListener('dragstart', (e) => { + e.preventDefault(); + }) + }); + + crop_button.addEventListener('click', () => { + document.getElementById('crop_x').value = selection.x + (selection.width / 1.5); + document.getElementById('crop_y').value = selection.y + (selection.width / 1.5); + document.getElementById('crop_width').value = selection.width; + + closeModal(); + }); + + function resizing(e) { + if (!selection.is_resizing) return; + + const aspect_ratio = 1; + + let newWidth = selection.width; + + switch (selection.resize_corner) { + case 'corner_nw': + newWidth = selection.x - e.clientX; + break; + case 'corner_sw': + newWidth = selection.x - e.clientX; + break; + case 'corner_ne': + newWidth = e.clientX - selection.x; + break; + case 'corner_se': + newWidth = e.clientX - selection.x; + break; + } + + selection.width = newWidth; + selection.height = newWidth / aspect_ratio; + // } + updateSelection(); + } + } + + modal_content.addEventListener('click', function(event) { + if (event.target.classList.contains('modal_close')) { + closeModal(); + } + }); + + window.onclick = function(event) { + if (event.target.classList.contains('modal')) { + closeModal(); + } + }; + + // Close modal function + function closeModal() { + modal_content.innerHTML = ""; // Clear content when closing + user_actions_modal.style.display = "none"; + } +}); diff --git a/js/upload.js b/js/upload.js new file mode 100644 index 0000000..82b757b --- /dev/null +++ b/js/upload.js @@ -0,0 +1,12 @@ +const file_inputs = document.getElementsByClassName('file_input'); +const filename_displays = document.getElementsByClassName('file_input_feedback'); + +for (let i = 0; i < file_inputs.length; i++) { + file_inputs[i].addEventListener('change', function() { + const selected_file = event.target.files[0]; + if (selected_file) + filename_displays[i].textContent = 'File uploaded: ' + selected_file.name; + else + filename_displays[i].textContent = 'No file selected.'; + }) +} diff --git a/js/modal.js b/js/user_actions_modal.js similarity index 65% rename from js/modal.js rename to js/user_actions_modal.js index f4102f6..d0b2055 100644 --- a/js/modal.js +++ b/js/user_actions_modal.js @@ -1,23 +1,22 @@ document.addEventListener('DOMContentLoaded', function() { - var modal = document.getElementById("user_actions_modal"); + var user_actions_modal = document.getElementById("user_actions_modal"); var modal_content = document.getElementById("modal_content"); var buttons = document.getElementsByClassName("modal_button"); - var spans = document.getElementsByClassName("modal_close"); for (let i = 0; i < buttons.length; i++) { buttons[i].onclick = function() { - switch (i) { - case 0: - openModal("reset_pw_form"); + switch (buttons[i].id) { + case 'reset_pw_form_button': + openModal('reset_pw_form'); break; - case 1: - openModal("update_email_form"); + case 'update_email_form_button': + openModal('update_email_form'); break; - case 2: - openModal("update_username_form"); + case 'update_username_form_button': + openModal('update_username_form'); break; - case 3: - openModal("delete_account_form"); + case 'delete_account_form_button': + openModal('delete_account_form'); break; // case 3: // openModal("request_role_form"); @@ -31,7 +30,7 @@ document.addEventListener('DOMContentLoaded', function() { // Open modal function function openModal(formId) { modal_content.innerHTML = document.getElementById(formId).innerHTML; - modal.style.display = "block"; + user_actions_modal.style.display = "block"; } modal_content.addEventListener('click', function(event) { @@ -49,7 +48,7 @@ document.addEventListener('DOMContentLoaded', function() { // Close modal function function closeModal() { modal_content.innerHTML = ""; // Clear content when closing - modal.style.display = "none"; + user_actions_modal.style.display = "none"; } }); diff --git a/user.php b/user.php index 2409c24..3838e49 100644 --- a/user.php +++ b/user.php @@ -5,43 +5,81 @@ require_once 'functions/functions.php'; include_once 'functions/account_functions.php'; $msg = ''; -// Handle creating a new user. -if (isset($_POST['verify_password'])) { - $msg = create_account($_POST['username'], $_POST['email'], $_POST['password'], $_POST['verify_password'], 4, $_POST['profile_picture_upload']); - - if ($msg === true) - header('Location: .'); -} - -if (isset($_POST['username'])) { - $msg = login($_POST['username'], $_POST['password'], (isset($POST['stay_logged_in']) ? true : false)); - - if ($msg === true) - header('Location: .'); -} if (isset($_POST['form_id'])) { switch ($_POST['form_id']) { + case 'signup_form': + $msg = signup( + $_POST['username'], + $_POST['email'], + $_POST['password'], + $_POST['verify_password'], + 4, // TODO: Need to allow for other roles to be created, if the submission is from an administrator or owner. + (!empty($_FILES) ? $_FILES : ''), + (!empty($_POST['crop_x']) ? $_POST['crop_x'] : ''), + (!empty($_POST['crop_y']) ? $_POST['crop_y'] : ''), + (!empty($_POST['crop_width']) ? $_POST['crop_width'] : '') + ); + + if ($msg === true) + header('Location: user.php?action=view&user_id=' . $_COOKIE['user_id']); + break; + + + + case 'login_form': + $msg = login( + $_POST['username'], + $_POST['password'], + (isset($POST['stay_logged_in']) ? true : false) + ); + + if ($msg === true) + header('Location: .'); + break; + + + case 'reset_pw_form': - $msg = reset_password($_COOKIE['user_id'], $_POST['new_pw'], $_POST['verify_new_pw']); - // code... + $msg = reset_password( + $_COOKIE['user_id'], + $_POST['new_pw'], + $_POST['verify_new_pw'] + ); + break; + + + case 'update_email_form': - $msg = update_email($_COOKIE['user_id'], $_POST['new_email'], $_POST['verify_new_email']); - // code... + $msg = update_email( + $_COOKIE['user_id'], + $_POST['new_email'], + $_POST['verify_new_email'] + ); + break; + + + case 'update_username_form': - $msg = update_username($_COOKIE['user_id'], $_POST['new_username'], $_POST['verify_new_username']); - // code... + $msg = update_username( + $_COOKIE['user_id'], + $_POST['new_username'], + $_POST['verify_new_username'] + ); + break; + + + case 'delete_account_form': - $msg = delete_account($_COOKIE['user_id'], $_POST['delete_password'], $_POST['delete_verify_password']); - break; - // case 'request_role_form': - // // code... - // break; - default: - // code... + $msg = delete_account( + $_COOKIE['user_id'], + $_POST['delete_password'], + $_POST['delete_verify_password'] + ); + break; } echo $msg; @@ -55,8 +93,7 @@ else if ($_GET['action'] == 'signup') echo signup_form(($msg ? $msg : '')); else if ($_GET['action'] == 'logout') logout(); -else if ($_GET['action'] == 'view' && isset($_GET['user_id'])) { +else if ($_GET['action'] == 'view' && isset($_GET['user_id'])) echo user_view($_GET['user_id']); -} include_once 'components/foot.php';