diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..bf85306 --- /dev/null +++ b/admin.php @@ -0,0 +1,44 @@ += 3) + header('Location: user.php?action=view&user_id=' . $_COOKIE['user_id']); + +include_once 'functions/admin_functions.php'; + +if (isset($_POST['action']) && isset($_POST['form_id'])) { + switch ($_POST['action']) { + case 'delete': + if ($_POST['form_id'] == 'users') + foreach ($_POST['selected_users'] as $id) + delete_account($id); + else if ($_POST['form_id'] == 'articles') + foreach ($_POST['selected_articles'] as $id) + delete_article($id); + break; + + case 'view': + header('Location: user.php?action=view&user_id=' . $_POST['selected_users'][0]); + } +} + +if (isset($_GET['view'])) { + switch ($_GET['view']) { + case 'users': + echo admin_user_view(); + break; + + case 'articles': + echo admin_article_view(); + break; + } +} else { + echo admin_article_view(); +} + +include_once 'components/foot.php'; diff --git a/articles.php b/articles.php index a1e2e1c..639afbf 100644 --- a/articles.php +++ b/articles.php @@ -9,20 +9,24 @@ include_once 'functions/article_functions.php'; // TODO: Move to a grid, where the left hand column is either a search and filtering system, or...Table of Contents? // Default view, it'll show articles by recency -if (!isset($_GET['article_id']) && !isset($_GET['tag'])) { +if (!isset($_GET['article_id']) && !isset($_GET['tag']) && !isset($_GET['author_id'])) { $ids = article_ids_by_recency(); - foreach ($ids as $id) { + foreach ($ids as $id) echo article_card($id); - } } else if (isset($_GET['article_id'])) { + increment_read_counter($_GET['article_id']); echo article_page_from_markdown($_GET['article_id']); +} else if (isset($_GET['author_id'])) { + $ids = articles_ids_by_author($_GET['author_id']); + + foreach ($ids as $id) + echo article_card($id); } else if (isset($_GET['tag'])) { $ids = article_ids_by_tag($_GET['tag']); - foreach ($ids as $id) { + foreach ($ids as $id) echo article_card($id); - } } include_once 'components/foot.php'; diff --git a/components/foot.php b/components/foot.php index 90a546a..165ddd8 100644 --- a/components/foot.php +++ b/components/foot.php @@ -17,7 +17,6 @@ if (isset($args['action'])) { if ($args['action'] == 'signup') { echo ' - '; diff --git a/components/nav.php b/components/nav.php index a0c8660..1d6e7ff 100644 --- a/components/nav.php +++ b/components/nav.php @@ -9,14 +9,20 @@ $nav = '
  • Articles
  • '; +if (isset($_COOKIE['role_id']) && $_COOKIE['role_id'] <= 2) { + $nav .= ' +
  • Admin
  • + '; +} + if (isset($_COOKIE['user_id'])) { - $nav .= ' + $nav .= '
  • Account
  • '; } if (isset($_COOKIE['role_id']) && $_COOKIE['role_id'] <= 3) { - $nav .= ' + $nav .= '
  • Compose
  • '; } @@ -30,9 +36,9 @@ $nav .= ' $theme_toggle = ''; if ($_SESSION['theme'] == 'dark') - $theme_toggle = 'light'; + $theme_toggle = 'light'; else - $theme_toggle = 'dark'; + $theme_toggle = 'dark'; $nav .= ' @@ -40,11 +46,11 @@ $nav .= ' '; if (isset($_COOKIE['username'])) { - $nav .= ' + $nav .= ' Log Out '; } else { - $nav .= ' + $nav .= ' Login Sign Up '; diff --git a/compose.php b/compose.php index 69ea01e..f22260d 100644 --- a/compose.php +++ b/compose.php @@ -4,10 +4,13 @@ require_once 'functions/functions.php'; include_once 'functions/article_functions.php'; if (!empty($_POST)) { - $id = create_article(2025, $_POST['title'], $_POST['excerpt'], $_POST['tags'], $_POST['markdown']); - header('Location: articles.php?article_id=' . $id); + $id = create_article($_COOKIE['user_id'], $_POST['title'], $_POST['excerpt'], $_POST['tags'], $_POST['markdown']); + header('Location: articles.php?article_id=' . $id); } +if (!isset($_COOKIE['role_id']) || $_COOKIE['role_id'] == 4) + header('Location: articles.php'); + include_once 'components/head.php'; echo form(); @@ -15,26 +18,26 @@ include_once 'components/foot.php'; function form() { - $raw_tags = article_tags(); - $tags = ' + $raw_tags = article_tags(); + $tags = '
    '; - foreach ($raw_tags as $row) { - $tags .= ' + foreach ($raw_tags as $row) { + $tags .= ' '; - } + } - $tags .= ' + $tags .= '
    '; - $form = ' + $form = '
    @@ -57,5 +60,5 @@ vintagecoding.net uses markdown for articles. This means:
    '; - return $form; + return $form; } diff --git a/css/base.css b/css/base.css index 5ce35e6..5d0ee78 100644 --- a/css/base.css +++ b/css/base.css @@ -25,7 +25,9 @@ label, input, textarea, a, -.modal_button { +.modal_button, +th, +td { font-family: 'HackNerdFontMono', Hack, sans-serif; padding: 0; margin: 0; @@ -165,7 +167,8 @@ code { } .article_card, -.form_card { +.form_card, +.view_card { margin: 0 auto; margin-top: 50px; padding: 18px; @@ -189,6 +192,36 @@ code { width: 80%; } +.view_card form { + width: 95%; + padding: 15px; +} + +form table { + border-collapse: separate; + border-spacing: 5px; + width: 100%; +} + +tbody tr { + transition-duration: 1s; +} + +tbody tr:hover { + outline: solid 3px orange; +} + +tbody tr td { + text-align: center; + align-items: center; + justify-content: center; + padding: 15px 0px 15px 5px; +} + +table tr td label { + width: max-content; +} + .card_info_box { display: flex; } @@ -218,6 +251,14 @@ code { border: 3px solid orange; } +.button:hover { + box-shadow: 0 2px 4px 0 rgba(255, 165, 0, 0.3), 0 3px 8px 0 rgba(255, 165, 0, 0.7); +} + +.narrow { + width: 40%; +} + .tag { margin: 15px; padding: 10px; @@ -226,6 +267,10 @@ code { text-decoration: none; } +.tag:hover { + box-shadow: 0 2px 4px 0 rgba(255, 165, 0, 0.3), 0 3px 8px 0 rgba(255, 165, 0, 0.7); +} + .tag_row {} .tag_box { @@ -295,6 +340,7 @@ textarea:focus, input:hover, textarea:hover { border: 3px solid orange; + box-shadow: 0 2px 4px 0 rgba(255, 165, 0, 0.3), 0 3px 8px 0 rgba(255, 165, 0, 0.7); outline: none; } @@ -305,6 +351,10 @@ textarea { .checkboxes {} +input[type="radio"] { + display: none; +} + .form_checkbox_container { display: flex; align-items: center; @@ -414,7 +464,9 @@ input[type="file"] { } .form_card h3, -.form_card h5 { +.form_card h5, +.view_card h3, +.view_card h5 { padding-bottom: 15px; } @@ -533,3 +585,7 @@ input[type="file"] { right: -5px; cursor: se-resize; } + +[data-href] { + cursor: pointer; +} diff --git a/css/dark.css b/css/dark.css index 7255432..f0e9cec 100644 --- a/css/dark.css +++ b/css/dark.css @@ -40,11 +40,16 @@ code { } .article_card, -.form_card { +.form_card, +.view_card { border: 5px solid white; background-color: black; } +table tr { + outline: solid 3px white; +} + .button { color: white; } diff --git a/css/light.css b/css/light.css index 7a8f482..bd6879d 100644 --- a/css/light.css +++ b/css/light.css @@ -40,11 +40,16 @@ code { } .article_card, -.form_card { +.form_card, +.view_card { border: 5px solid black; background-color: white; } +table tr { + outline: solid 3px black; +} + .button { color: black; } diff --git a/functions/account_functions.php b/functions/account_functions.php index a2b8b91..d12b0af 100644 --- a/functions/account_functions.php +++ b/functions/account_functions.php @@ -1,5 +1,5 @@ × + @@ -260,6 +261,7 @@ function update_email_form() × + @@ -299,6 +301,7 @@ function update_username_form() × + @@ -387,6 +390,7 @@ function delete_account_form() × + @@ -402,7 +406,7 @@ function delete_account_form() return $form; } -function delete_account($user_id, $password, $verify_password) +function delete_account($user_id, $password = null, $verify_password = null) { if ($user_id == 2025) { $error_msg = ' @@ -424,23 +428,43 @@ function delete_account($user_id, $password, $verify_password) return $error_msg; } - $password_hash = hash('sha256', $password); - $query = 'SELECT user_id FROM user WHERE user_id = ' . $user_id . ' AND password_hash = "' . $password_hash . '";'; - $user = query_one_result($query); + if ($password && $verify_password) { + $password_hash = hash('sha256', $password); + $query = 'SELECT user_id, profile_picture FROM user WHERE user_id = ' . $user_id . ' AND password_hash = "' . $password_hash . '";'; + $user = query_one_result($query); - if ($user == null) { - $error_msg = ' + if ($user == null) { + $error_msg = '
    Username and/or Password are invalid!
    '; - return $error_msg; - } + return $error_msg; + } - $stmt = 'DELETE FROM user WHERE user_id = ' . $user_id . ';'; - exec_statement($stmt, 1); - logout(); + delete_file($_ENV['PROFILE_IMAGES_FQ_PATH'] . $user['profile_picture']); + $stmt = 'DELETE FROM user WHERE user_id = ' . $user_id . ';'; + exec_statement($stmt, 1); + logout(); + } else if ($_COOKIE['role_id'] < 3) { + $query = 'SELECT user_id FROM user WHERE user_id = ' . $user_id . ';'; + $user = query_one_result($query); + + if ($user['user_id'] == 2025) { + $error_msg = ' +
    + Cannot delete the owner! +
    + '; + + return $error_msg; + } + + delete_file($_ENV['PROFILE_IMAGES_FQ_PATH'] . $user['profile_picture']); + $stmt = 'DELETE FROM user WHERE user_id = ' . $user_id . ';'; + exec_statement($stmt, 1); + } } function user_view($user_id) @@ -492,9 +516,3 @@ function user_view($user_id) return $view; } - -function admin_user_view($user_id) -{ - $query = 'SELECT user_id, username, email, role_id, created_at, last_active, is_active, profile_picture FROM user WHERE user_id = ' . $user_id . ';'; - $user = query_one_result($query); -} diff --git a/functions/admin_functions.php b/functions/admin_functions.php new file mode 100644 index 0000000..807487e --- /dev/null +++ b/functions/admin_functions.php @@ -0,0 +1,146 @@ + +
    +

    Articles

    + View Users +
    +
    + + +
    + + +
    + + + + + + + + + + + + + + + + '; + $query = 'SELECT article_id, username, title, read_count, article.created_at, updated_at, published_at, excerpt FROM article INNER JOIN user ON article.author_id = user.user_id;'; + + $results = query_one_or_more_results($query); + + foreach ($results as $key => $result) { + $display .= ' + + + + + + + + + + + '; + } + + $display .= ' + +
    SelectArticle IDAuthorTitleCreatedUpdatedPublishedRead Count
    + + ' . $result['article_id'] . '' . $result['username'] . '' . $result['title'] . '' . $result['created_at'] . '' . $result['updated_at'] . '' . $result['published_at'] . '' . $result['read_count'] . '
    + + + + '; + + return $display; +} +function admin_user_view() +{ + $display = ' +
    +
    +

    Users

    + View Articles +
    +
    +
    + +
    + + +
    + + + + + + + + + + + + + + '; + $query = 'SELECT user_id, username, email, last_active, role, created_at, is_active, profile_picture FROM user INNER JOIN roles ON user.role_id = roles.role_id;'; + + $results = query_one_or_more_results($query); + + foreach ($results as $key => $result) { + $display .= ' + + + + + + + + + + + + '; + } + + $display .= ' +
    SelectUser IDUsernameEmailRoleCreatedLast ActiveActiveProfile Picture
    + + ' . $result['user_id'] . '' . $result['username'] . '' . $result['email'] . '' . ucwords($result['role']) . '' . $result['created_at'] . '' . $result['last_active'] . '' . $result['is_active'] . '
    + +
    +
    + '; + + return $display; +} + +function delete_users($ids) +{ + foreach ($ids as $id) { + delete_account($id); + } +} diff --git a/functions/article_functions.php b/functions/article_functions.php index 3de1238..44c2d09 100644 --- a/functions/article_functions.php +++ b/functions/article_functions.php @@ -1,5 +1,5 @@ Published on ' . $article['published_at'] . '

    - +
    @@ -129,8 +142,10 @@ function create_article($author_id, $title, $excerpt, $tags, $markdown_file_cont $stmt = 'INSERT INTO article (author_id, title, excerpt, published_at) VALUES (' . $author_id . ', "' . $title . '", "' . $excerpt . '", CURRENT_TIMESTAMP);'; $id = exec_statement($stmt, 0); - $path = $_ENV['ARTICLES_PQ_PATH'] . $id . '/'; + $path = $_ENV['ARTICLES_FQ_PATH'] . $id . '/'; mkdir($path); + echo '

    created ' . $path . '

    '; + $fs = fopen($path . 'article.md', 'a'); fwrite($fs, $markdown_file_contents); fclose($fs); @@ -146,8 +161,6 @@ function create_article($author_id, $title, $excerpt, $tags, $markdown_file_cont $stmt .= ';'; } - echo $stmt; - pretty_dump($tags); if (count($tags) > 0) exec_statement($stmt, 0); @@ -156,8 +169,13 @@ function create_article($author_id, $title, $excerpt, $tags, $markdown_file_cont function delete_article($article_id) { + $stmt = 'DELETE FROM article_tags WHERE article_id = ' . $article_id . ';'; + exec_statement($stmt, 1); + $stmt = 'DELETE FROM article WHERE article_id = ' . $article_id . ';'; - return exec_statement($stmt, 1); + exec_statement($stmt, 1); + + delete_dir($_ENV['ARTICLES_FQ_PATH'] . $article_id . '/'); } function delete_articles_by_author($author_id) @@ -165,3 +183,9 @@ function delete_articles_by_author($author_id) $stmt = 'DELETE FROM article WHERE author_id = "' . $author_id . '";'; return exec_statement($stmt, 1); } + +function increment_read_counter($article_id) +{ + $stmt = 'UPDATE article SET read_count = read_count + 1 WHERE article_id = ' . $article_id . ';'; + return exec_statement($stmt, 1); +} diff --git a/functions/db_functions.php b/functions/db_functions.php index 9b5d7e3..16facff 100644 --- a/functions/db_functions.php +++ b/functions/db_functions.php @@ -1,4 +1,9 @@ safeLoad(); + // Use environment variables for the database password and IP address. $db_user = $_ENV['DATABASE_USER']; $db_pass = $_ENV['DATABASE_PASSWORD']; diff --git a/functions/functions.php b/functions/functions.php index 468a3b1..d384291 100644 --- a/functions/functions.php +++ b/functions/functions.php @@ -9,6 +9,30 @@ if (!empty($_FILES['upload'])) { upload($_FILES['upload'], $_ENV['UPLOAD_TMP_FQ_PATH'], isset($_POST['filename']) ? $_POST['filename'] : null); } +function delete_dir($dir) +{ + if (!is_dir($dir)) return false; + + if (substr($dir, strlen($dir) - 1, 1) != '/') { + $dir .= '/'; + } + $files = glob($dir . '*', GLOB_MARK); + foreach ($files as $file) { + if (is_dir($file)) + delete_dir($file); + else + delete_file($file); + } + rmdir($dir); +} + +function delete_file($file) +{ + if (!file_exists($file)) return false; + + unlink($file); +} + function upload($input, $dest_dir, $filename = null) { $filetype = strtolower(pathinfo($input['name'], PATHINFO_EXTENSION)); diff --git a/functions/init.php b/functions/init.php index 989d46c..f805cd0 100644 --- a/functions/init.php +++ b/functions/init.php @@ -3,6 +3,7 @@ session_start(); // Initialize Composer. require_once 'vendor/autoload.php'; +require_once 'db_functions.php'; // Load environment variables. $dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../'); @@ -18,3 +19,6 @@ if ($_SESSION['initialized'] && isset($_POST['theme'])) { $_SESSION['theme'] = $_POST['theme']; unset($_POST['theme']); } + +if (isset($_COOKIE['user_id'])) + exec_statement('UPDATE user SET is_active = true WHERE user_id = ' . $_COOKIE['user_id'] . ';', 1); diff --git a/functions/profile.jpg b/functions/profile.jpg deleted file mode 100644 index 8dc6d4f..0000000 Binary files a/functions/profile.jpg and /dev/null differ diff --git a/scripts/dummy_data.sql b/scripts/dummy_data.sql new file mode 100644 index 0000000..ac688c2 --- /dev/null +++ b/scripts/dummy_data.sql @@ -0,0 +1,14 @@ +USE vintagecoding; + +INSERT INTO user (user_id, username, email, password_hash, role_id, profile_picture) + VALUES (5749248, 'jsmith', 'jsmith@email.com', 'f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b', 4, '2025.jpg'), + (5789203, 'dummy', 'dummy@iamdumb.com', 'f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b', 4, '2025.jpg'), + (5723948, 'writer', 'writer@something.com', 'f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b', 3, '5723948.jpg') +; + +INSERT INTO article (author_id, title, excerpt, published_at) + VALUES (2025, 'Testing vintagecoding.net', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a mauris sit amet dui pellentesque sodales. Pellentesque non viverra leo, et congue metus.', CURRENT_TIMESTAMP), + (5723948, 'Something Else', 'Here goes some random crap. Lorem ipsum akdfljskjlag IDK what to put here. I am just filling the space requirement. Blah blah blah blah, what does this look like? Is vintagecoding.net cool?', CURRENT_TIMESTAMP) +; + +INSERT INTO article_tags (article_id, tag_id) VALUES (21, 2), (21, 6); diff --git a/scripts/init.sql b/scripts/init.sql index c1f3e98..86fc115 100644 --- a/scripts/init.sql +++ b/scripts/init.sql @@ -53,13 +53,9 @@ INSERT INTO tags (tag) VALUES ('linux'), ('web-dev'), ('raspberry-pi'), ('self-hosting'), ('devlog'), ('testing'); INSERT INTO user (user_id, username, email, password_hash, role_id, profile_picture) - VALUES (2025, 'quaxlyqueen', 'me@joshashton.dev', 'changeme', 1, '2025.jpg'); + VALUES (2025, 'quaxlyqueen', 'me@joshashton.dev', '057ba03d6c44104863dc7361fe4578965d1887360f90a0895882e58a6248fc86', 1, '2025.jpg'); INSERT INTO article (author_id, title, excerpt, published_at) - VALUES (2025, 'Building vintagecoding.net', '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.', CURRENT_TIMESTAMP); - -INSERT INTO article (author_id, title, excerpt, published_at) - VALUES (2025, 'Testing vintagecoding.net', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a mauris sit amet dui pellentesque sodales. Pellentesque non viverra leo, et congue metus.', CURRENT_TIMESTAMP); + VALUES (2025, 'Building vintagecoding.net', 'A devlog of the creation of vintagecoding.net and a test at the formatting of article cards and articles. Let the great experiment begin!', CURRENT_TIMESTAMP); INSERT INTO article_tags (article_id, tag_id) VALUES (1, 2), (1, 4), (1, 5); -INSERT INTO article_tags (article_id, tag_id) VALUES (2, 2), (2, 6); diff --git a/user.php b/user.php index 3838e49..afd8ffc 100644 --- a/user.php +++ b/user.php @@ -42,7 +42,7 @@ if (isset($_POST['form_id'])) { case 'reset_pw_form': $msg = reset_password( - $_COOKIE['user_id'], + ($_COOKIE['role_id'] <= 2 && $_COOKIE['user_id'] != $_POST['user_id'] ? $_POST['user_id'] : $_COOKIE['user_id']), $_POST['new_pw'], $_POST['verify_new_pw'] ); @@ -53,7 +53,7 @@ if (isset($_POST['form_id'])) { case 'update_email_form': $msg = update_email( - $_COOKIE['user_id'], + ($_COOKIE['role_id'] <= 2 && $_COOKIE['user_id'] != $_POST['user_id'] ? $_POST['user_id'] : $_COOKIE['user_id']), $_POST['new_email'], $_POST['verify_new_email'] ); @@ -64,7 +64,7 @@ if (isset($_POST['form_id'])) { case 'update_username_form': $msg = update_username( - $_COOKIE['user_id'], + ($_COOKIE['role_id'] <= 2 && $_COOKIE['user_id'] != $_POST['user_id'] ? $_POST['user_id'] : $_COOKIE['user_id']), $_POST['new_username'], $_POST['verify_new_username'] ); @@ -75,7 +75,7 @@ if (isset($_POST['form_id'])) { case 'delete_account_form': $msg = delete_account( - $_COOKIE['user_id'], + ($_COOKIE['role_id'] <= 2 && $_COOKIE['user_id'] != $_POST['user_id'] ? $_POST['user_id'] : $_COOKIE['user_id']), $_POST['delete_password'], $_POST['delete_verify_password'] );