From c393146d9e3d494943de32ecd86983335358cf88 Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Fri, 5 Jul 2024 19:07:15 +0200 Subject: [PATCH] feat: add cancelation reminders (#425) --- README.md | 3 + cronjobs | 1 + .../sendcancellationnotifications.php | 470 ++++++++++++++++++ endpoints/cronjobs/sendnotifications.php | 2 +- endpoints/subscription/add.php | 12 +- endpoints/subscription/clone.php | 3 +- endpoints/subscription/get.php | 1 + includes/i18n/de.php | 1 + includes/i18n/el.php | 1 + includes/i18n/en.php | 1 + includes/i18n/es.php | 1 + includes/i18n/fr.php | 1 + includes/i18n/it.php | 1 + includes/i18n/jp.php | 1 + includes/i18n/ko.php | 1 + includes/i18n/pl.php | 1 + includes/i18n/pt.php | 1 + includes/i18n/pt_br.php | 1 + includes/i18n/ru.php | 1 + includes/i18n/sl.php | 1 + includes/i18n/sr.php | 2 + includes/i18n/sr_lat.php | 1 + includes/i18n/tr.php | 1 + includes/i18n/zh_cn.php | 1 + includes/i18n/zh_tw.php | 1 + includes/version.php | 2 +- index.php | 112 +++-- migrations/000024.php | 10 + screenshots/wallos-calendar.png | Bin 0 -> 42043 bytes scripts/dashboard.js | 3 + styles/dark-theme.css | 4 + styles/styles.css | 14 + 32 files changed, 600 insertions(+), 56 deletions(-) create mode 100644 endpoints/cronjobs/sendcancellationnotifications.php create mode 100644 migrations/000024.php create mode 100644 screenshots/wallos-calendar.png diff --git a/README.md b/README.md index f7e1730a4..4b6684a89 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ See instructions to run Wallos below. ```bash 0 1 * * * php /var/www/html/endpoints/cronjobs/updatenextpayment.php >> /var/log/cron/updatenextpayment.log 2>&1 0 2 * * * php /var/www/html/endpoints/cronjobs/updateexchange.php >> /var/log/cron/updateexchange.log 2>&1 +0 8 * * * php /var/www/html/endpoints/cronjobs/sendcancellationnotifications.php >> /var/log/cron/sendcancellationnotifications.log 2>&1 0 9 * * * php /var/www/html/endpoints/cronjobs/sendnotifications.php >> /var/log/cron/sendnotifications.log 2>&1 */2 * * * * php /var/www/html/endpoints/cronjobs/sendverificationemails.php >> /var/log/cron/sendverificationemail.log 2>&1 */2 * * * * php /var/www/html/endpoints/cronjobs/sendresetpasswordemails.php >> /var/log/cron/sendresetpasswordemails.log 2>&1 @@ -138,6 +139,8 @@ If you want to trigger an Update of the exchange rates, change your main currenc ![Screenshot](screenshots/wallos-stats.png) +![Screenshot](screenshots/wallos-calendar.png) + ![Screenshot](screenshots/wallos-form.png) ![Screenshot](screenshots/wallos-dashboard-mobile-light.png) ![Screenshot](screenshots/wallos-dashboard-mobile-dark.png) diff --git a/cronjobs b/cronjobs index 500cc7b6f..fe9372b13 100644 --- a/cronjobs +++ b/cronjobs @@ -1,6 +1,7 @@ # Run the scripts every day 0 1 * * * /usr/local/bin/php /var/www/html/endpoints/cronjobs/updatenextpayment.php >> /var/log/cron/updatenextpayment.log 2>&1 0 2 * * * /usr/local/bin/php /var/www/html/endpoints/cronjobs/updateexchange.php >> /var/log/cron/updateexchange.log 2>&1 +0 8 * * * /usr/local/bin/php /var/www/html/endpoints/cronjobs/sendcancellationnotifications.php >> /var/log/cron/sendcancellationnotifications.log 2>&1 0 9 * * * /usr/local/bin/php /var/www/html/endpoints/cronjobs/sendnotifications.php >> /var/log/cron/sendnotifications.log 2>&1 */2 * * * * /usr/local/bin/php /var/www/html/endpoints/cronjobs/sendverificationemails.php >> /var/log/cron/sendverificationemails.log 2>&1 */2 * * * * /usr/local/bin/php /var/www/html/endpoints/cronjobs/sendresetpasswordemails.php >> /var/log/cron/sendresetpasswordemails.log 2>&1 diff --git a/endpoints/cronjobs/sendcancellationnotifications.php b/endpoints/cronjobs/sendcancellationnotifications.php new file mode 100644 index 000000000..c24350871 --- /dev/null +++ b/endpoints/cronjobs/sendcancellationnotifications.php @@ -0,0 +1,470 @@ +prepare($query); +$usersToNotify = $stmt->execute(); + +while ($userToNotify = $usersToNotify->fetchArray(SQLITE3_ASSOC)) { + $userId = $userToNotify['id']; + echo "For user: " . $userToNotify['username'] . "
"; + + $emailNotificationsEnabled = false; + $gotifyNotificationsEnabled = false; + $telegramNotificationsEnabled = false; + $pushoverNotificationsEnabled = false; + $discordNotificationsEnabled = false; + $ntfyNotificationsEnabled = false; + + // Check if email notifications are enabled and get the settings + $query = "SELECT * FROM email_notifications WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + if ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $emailNotificationsEnabled = $row['enabled']; + $email['smtpAddress'] = $row["smtp_address"]; + $email['smtpPort'] = $row["smtp_port"]; + $email['encryption'] = $row["encryption"]; + $email['smtpUsername'] = $row["smtp_username"]; + $email['smtpPassword'] = $row["smtp_password"]; + $email['fromEmail'] = $row["from_email"] ? $row["from_email"] : "wallos@wallosapp.com"; + } + + // Check if Discord notifications are enabled and get the settings + $query = "SELECT * FROM discord_notifications WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + if ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $discordNotificationsEnabled = $row['enabled']; + $discord['webhook_url'] = $row["webhook_url"]; + $discord['bot_username'] = $row["bot_username"]; + $discord['bot_avatar_url'] = $row["bot_avatar_url"]; + } + + // Check if Gotify notifications are enabled and get the settings + $query = "SELECT * FROM gotify_notifications WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + $gotify = []; + + if ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $gotifyNotificationsEnabled = $row['enabled']; + $gotify['serverUrl'] = $row["url"]; + $gotify['appToken'] = $row["token"]; + } + + // Check if Telegram notifications are enabled and get the settings + $query = "SELECT * FROM telegram_notifications WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + if ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $telegramNotificationsEnabled = $row['enabled']; + $telegram['botToken'] = $row["bot_token"]; + $telegram['chatId'] = $row["chat_id"]; + } + + // Check if Pushover notifications are enabled and get the settings + $query = "SELECT * FROM pushover_notifications WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + if ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $pushoverNotificationsEnabled = $row['enabled']; + $pushover['user_key'] = $row["user_key"]; + $pushover['token'] = $row["token"]; + } + + // Check if Ntfy notifications are enabled and get the settings + $query = "SELECT * FROM ntfy_notifications WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + if ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $ntfyNotificationsEnabled = $row['enabled']; + $ntfy['host'] = $row["host"]; + $ntfy['topic'] = $row["topic"]; + $ntfy['headers'] = $row["headers"]; + } + + $notificationsEnabled = $emailNotificationsEnabled || $gotifyNotificationsEnabled || $telegramNotificationsEnabled || + $pushoverNotificationsEnabled || $discordNotificationsEnabled ||$ntfyNotificationsEnabled; + + // If no notifications are enabled, no need to run + if (!$notificationsEnabled) { + echo "Notifications are disabled. No need to run.
"; + continue; + } else { + // Get all currencies + $currencies = array(); + $query = "SELECT * FROM currencies WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $currencies[$row['id']] = $row; + } + + // Get all household members + $query = "SELECT * FROM household WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $resultHousehold = $stmt->execute(); + + $household = []; + while ($rowHousehold = $resultHousehold->fetchArray(SQLITE3_ASSOC)) { + $household[$rowHousehold['id']] = $rowHousehold; + } + + // Get all categories + $query = "SELECT * FROM categories WHERE user_id = :userId"; + $stmt = $db->prepare($query); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $resultCategories = $stmt->execute(); + + $categories = []; + while ($rowCategory = $resultCategories->fetchArray(SQLITE3_ASSOC)) { + $categories[$rowCategory['id']] = $rowCategory; + } + + // Get current date to check which subscriptions are set to notify for cancellation + $currentDate = new DateTime('now'); + $currentDate = $currentDate->format('Y-m-d'); + + $query = "SELECT * FROM subscriptions WHERE user_id = :user_id AND inactive = :inactive AND cancellation_date = :cancellationDate ORDER BY payer_user_id ASC"; + $stmt = $db->prepare($query); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $stmt->bindValue(':inactive', 0, SQLITE3_INTEGER); + $stmt->bindValue(':cancellationDate', $currentDate, SQLITE3_TEXT); + $resultSubscriptions = $stmt->execute(); + + $notify = []; + $i = 0; + $currentDate = new DateTime('now'); + while ($rowSubscription = $resultSubscriptions->fetchArray(SQLITE3_ASSOC)) { + $notify[$rowSubscription['payer_user_id']][$i]['name'] = $rowSubscription['name']; + $notify[$rowSubscription['payer_user_id']][$i]['price'] = $rowSubscription['price'] . $currencies[$rowSubscription['currency_id']]['symbol']; + $notify[$rowSubscription['payer_user_id']][$i]['currency'] = $currencies[$rowSubscription['currency_id']]['name']; + $notify[$rowSubscription['payer_user_id']][$i]['category'] = $categories[$rowSubscription['category_id']]['name']; + $notify[$rowSubscription['payer_user_id']][$i]['payer'] = $household[$rowSubscription['payer_user_id']]['name']; + $notify[$rowSubscription['payer_user_id']][$i]['date'] = $rowSubscription['next_payment']; + $i++; + } + + if (!empty($notify)) { + + // Email notifications if enabled + if ($emailNotificationsEnabled) { + + $stmt = $db->prepare('SELECT * FROM user WHERE id = :user_id'); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $defaultUser = $result->fetchArray(SQLITE3_ASSOC); + $defaultEmail = $defaultUser['email']; + $defaultName = $defaultUser['username']; + + foreach ($notify as $userId => $perUser) { + $message = "The following subscriptions are up for cancellation:\n"; + + foreach ($perUser as $subscription) { + $message .= $subscription['name'] . " for " . $subscription['price'] ."\n"; + } + + $mail = new PHPMailer(true); + $mail->CharSet = "UTF-8"; + $mail->isSMTP(); + + $mail->Host = $email['smtpAddress']; + $mail->SMTPAuth = true; + $mail->Username = $email['smtpUsername']; + $mail->Password = $email['smtpPassword']; + $mail->SMTPSecure = $email['encryption']; + $mail->Port = $email['smtpPort']; + + $stmt = $db->prepare('SELECT * FROM household WHERE id = :userId'); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $user = $result->fetchArray(SQLITE3_ASSOC); + + $emailaddress = !empty($user['email']) ? $user['email'] : $defaultEmail; + $name = !empty($user['name']) ? $user['name'] : $defaultName; + + $mail->setFrom($email['fromEmail'], 'Wallos App'); + $mail->addAddress($emailaddress, $name); + + $mail->Subject = 'Wallos Cancellation Notification'; + $mail->Body = $message; + + if ($mail->send()) { + echo "Email Notifications sent
"; + } else { + echo "Error sending notifications: " . $mail->ErrorInfo . "
"; + } + } + } + + // Discord notifications if enabled + if ($discordNotificationsEnabled) { + foreach ($notify as $userId => $perUser) { + // Get name of user from household table + $stmt = $db->prepare('SELECT * FROM household WHERE id = :userId'); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $user = $result->fetchArray(SQLITE3_ASSOC); + + $title = translate('wallos_notification', $i18n); + + if ($user['name']) { + $message = $user['name'] . ", the following subscriptions are up for cancellation:\n"; + } else { + $message = "The following subscriptions are up for cancellation:\n"; + } + + foreach ($perUser as $subscription) { + $message .= $subscription['name'] . " for " . $subscription['price'] . "\n"; + } + + $postfields = [ + 'content' => $message + ]; + + if (!empty($discord['bot_username'])) { + $postfields['username'] = $discord['bot_username']; + } + + if (!empty($discord['bot_avatar_url'])) { + $postfields['avatar_url'] = $discord['bot_avatar_url']; + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $discord['webhook_url']); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postfields)); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json' + ]); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + curl_close($ch); + + if ($result === false) { + echo "Error sending notifications: " . curl_error($ch) . "
"; + } else { + echo "Discord Notifications sent
"; + } + } + } + + // Gotify notifications if enabled + if ($gotifyNotificationsEnabled) { + foreach ($notify as $userId => $perUser) { + // Get name of user from household table + $stmt = $db->prepare('SELECT * FROM household WHERE id = :userId'); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $user = $result->fetchArray(SQLITE3_ASSOC); + + if ($user['name']) { + $message = $user['name'] . ", the following subscriptions are up for cancellation:\n"; + } else { + $message = "The following subscriptions are up for cancellation:\n"; + } + + foreach ($perUser as $subscription) { + $message .= $subscription['name'] . " for " . $subscription['price'] . "\n"; + } + + $data = array( + 'message' => $message, + 'priority' => 5 + ); + + $data_string = json_encode($data); + + $ch = curl_init($gotify['serverUrl'] . '/message?token=' . $gotify['appToken']); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt( + $ch, + CURLOPT_HTTPHEADER, + array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen($data_string) + ) + ); + + $result = curl_exec($ch); + if ($result === false) { + echo "Error sending notifications: " . curl_error($ch) . "
"; + } else { + echo "Gotify Notifications sent
"; + } + } + } + + // Telegram notifications if enabled + if ($telegramNotificationsEnabled) { + foreach ($notify as $userId => $perUser) { + // Get name of user from household table + $stmt = $db->prepare('SELECT * FROM household WHERE id = :userId'); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $user = $result->fetchArray(SQLITE3_ASSOC); + + if ($user['name']) { + $message = $user['name'] . ", the following subscriptions are up for cancellation:\n"; + } else { + $message = "The following subscriptions are up for cancellation:\n"; + } + + foreach ($perUser as $subscription) { + $message .= $subscription['name'] . " for " . $subscription['price'] . "\n"; + } + + $data = array( + 'chat_id' => $telegram['chatId'], + 'text' => $message + ); + + $data_string = json_encode($data); + + $ch = curl_init('https://api.telegram.org/bot' . $telegram['botToken'] . '/sendMessage'); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt( + $ch, + CURLOPT_HTTPHEADER, + array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen($data_string) + ) + ); + + $result = curl_exec($ch); + if ($result === false) { + echo "Error sending notifications: " . curl_error($ch) . "
"; + } else { + echo "Telegram Notifications sent
"; + } + } + } + + // Pushover notifications if enabled + if ($pushoverNotificationsEnabled) { + foreach ($notify as $userId => $perUser) { + // Get name of user from household table + $stmt = $db->prepare('SELECT * FROM household WHERE id = :userId'); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $user = $result->fetchArray(SQLITE3_ASSOC); + + if ($user['name']) { + $message = $user['name'] . ", the following subscriptions are up for cancellation:\n"; + } else { + $message = "The following subscriptions are up for cancellation:\n"; + } + + foreach ($perUser as $subscription) { + $message .= $subscription['name'] . " for " . $subscription['price'] . "\n"; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://api.pushover.net/1/messages.json"); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'token' => $pushover['token'], + 'user' => $pushover['user_key'], + 'message' => $message, + ])); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $result = curl_exec($ch); + + curl_close($ch); + + if ($result === false) { + echo "Error sending notifications: " . curl_error($ch) . "
"; + } else { + echo "Pushover Notifications sent
"; + } + } + } + + // Ntfy notifications if enabled + if ($ntfyNotificationsEnabled) { + foreach ($notify as $userId => $perUser) { + // Get name of user from household table + $stmt = $db->prepare('SELECT * FROM household WHERE id = :userId'); + $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $user = $result->fetchArray(SQLITE3_ASSOC); + + if ($user['name']) { + $message = $user['name'] . ", the following subscriptions are up for cancellation:\n"; + } else { + $message = "The following subscriptions are up for cancellation:\n"; + } + + foreach ($perUser as $subscription) { + $message .= $subscription['name'] . " for " . $subscription['price'] . "\n"; + } + + $headers = json_decode($ntfy["headers"], true); + $customheaders = array_map(function ($key, $value) { + return "$key: $value"; + }, array_keys($headers), $headers); + + $ch = curl_init(); + + $ntfyHost = rtrim($ntfy["host"], '/'); + $ntfyTopic = $ntfy['topic']; + + curl_setopt($ch, CURLOPT_URL, $ntfyHost . '/' . $ntfyTopic); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_HTTPHEADER, $customheaders); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + curl_close($ch); + + if ($response === false) { + echo "Error sending notifications: " . curl_error($ch) . "
"; + } else { + echo "Ntfy Notifications sent
"; + } + } + } + + } else { + echo "Nothing to notify.
"; + } + + } + +} + +?> \ No newline at end of file diff --git a/endpoints/cronjobs/sendnotifications.php b/endpoints/cronjobs/sendnotifications.php index 3471c8b8e..8b9153ff0 100644 --- a/endpoints/cronjobs/sendnotifications.php +++ b/endpoints/cronjobs/sendnotifications.php @@ -105,7 +105,7 @@ $pushover['token'] = $row["token"]; } - // Check if Nrfy notifications are enabled and get the settings + // Check if Ntfy notifications are enabled and get the settings $query = "SELECT * FROM ntfy_notifications WHERE user_id = :userId"; $stmt = $db->prepare($query); $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); diff --git a/endpoints/subscription/add.php b/endpoints/subscription/add.php index 222e80103..0715e5076 100644 --- a/endpoints/subscription/add.php +++ b/endpoints/subscription/add.php @@ -172,6 +172,7 @@ function resizeAndUploadLogo($uploadedFile, $uploadDir, $name, $settings) $notify = isset($_POST['notifications']) ? true : false; $notifyDaysBefore = $_POST['notify_days_before']; $inactive = isset($_POST['inactive']) ? true : false; + $cancellationDate = $_POST['cancellation_date'] ?? null; if ($logoUrl !== "") { $logo = getLogoFromUrl($logoUrl, '../../images/uploads/logos/', $name, $settings, $i18n); @@ -188,21 +189,21 @@ function resizeAndUploadLogo($uploadedFile, $uploadDir, $name, $settings) if (!$isEdit) { $sql = "INSERT INTO subscriptions (name, logo, price, currency_id, next_payment, cycle, frequency, notes, - payment_method_id, payer_user_id, category_id, notify, inactive, url, notify_days_before, user_id) + payment_method_id, payer_user_id, category_id, notify, inactive, url, notify_days_before, user_id, cancellation_date) VALUES (:name, :logo, :price, :currencyId, :nextPayment, :cycle, :frequency, :notes, - :paymentMethodId, :payerUserId, :categoryId, :notify, :inactive, :url, :notifyDaysBefore, :userId)"; + :paymentMethodId, :payerUserId, :categoryId, :notify, :inactive, :url, :notifyDaysBefore, :userId, :cancellationDate)"; } else { $id = $_POST['id']; if ($logo != "") { $sql = "UPDATE subscriptions SET name = :name, logo = :logo, price = :price, currency_id = :currencyId, next_payment = :nextPayment, cycle = :cycle, frequency = :frequency, notes = :notes, payment_method_id = :paymentMethodId, payer_user_id = :payerUserId, category_id = :categoryId, notify = :notify, inactive = :inactive, - url = :url, notify_days_before = :notifyDaysBefore WHERE id = :id AND user_id = :userId"; + url = :url, notify_days_before = :notifyDaysBefore, camcelation_date = :cancellationDate WHERE id = :id AND user_id = :userId"; } else { $sql = "UPDATE subscriptions SET name = :name, price = :price, currency_id = :currencyId, next_payment = :nextPayment, cycle = :cycle, frequency = :frequency, notes = :notes, payment_method_id = :paymentMethodId, payer_user_id = :payerUserId, - category_id = :categoryId, notify = :notify, inactive = :inactive, url = :url,notify_days_before = :notifyDaysBefore - WHERE id = :id AND user_id = :userId"; + category_id = :categoryId, notify = :notify, inactive = :inactive, url = :url, notify_days_before = :notifyDaysBefore, + cancellation_date = :cancellationDate WHERE id = :id AND user_id = :userId"; } } @@ -228,6 +229,7 @@ function resizeAndUploadLogo($uploadedFile, $uploadDir, $name, $settings) $stmt->bindParam(':url', $url, SQLITE3_TEXT); $stmt->bindParam(':notifyDaysBefore', $notifyDaysBefore, SQLITE3_INTEGER); $stmt->bindParam(':userId', $userId, SQLITE3_INTEGER); + $stmt->bindParam(':cancellationDate', $cancellationDate, SQLITE3_TEXT); if ($stmt->execute()) { $success['status'] = "Success"; diff --git a/endpoints/subscription/clone.php b/endpoints/subscription/clone.php index bb9682281..35282bb14 100644 --- a/endpoints/subscription/clone.php +++ b/endpoints/subscription/clone.php @@ -17,7 +17,7 @@ ])); } - $query = "INSERT INTO subscriptions (name, logo, price, currency_id, next_payment, cycle, frequency, notes, payment_method_id, payer_user_id, category_id, notify, url, inactive, notify_days_before, user_id) VALUES (:name, :logo, :price, :currency_id, :next_payment, :cycle, :frequency, :notes, :payment_method_id, :payer_user_id, :category_id, :notify, :url, :inactive, :notify_days_before, :user_id)"; + $query = "INSERT INTO subscriptions (name, logo, price, currency_id, next_payment, cycle, frequency, notes, payment_method_id, payer_user_id, category_id, notify, url, inactive, notify_days_before, user_id, cancellation_date) VALUES (:name, :logo, :price, :currency_id, :next_payment, :cycle, :frequency, :notes, :payment_method_id, :payer_user_id, :category_id, :notify, :url, :inactive, :notify_days_before, :user_id, :cancellation_date)"; $cloneStmt = $db->prepare($query); $cloneStmt->bindValue(':name', $subscriptionToClone['name'], SQLITE3_TEXT); $cloneStmt->bindValue(':logo', $subscriptionToClone['logo'], SQLITE3_TEXT); @@ -35,6 +35,7 @@ $cloneStmt->bindValue(':inactive', $subscriptionToClone['inactive'], SQLITE3_INTEGER); $cloneStmt->bindValue(':notify_days_before', $subscriptionToClone['notify_days_before'], SQLITE3_INTEGER); $cloneStmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $cloneStmt->bindValue(':cancellation_date', $subscriptionToClone['cancellation_date'], SQLITE3_TEXT); if ($cloneStmt->execute()) { $response = [ diff --git a/endpoints/subscription/get.php b/endpoints/subscription/get.php index f9e2134ba..1ab31d722 100644 --- a/endpoints/subscription/get.php +++ b/endpoints/subscription/get.php @@ -29,6 +29,7 @@ $subscriptionData['inactive'] = $row['inactive']; $subscriptionData['url'] = htmlspecialchars_decode($row['url'] ?? ""); $subscriptionData['notify_days_before'] = $row['notify_days_before']; + $subscriptionData['cancellation_date'] = $row['cancellation_date']; $subscriptionJson = json_encode($subscriptionData); header('Content-Type: application/json'); diff --git a/includes/i18n/de.php b/includes/i18n/de.php index 811f989a0..aeb1e0567 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -83,6 +83,7 @@ "notes" => "Notizen", "enable_notifications" => "Benachrichtigungen für dieses Abonnement aktivieren", "default_value_from_settings" => "Standardwert aus den Einstellungen", + "cancellation_notification" => "Benachrichtigung bei Kündigung", "delete" => "Löschen", "cancel" => "Abbrechen", "upload_logo" => "Logo hochladen", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index 969298490..8a123abaa 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -83,6 +83,7 @@ "notes" => "Σημειώσεις", "enable_notifications" => "Ενεργοποίηση ειδοποιήσεων για αυτή τη συνδρομή", "default_value_from_settings" => "Προεπιλεγμένη τιμή από τις ρυθμίσεις", + "cancellation_notification" => "Ειδοποίηση ακύρωσης", "delete" => "Διαγραφή", "cancel" => "Ακύρωση", "upload_logo" => "Φόρτωση λογότυπου", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index c38d4b671..8e53989a6 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -83,6 +83,7 @@ "notes" => "Notes", "enable_notifications" => "Enable Notifications for this subscription", "default_value_from_settings" => "Default value from settings", + "cancellation_notification" => "cancellation Notification", "delete" => "Delete", "cancel" => "Cancel", "upload_logo" => "Upload Logo", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index aba72ebfd..d0267c9bb 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -83,6 +83,7 @@ "notes" => "Notas", "enable_notifications" => "Habilitar notificaciones para esta suscripción", "default_value_from_settings" => "Valor predeterminado de la configuración", + "cancellation_notification" => "Notificación de cancelación", "delete" => "Eliminar", "cancel" => "Cancelar", "upload_logo" => "Cargar Logotipo", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index 4295ac06e..307e50bde 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -83,6 +83,7 @@ "notes" => "Notes", "enable_notifications" => "Activer les notifications pour cet abonnement", "default_value_from_settings" => "Valeur par défaut des paramètres", + "cancellation_notification" => "Notification d'annulation", "delete" => "Supprimer", "cancel" => "Annuler", "upload_logo" => "Télécharger le logo", diff --git a/includes/i18n/it.php b/includes/i18n/it.php index 362208c7b..5b76206a3 100644 --- a/includes/i18n/it.php +++ b/includes/i18n/it.php @@ -88,6 +88,7 @@ 'notes' => 'Note', 'enable_notifications' => 'Abilita notifiche per questo abbonamento', 'default_value_from_settings' => 'Valore predefinito dalle impostazioni', + "cancellation_notification" => "Notifica di cancellazione", 'delete' => 'Cancella', 'cancel' => 'Annulla', 'upload_logo' => 'Carica logo', diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index 5a313972a..138adad51 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -83,6 +83,7 @@ "notes" => "注釈", "enable_notifications" => "この定期購入の通知を有効にする", "default_value_from_settings" => "設定からデフォルト値を使用", + "cancellation_notification" => "キャンセル通知", "delete" => "削除", "cancel" => "キャンセル", "upload_logo" => "ロゴのアップロード", diff --git a/includes/i18n/ko.php b/includes/i18n/ko.php index ee3365ff5..cbed40456 100644 --- a/includes/i18n/ko.php +++ b/includes/i18n/ko.php @@ -83,6 +83,7 @@ "notes" => "메모", "enable_notifications" => "이 구독에 대한 알림을 활성화합니다.", "default_value_from_settings" => "설정에서 기본값 사용", + "cancellation_notification" => "구독 취소 알림", "delete" => "삭제", "cancel" => "취소", "upload_logo" => "로고 업로드", diff --git a/includes/i18n/pl.php b/includes/i18n/pl.php index 5873c2d5d..d34c5614a 100644 --- a/includes/i18n/pl.php +++ b/includes/i18n/pl.php @@ -83,6 +83,7 @@ "notes" => "Notatki", "enable_notifications" => "Włącz powiadomienia dla tej subskrypcji", "default_value_from_settings" => "Wartość domyślna z ustawień", + "cancellation_notification" => "Powiadomienie o anulowaniu", "delete" => "Usuń", "cancel" => "Anuluj", "upload_logo" => "Prześlij logo", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index f1e265490..09b1fd022 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -83,6 +83,7 @@ "notes" => "Notas", "enable_notifications" => "Activar notificações para esta subscrição", "default_value_from_settings" => "Valor por defeito das definições", + "cancellation_notification" => "Notificação de cancelamento", "delete" => "Remover", "cancel" => "Cancelar", "upload_logo" => "Enviar Logo", diff --git a/includes/i18n/pt_br.php b/includes/i18n/pt_br.php index fe57578c8..e1a3f5d92 100644 --- a/includes/i18n/pt_br.php +++ b/includes/i18n/pt_br.php @@ -83,6 +83,7 @@ "notes" => "Anotações", "enable_notifications" => "Ativar notificações para essa assinatura", "default_value_from_settings" => "Valor padrão das configurações", + "cancellation_notification" => "Notificação de cancelamento", "delete" => "Excluir", "cancel" => "Cancelar", "upload_logo" => "Enviar Logo", diff --git a/includes/i18n/ru.php b/includes/i18n/ru.php index 02bbdec24..d84bff6d0 100644 --- a/includes/i18n/ru.php +++ b/includes/i18n/ru.php @@ -83,6 +83,7 @@ "notes" => "Примечания", "enable_notifications" => "Включить уведомления для этой подписки", "default_value_from_settings" => "Значение по умолчанию из настроек", + "cancellation_notification" => "Уведомление об отмене", "delete" => "Удалить", "cancel" => "Отмена", "upload_logo" => "Загрузить логотип", diff --git a/includes/i18n/sl.php b/includes/i18n/sl.php index 322bc7474..e11e2888d 100644 --- a/includes/i18n/sl.php +++ b/includes/i18n/sl.php @@ -83,6 +83,7 @@ "notes" => "Opombe", "enable_notifications" => "Omogoči obvestila za to naročnino", "default_value_from_settings" => "Privzeta vrednost iz nastavitev", + "cancellation_notification" => "Obvestilo o preklicu", "delete" => "Izbriši", "cancel" => "Prekliči", "upload_logo" => "Naloži logotip", diff --git a/includes/i18n/sr.php b/includes/i18n/sr.php index 4149c8501..0c0a0626a 100644 --- a/includes/i18n/sr.php +++ b/includes/i18n/sr.php @@ -82,6 +82,8 @@ "url" => "URL", "notes" => "Напомене", "enable_notifications" => "Омогући обавештења за ову претплату", + "default_value_from_settings" => "Подразумевана вредност из подешавања", + "cancellation_notification" => "Обавештење о отказивању", "delete" => "Обриши", "cancel" => "Откажи", "upload_logo" => "Постави логотип", diff --git a/includes/i18n/sr_lat.php b/includes/i18n/sr_lat.php index 79456b0bc..78b2ab0da 100644 --- a/includes/i18n/sr_lat.php +++ b/includes/i18n/sr_lat.php @@ -83,6 +83,7 @@ "notes" => "Beleške", "enable_notifications" => "Omogući obaveštenja za ovu pretplatu", "default_value_from_settings" => "Podrazumevana vrednost iz podešavanja", + "cancellation_notification" => "Obaveštenje o otkazivanju", "delete" => "Izbriši", "cancel" => "Otkaži", "upload_logo" => "Učitaj logo", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index 91243a86c..433ec439f 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -83,6 +83,7 @@ "notes" => "Notlar", "enable_notifications" => "Bu abonelik için bildirimleri etkinleştir", "default_value_from_settings" => "Ayarlar'dan varsayılan değeri al", + "cancellation_notification" => "İptal Bildirimi", "delete" => "Sil", "cancel" => "İptal", "upload_logo" => "Logo Yükle", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index 1111a8899..86c4a0df6 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -88,6 +88,7 @@ "notes" => "备注", "enable_notifications" => "为此订阅启用通知", "default_value_from_settings" => "默认值从设置中获取", + "cancellation_notification" => "取消通知", "delete" => "删除", "cancel" => "取消", "upload_logo" => "上传 Logo", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index 2ad410e71..7a7a63cc8 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -83,6 +83,7 @@ "notes" => "備註", "enable_notifications" => "為該訂閱開啟通知", "default_value_from_settings" => "從設定中取得預設值", + "cancellation_notification" => "取消通知", "delete" => "刪除", "cancel" => "取消", "upload_logo" => "上傳圖示", diff --git a/includes/version.php b/includes/version.php index d35e05d96..e360ce324 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/index.php b/index.php index 6b9770d29..36004c81d 100644 --- a/index.php +++ b/index.php @@ -93,7 +93,8 @@ } ?>
-
+ + @@ -116,7 +117,8 @@ } ?>
-
+ + @@ -141,11 +143,14 @@
-
- - -
- -
- - -
-
- - +
+
+ + +
+
+ + +
+
@@ -347,18 +349,32 @@
+
+ + +
- - +
+
+ + +
+
+ + +
+
diff --git a/migrations/000024.php b/migrations/000024.php new file mode 100644 index 000000000..72bd31408 --- /dev/null +++ b/migrations/000024.php @@ -0,0 +1,10 @@ +query("SELECT * FROM pragma_table_info('subscriptions') where name='cancellation_date'"); +$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; + +if ($columnRequired) { + $db->exec('ALTER TABLE subscriptions ADD COLUMN cancellation_date DATE;'); +} \ No newline at end of file diff --git a/screenshots/wallos-calendar.png b/screenshots/wallos-calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..8b837abc4d5ad5f26d2e85cf259a99342df3297b GIT binary patch literal 42043 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