implemented following users to receive (opt-in) email notifications when they publish an article.
This commit is contained in:
+11
-2
@@ -5,6 +5,7 @@ require_once 'functions/functions.php';
|
||||
include_once 'components/head.php';
|
||||
|
||||
include_once 'functions/article_functions.php';
|
||||
include_once 'functions/account_functions.php';
|
||||
|
||||
// UI Components
|
||||
foreach (glob('components/article/*.php') as $file)
|
||||
@@ -22,12 +23,21 @@ if (isset($_POST['form_id'])) {
|
||||
header('Location: articles.php?article_id=' . $_POST['article_id']);
|
||||
} else {
|
||||
$id = create_article($_SESSION['user_id'], $_POST['title'], $_POST['excerpt'], (isset($_POST['tags']) ? $_POST['tags'] : []), $_POST['markdown']);
|
||||
$emails = exec_stmt('SELECT email FROM user INNER JOIN follows ON user.user_id = follows.follower_user_id WHERE follows.following_user_id = ? AND follows.notify_user = 1', 'i', $_SESSION['user_id'])->fetch_assoc();
|
||||
notify_users($emails, $_SESSION['username'], $id, $_POST['title'], $_POST['excerpt']);
|
||||
header('Location: articles.php?article_id=' . $id);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'follow':
|
||||
follow_user($_SESSION['user_id'], $_POST['author_id'], ($_POST['follow'] == 'email_notify' ? 1 : 0));
|
||||
break;
|
||||
case 'unfollow':
|
||||
unfollow_user($_SESSION['user_id'], $_POST['author_id']);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (isset($_GET['view']))
|
||||
$view = $_GET['view'];
|
||||
else
|
||||
@@ -65,7 +75,6 @@ if (isset($_POST['form_id'])) {
|
||||
echo compose_view((isset($_GET['article_id']) ? $_GET['article_id'] : ''));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (($view == 'list' || $view == 'sort') && (isset($_SESSION['role_id']) && $_SESSION['role_id'] <= 3)) {
|
||||
echo '
|
||||
|
||||
@@ -8,6 +8,7 @@ function article_page_from_markdown($article_id)
|
||||
$article = exec_stmt('SELECT * FROM article WHERE article_id = ?', 'i', $article_id)->fetch_assoc();
|
||||
$author = exec_stmt('SELECT user_id, username, profile_picture FROM user WHERE user_id = ?', 'i', $article['author_id'])->fetch_assoc();
|
||||
$markdown = read_file_one_string($_ENV['ARTICLES_FQ_PATH'] . $article_id . '/article.md');
|
||||
$following = exec_stmt('SELECT follower_user_id, following_user_id FROM follows WHERE follower_user_id = ? AND following_user_id = ?', 'ii', $_SESSION['user_id'], $author['user_id'])->fetch_assoc();
|
||||
|
||||
$parsedown = new Parsedown();
|
||||
$parsedown->setSafeMode(true);
|
||||
@@ -20,12 +21,43 @@ function article_page_from_markdown($article_id)
|
||||
<h1>' . $article['title'] . '</h1>
|
||||
' . (isset($_SESSION['user_id']) && $_SESSION['user_id'] == $article['author_id'] ? '<a href="articles.php?view=compose&article_id=' . $article_id . '"><i class="nf nf-fa-edit"></i></a>' : '') . '
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<div class="col_right">
|
||||
<h5>Written by <a href="user.php?action=view&user_id=' . $author['user_id'] . '">' . $author['username'] . '</a></h5>
|
||||
<h5>Written by <a href="user.php?view=display&user_id=' . $author['user_id'] . '">' . $author['username'] . '</a></h5>
|
||||
<p>Published on ' . $article['published_at'] . '<br>
|
||||
Lasted edited on ' . $article['updated_at'] . '</p>
|
||||
</div>
|
||||
';
|
||||
|
||||
if ($_SESSION['user_id'] != $author['user_id'] && !$following) {
|
||||
$html .= '
|
||||
<div class="dropdown">
|
||||
<span>Follow</span>
|
||||
<div class="dropdown_content">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form_id" value="follow">
|
||||
<input type="hidden" name="author_id" value="' . $author['user_id'] . '">
|
||||
<button name="follow" value="no_notify" class="button"><i class="nf nf-md-bell_cancel"></i></button>
|
||||
<button name="follow" value="email_notify" class="button"><i class="nf nf-cod-mail"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
} else if ($following) {
|
||||
$html .= '
|
||||
<div class="margin_right">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form_id" value="unfollow">
|
||||
<input type="hidden" name="author_id" value="' . $author['user_id'] . '">
|
||||
<input type="submit" value="Unfollow">
|
||||
</form>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
$html .= '
|
||||
</div>
|
||||
<div class="card_pic_box">
|
||||
<img src="' . $_ENV['PROFILE_IMAGES_PQ_PATH'] . $author['profile_picture'] . '" class="card_profile_picture" />
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@ function article_card($article_id)
|
||||
<h4>' . $article['title'] . '</h4>
|
||||
<div class="card_info_box">
|
||||
<div class="col-right">
|
||||
<a href="user.php?action=view&user_id=' . $author['user_id'] . '">Written by: ' . $author['username'] . '</a>
|
||||
<a href="user.php?view=display&user_id=' . $author['user_id'] . '">Written by: ' . $author['username'] . '</a>
|
||||
<small>Published: ' . $article['published_at'] . '</small>
|
||||
<small>Updated: ' . $article['updated_at'] . '</small>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,50 @@
|
||||
<?php
|
||||
include_once 'components/article/card.php';
|
||||
|
||||
function user_view($user_id)
|
||||
{
|
||||
if ($_SESSION['user_id'] != $user_id)
|
||||
$following = exec_stmt('SELECT follower_user_id, following_user_id FROM follows WHERE follower_user_id = ? AND following_user_id = ?', 'ii', $_SESSION['user_id'], $user_id)->fetch_assoc();
|
||||
$user = exec_stmt('SELECT user_id, username, email, role_id, created_at, last_active, is_active, profile_picture FROM user WHERE user_id = ?', 'i', $user_id)->fetch_assoc();
|
||||
|
||||
if ($user == null)
|
||||
header('Location: articles.php');
|
||||
header('Location: articles.php?view=sort&sort=newest');
|
||||
|
||||
$view = '
|
||||
<div class="user_view">
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<h1>' . $user['username'] . '</h1>
|
||||
';
|
||||
|
||||
if ($_SESSION['user_id'] != $user_id && !$following) {
|
||||
$view .= '
|
||||
<div class="dropdown">
|
||||
<span>Follow</span>
|
||||
<div class="dropdown_content">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form_id" value="follow">
|
||||
<input type="hidden" name="author_id" value="' . $user_id . '">
|
||||
<button name="follow" value="no_notify" class="button"><i class="nf nf-md-bell_cancel"></i></button>
|
||||
<button name="follow" value="email_notify" class="button"><i class="nf nf-cod-mail"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
} else if ($following) {
|
||||
$view .= '
|
||||
<div class="margin_right">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form_id" value="unfollow">
|
||||
<input type="hidden" name="author_id" value="' . $user_id . '">
|
||||
<input type="submit" value="Unfollow">
|
||||
</form>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
$view .= '
|
||||
</div>
|
||||
<div class="card_pic_box">
|
||||
<img src="' . $_ENV['PROFILE_IMAGES_PQ_PATH'] . $user['profile_picture'] . '" class="profile_picture" />
|
||||
</div>
|
||||
|
||||
+37
-12
@@ -31,7 +31,8 @@ a,
|
||||
.modal_button,
|
||||
th,
|
||||
td,
|
||||
li {
|
||||
li,
|
||||
span {
|
||||
font-family: 'Hack Nerd Font Mono', Hack, sans-serif;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -65,7 +66,8 @@ a,
|
||||
label,
|
||||
li,
|
||||
.modal_button,
|
||||
.button {
|
||||
.button,
|
||||
span {
|
||||
font-size: 1vw;
|
||||
}
|
||||
|
||||
@@ -285,10 +287,6 @@ table tr td label {
|
||||
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: 20%;
|
||||
}
|
||||
@@ -301,10 +299,6 @@ table tr td label {
|
||||
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 {
|
||||
@@ -383,7 +377,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -734,6 +727,37 @@ hr {
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-right: 15px;
|
||||
padding: 15px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.dropdown_content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
min-width: 50px;
|
||||
padding: 15px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown_content {
|
||||
display: block;
|
||||
margin-left: -15px;
|
||||
margin-top: 15px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.dropdown_content form * {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.margin_right {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
body {
|
||||
@@ -772,7 +796,8 @@ hr {
|
||||
ul li a,
|
||||
ol li a,
|
||||
.modal_button,
|
||||
.button {
|
||||
.button,
|
||||
span {
|
||||
font-size: 2vw;
|
||||
}
|
||||
|
||||
|
||||
+14
-4
@@ -13,7 +13,8 @@ h4,
|
||||
h5,
|
||||
p,
|
||||
small,
|
||||
label {
|
||||
label,
|
||||
span {
|
||||
color: white;
|
||||
}
|
||||
|
||||
@@ -54,9 +55,9 @@ table tr {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
color: black;
|
||||
background-color: white;
|
||||
.button:hover,
|
||||
.button:hover i {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.tag {
|
||||
@@ -139,6 +140,15 @@ select:-webkit-autofill {
|
||||
border: 2px solid orange;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.dropdown_content {
|
||||
background-color: black;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
nav {
|
||||
border-bottom: none;
|
||||
|
||||
+14
-4
@@ -13,7 +13,8 @@ h4,
|
||||
h5,
|
||||
p,
|
||||
small,
|
||||
label {
|
||||
label,
|
||||
span {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@@ -54,9 +55,9 @@ table tr {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
color: white;
|
||||
background-color: black;
|
||||
.button:hover,
|
||||
.button:hover * {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.tag {
|
||||
@@ -139,6 +140,15 @@ select:-webkit-autofill {
|
||||
border: 2px solid orange;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
.dropdown_content {
|
||||
background-color: white;
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
nav {
|
||||
border-bottom: none;
|
||||
|
||||
@@ -4,6 +4,16 @@ require_once 'article_functions.php';
|
||||
|
||||
use Postmark\PostmarkClient;
|
||||
|
||||
function follow_user($follower_user_id, $following_user_id, $notify)
|
||||
{
|
||||
exec_stmt('INSERT INTO follows (follower_user_id, following_user_id, notify_user) VALUES (?, ?, ?)', 'iii', $follower_user_id, $following_user_id, $notify);
|
||||
}
|
||||
|
||||
function unfollow_user($follower_user_id, $following_user_id)
|
||||
{
|
||||
exec_stmt('DELETE FROM follows WHERE follower_user_id = ? AND following_user_id = ?', 'ii', $follower_user_id, $following_user_id);
|
||||
}
|
||||
|
||||
function user_id_exists($user_id)
|
||||
{
|
||||
$result = exec_stmt('SELECT user_id FROM user WHERE user_id = ?', 'i', $user_id)->fetch_assoc();
|
||||
|
||||
+32
-14
@@ -1,17 +1,36 @@
|
||||
<?php
|
||||
include_once 'functions/init.php';
|
||||
|
||||
if (!empty($_FILES['upload'])) {
|
||||
require_once '../vendor/autoload.php';
|
||||
use Postmark\PostmarkClient;
|
||||
|
||||
// Load environment variables.
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
|
||||
$dotenv->safeLoad();
|
||||
if (!empty($_FILES['upload']))
|
||||
upload($_FILES['upload'], $_ENV['UPLOAD_TMP_FQ_PATH'], isset($_POST['filename']) ? $_POST['filename'] : null);
|
||||
|
||||
function notify_users($emails, $auther_username, $article_id, $article_title, $excerpt)
|
||||
{
|
||||
foreach ($emails as $email) {
|
||||
$email_html = '
|
||||
<h1>' . $article_title . '</h1>
|
||||
<p>An author you follow just published a new article! Read it <a href="https://vintagecoding.net/articles.php?view=read&article_id=' . $article_id . '" target="_blank">here</a></p>
|
||||
<p><strong>Here is a snippet from the article!</strong></p>
|
||||
<p>' . $excerpt . '</p>
|
||||
';
|
||||
|
||||
$client = new PostmarkClient($_ENV['POSTMARK_API_TOKEN']);
|
||||
|
||||
$client->sendEmail(
|
||||
'mailer@joshashton.dev',
|
||||
$email,
|
||||
$auther_username . ' just published a new article! - vintagecoding.net',
|
||||
$email_html
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function delete_dir($dir)
|
||||
{
|
||||
if (!is_dir($dir)) return false;
|
||||
if (!is_dir($dir))
|
||||
return false;
|
||||
|
||||
if (substr($dir, strlen($dir) - 1, 1) != '/') {
|
||||
$dir .= '/';
|
||||
@@ -28,7 +47,8 @@ function delete_dir($dir)
|
||||
|
||||
function delete_file($file)
|
||||
{
|
||||
if (!file_exists($file)) return false;
|
||||
if (!file_exists($file))
|
||||
return false;
|
||||
|
||||
unlink($file);
|
||||
}
|
||||
@@ -54,7 +74,7 @@ function upload($input, $dest_dir, $filename = null)
|
||||
|
||||
break;
|
||||
default:
|
||||
# code...
|
||||
// code...
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -132,7 +152,7 @@ function extract_content_from_tag($string, $start, $end)
|
||||
{
|
||||
$ini = strpos($string, $start);
|
||||
if ($ini != 0)
|
||||
return "nope";
|
||||
return 'nope';
|
||||
|
||||
$ini += strlen($start);
|
||||
$len = strpos($string, $end, $ini) - $ini;
|
||||
@@ -151,9 +171,7 @@ function check_csv_upload($target)
|
||||
|
||||
function pretty_dump($arr)
|
||||
{
|
||||
echo '
|
||||
<pre>
|
||||
' . print_r($arr) . '
|
||||
</pre>
|
||||
';
|
||||
echo '<pre>';
|
||||
print_r($arr);
|
||||
echo '</pre>';
|
||||
}
|
||||
|
||||
@@ -60,6 +60,16 @@ CREATE TABLE article_tags (
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(tag_id)
|
||||
);
|
||||
|
||||
CREATE TABLE follows (
|
||||
follower_user_id INT NOT NULL,
|
||||
following_user_id INT NOT NULL,
|
||||
notify_user BOOLEAN DEFAULT FALSE,
|
||||
followed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (follower_user_id, following_user_id),
|
||||
FOREIGN KEY (follower_user_id) REFERENCES user(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (following_user_id) REFERENCES user(user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO roles (role)
|
||||
VALUES ('owner'), ('admin'), ('contributor'), ('reader');
|
||||
|
||||
|
||||
@@ -82,6 +82,12 @@ if (isset($_POST['form_id'])) {
|
||||
$_POST['delete_verify_password']
|
||||
);
|
||||
|
||||
break;
|
||||
case 'follow':
|
||||
follow_user($_SESSION['user_id'], $_POST['author_id'], ($_POST['follow'] == 'email_notify' ? 1 : 0));
|
||||
break;
|
||||
case 'unfollow':
|
||||
unfollow_user($_SESSION['user_id'], $_POST['author_id']);
|
||||
break;
|
||||
}
|
||||
echo $msg;
|
||||
|
||||
Reference in New Issue
Block a user