From e73962260678caf0843b6302f7fbb7d49469a1a9 Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Fri, 1 Mar 2024 21:41:39 +0100 Subject: [PATCH] feat: added custom payment methods (#173) --- endpoints/payments/add.php | 183 ++++++++++++++++++++++++++++++++ endpoints/payments/delete.php | 29 +++++ endpoints/payments/get.php | 59 ++++++++++ endpoints/payments/search.php | 81 ++++++++++++++ endpoints/subscriptions/get.php | 3 +- includes/i18n/de.php | 3 + includes/i18n/el.php | 3 + includes/i18n/en.php | 3 + includes/i18n/es.php | 3 + includes/i18n/fr.php | 3 + includes/i18n/jp.php | 3 + includes/i18n/pt.php | 3 + includes/i18n/tr.php | 3 + includes/i18n/zh_cn.php | 3 + includes/i18n/zh_tw.php | 3 + includes/version.php | 2 +- index.php | 3 +- scripts/settings.js | 177 +++++++++++++++++++++++++++++- settings.php | 47 +++++++- styles/dark-theme.css | 6 +- styles/styles.css | 86 +++++++++++++-- 21 files changed, 691 insertions(+), 15 deletions(-) create mode 100644 endpoints/payments/add.php create mode 100644 endpoints/payments/delete.php create mode 100644 endpoints/payments/get.php create mode 100644 endpoints/payments/search.php diff --git a/endpoints/payments/add.php b/endpoints/payments/add.php new file mode 100644 index 000000000..b9357ff31 --- /dev/null +++ b/endpoints/payments/add.php @@ -0,0 +1,183 @@ +transparentPaintImage("rgb(247, 247, 247)", 0, $fuzz, false); + } + $imagick->setImageFormat('png'); + $imagick->writeImage($uploadFile); + + $imagick->clear(); + $imagick->destroy(); + unlink($tempFile); + + return true; + } else { + return false; + } + } + + function resizeAndUploadLogo($uploadedFile, $uploadDir, $name) { $targetWidth = 70; + $targetHeight = 48; + + $timestamp = time(); + $originalFileName = $uploadedFile['name']; + $fileExtension = pathinfo($originalFileName, PATHINFO_EXTENSION); + $fileName = $timestamp . '-payments-' . sanitizeFilename($name) . '.' . $fileExtension; + $uploadFile = $uploadDir . $fileName; + + if (move_uploaded_file($uploadedFile['tmp_name'], $uploadFile)) { + $fileInfo = getimagesize($uploadFile); + + if ($fileInfo !== false) { + $width = $fileInfo[0]; + $height = $fileInfo[1]; + + // Load the image based on its format + if ($fileExtension === 'png') { + $image = imagecreatefrompng($uploadFile); + } elseif ($fileExtension === 'jpg' || $fileExtension === 'jpeg') { + $image = imagecreatefromjpeg($uploadFile); + } else { + // Handle other image formats as needed + return ""; + } + + // Enable alpha channel (transparency) for PNG images + if ($fileExtension === 'png') { + imagesavealpha($image, true); + } + + $newWidth = $width; + $newHeight = $height; + + if ($width > $targetWidth) { + $newWidth = $targetWidth; + $newHeight = ($targetWidth / $width) * $height; + } + + if ($newHeight > $targetHeight) { + $newWidth = ($targetHeight / $newHeight) * $newWidth; + $newHeight = $targetHeight; + } + + $resizedImage = imagecreatetruecolor($newWidth, $newHeight); + imagesavealpha($resizedImage, true); + $transparency = imagecolorallocatealpha($resizedImage, 0, 0, 0, 127); + imagefill($resizedImage, 0, 0, $transparency); + imagecopyresampled($resizedImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); + + if ($fileExtension === 'png') { + imagepng($resizedImage, $uploadFile); + } elseif ($fileExtension === 'jpg' || $fileExtension === 'jpeg') { + imagejpeg($resizedImage, $uploadFile); + } else { + return ""; + } + + imagedestroy($image); + imagedestroy($resizedImage); + return $fileName; + } + } + + return ""; + } + + if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === true) { + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $enabled = 1; + $name = validate($_POST["paymentname"]); + $iconUrl = validate($_POST['icon-url']); + + if ($name === "" || ($iconUrl === "" && empty($_FILES['paymenticon']['name']))) { + $response = [ + "success" => false, + "errorMessage" => translate('fill_all_fields', $i18n) + ]; + echo json_encode($response); + exit(); + } + + + $icon = ""; + + if($iconUrl !== "") { + $icon = getLogoFromUrl($iconUrl, '../../images/uploads/logos/', $name); + } else { + if (!empty($_FILES['paymenticon']['name'])) { + $icon = resizeAndUploadLogo($_FILES['paymenticon'], '../../images/uploads/logos/', $name); + } + } + + $sql = "INSERT INTO payment_methods (name, icon, enabled) VALUES (:name, :icon, :enabled)"; + + $stmt = $db->prepare($sql); + + $stmt->bindParam(':name', $name, SQLITE3_TEXT); + $stmt->bindParam(':icon', $icon, SQLITE3_TEXT); + $stmt->bindParam(':enabled', $enabled, SQLITE3_INTEGER); + + if ($stmt->execute()) { + $success['success'] = true; + $success['message'] = translate('payment_method_added_successfuly', $i18n); + $json = json_encode($success); + header('Content-Type: application/json'); + echo $json; + exit(); + } else { + echo translate('error', $i18n) . ": " . $db->lastErrorMsg(); + } + } + } + $db->close(); + +?> \ No newline at end of file diff --git a/endpoints/payments/delete.php b/endpoints/payments/delete.php new file mode 100644 index 000000000..36f6602c9 --- /dev/null +++ b/endpoints/payments/delete.php @@ -0,0 +1,29 @@ +prepare($deleteQuery); + $deleteStmt->bindParam(':paymentMethodId', $paymentMethodId, SQLITE3_INTEGER); + + if ($deleteStmt->execute()) { + $success['success'] = true; + $success['message'] = translate('payment_method_added_successfuly', $i18n); + $json = json_encode($success); + header('Content-Type: application/json'); + echo $json; + } else { + http_response_code(500); + echo json_encode(array("message" => translate('error', $i18n))); + } + } else { + http_response_code(405); + echo json_encode(array("message" => translate('invalid_request_method', $i18n))); + } +} +$db->close(); + +?> \ No newline at end of file diff --git a/endpoints/payments/get.php b/endpoints/payments/get.php new file mode 100644 index 000000000..fdd5f4e06 --- /dev/null +++ b/endpoints/payments/get.php @@ -0,0 +1,59 @@ +query('SELECT id FROM payment_methods WHERE id IN (SELECT DISTINCT payment_method_id FROM subscriptions)'); + $paymentsInUse = []; + while ($row = $paymentsInUseQuery->fetchArray(SQLITE3_ASSOC)) { + $paymentsInUse[] = $row['id']; + } + + $sql = "SELECT * FROM payment_methods"; + + $result = $db->query($sql); + if ($result) { + $payments = array(); + while ($row = $result->fetchArray(SQLITE3_ASSOC)) { + $payments[] = $row; + } + } else { + http_response_code(500); + echo json_encode(array("message" => translate('error', $i18n))); + exit(); + } + + foreach ($payments as $payment) { + $paymentIconFolder = $payment['id'] <= 31 ? 'images/uploads/icons/' : 'images/uploads/logos/'; + $inUse = in_array($payment['id'], $paymentsInUse); + ?> +
+ Logo + + + + 31 && !$inUse) { + ?> +
+ x +
+ +
+ translate('error', $i18n))); + exit(); +} + +?> \ No newline at end of file diff --git a/endpoints/payments/search.php b/endpoints/payments/search.php new file mode 100644 index 000000000..602adea82 --- /dev/null +++ b/endpoints/payments/search.php @@ -0,0 +1,81 @@ + 'Failed to fetch data from Google.']); + } else { + $imageUrls = extractImageUrlsFromPage($response); + header('Content-Type: application/json'); + echo json_encode(['imageUrls' => $imageUrls]); + } + } else { + // Parse the HTML response to extract image URLs + $imageUrls = extractImageUrlsFromPage($response); + + // Pass the image URLs to the client + header('Content-Type: application/json'); + echo json_encode(['imageUrls' => $imageUrls]); + } + + curl_close($ch); + } else { + echo json_encode(['error' => 'Invalid request.']); + } + + function extractImageUrlsFromPage($html) { + $imageUrls = []; + + $doc = new DOMDocument(); + @$doc->loadHTML($html); + + $imgTags = $doc->getElementsByTagName('img'); + foreach ($imgTags as $imgTag) { + $src = $imgTag->getAttribute('src'); + if (!strstr($imgTag->getAttribute('class'), "favicon") && !strstr($imgTag->getAttribute('class'), "logo")) { + if (filter_var($src, FILTER_VALIDATE_URL)) { + $imageUrls[] = $src; + } + } + } + + return $imageUrls; + } + +?> \ No newline at end of file diff --git a/endpoints/subscriptions/get.php b/endpoints/subscriptions/get.php index 1f57a5bf1..9208b691e 100644 --- a/endpoints/subscriptions/get.php +++ b/endpoints/subscriptions/get.php @@ -50,7 +50,8 @@ $print[$id]['currency_code'] = $currencies[$subscription['currency_id']]['code']; $currencyId = $subscription['currency_id']; $print[$id]['next_payment'] = date('M d, Y', strtotime($subscription['next_payment'])); - $print[$id]['payment_method_icon'] = "images/uploads/icons/" . $payment_methods[$paymentMethodId]['icon']; + $paymentIconFolder = $paymentMethodId <= 31 ? 'images/uploads/icons/' : 'images/uploads/logos/'; + $print[$id]['payment_method_icon'] = $paymentIconFolder . $payment_methods[$paymentMethodId]['icon']; $print[$id]['payment_method_name'] = $payment_methods[$paymentMethodId]['name']; $print[$id]['payment_method_id'] = $paymentMethodId; $print[$id]['category_id'] = $subscription['category_id']; diff --git a/includes/i18n/de.php b/includes/i18n/de.php index 52e5dd9ec..632ed1a45 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -137,6 +137,9 @@ "payment_methods" => "Zahlungsmethoden", "payment_methods_info" => "Zahlungsmethode zum (de-)aktivieren anklicken.", "cant_delete_payment_method_in_use" => "Genutzte Zahlungsmethoden können nicht deaktiviert werden", + "add_custom_payment" => "Eigene Zahlungsmethode hinzufügen", + "payment_method_name" => "Name der Zahlungsmethode", + "payment_method_added_successfuly" => "Zahlungsmethode erfolgreich hinzugefügt", "disable" => "Deaktivieren", "enable" => "Aktivieren", "test" => "Test", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index f47d4b062..f73753fdd 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -137,6 +137,9 @@ "payment_methods" => "Τρόποι πληρωμής", "payment_methods_info" => "Κάνε κλικ σε μια μέθοδο πληρωμής για να την απενεργοποιήσεις/ενεργοποιήσεις.", "cant_delete_payment_method_in_use" => "Δεν είναι εφικτό να απενεργοποιηθεί η χρησιμοποιούμενη μέθοδο πληρωμής", + "add_custom_payment" => "Προσθήκη προσαρμοσμένης μεθόδου πληρωμής", + "payment_method_name" => "Όνομα μεθόδου πληρωμής", + "payment_method_added_successfuly" => "Η μέθοδος πληρωμής προστέθηκε με επιτυχία", "disable" => "Ανενεργό", "enable" => "Ενεργό", "test" => "Δοκιμή", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index f80783966..b6d06924f 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -137,6 +137,9 @@ "payment_methods" => "Payment Methods", "payment_methods_info" => "Click a payment method to disable / enable it.", "cant_delete_payment_method_in_use" => "Can't disable used payment method", + "add_custom_payment" => "Add Custom Payment Method", + "payment_method_name" => "Payment Method Name", + "payment_method_added_successfuly" => "Payment method added successfully", "disable" => "Disable", "enable" => "Enable", "test" => "Test", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index 83c6f8d29..c0f60f843 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -137,6 +137,9 @@ "payment_methods" => "Métodos de Pago", "payment_methods_info" => "Haz clic en un método de pago para deshabilitarlo/habilitarlo.", "cant_delete_payment_method_in_use" => "No se puede desactivar el método de pago utilizado", + "add_custom_payment" => "Añadir método de pago personalizado", + "payment_method_name" => "Nombre del método de pago", + "payment_method_added_successfuly" => "Método de pago añadido con éxito", "disable" => "Desactivar", "enable" => "Activar", "test" => "Probar", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index caa7fc481..ba844e315 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -137,6 +137,9 @@ "payment_methods" => "Méthodes de paiement", "payment_methods_info" => "Cliquez sur une méthode de paiement pour la désactiver / l'activer.", "cant_delete_payment_method_in_use" => "Impossible de désactiver la méthode de paiement utilisée", + "add_custom_payment" => "Ajouter un paiement personnalisé", + "payment_method_name" => "Nom de la méthode de paiement", + "payment_method_added_successfuly" => "Méthode de paiement ajoutée avec succès", "disable" => "Désactiver", "enable" => "Activer", "test" => "Test", diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index d365fdde2..d0d0b4aaa 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -137,6 +137,9 @@ "payment_methods" => "支払い方法", "payment_methods_info" => "支払い方法をクリックして無効/有効を切り替えます。", "cant_delete_payment_method_in_use" => "支払い方法が使用中のため無効にできません。", + "add_custom_payment" => "カスタム支払い方法を追加", + "payment_method_name" => "支払い方法名", + "payment_method_added_successfuly" => "支払い方法が追加されました", "disable" => "無効", "enable" => "有効", "test" => "テスト", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index 3058b0cd1..3f8249030 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -137,6 +137,9 @@ "payment_methods" => "Métodos de Pagamento", "payment_methods_info" => "Clique num método de pagamento para o activar / desactivar.", "cant_delete_payment_method_in_use" => "Não pode desactivar metodo de pagamento em uso", + "add_custom_payment" => "Adicionar método de pagamento personalizado", + "payment_method_name" => "Nome do método de pagamento", + "payment_method_added_successfuly" => "Método de pagamento adicionado com sucesso", "disable" => "Desactivar", "enable" => "Activar", "test" => "Testar", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index de4c519d2..15a7c9019 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -137,6 +137,9 @@ "payment_methods" => "Ödeme Yöntemleri", "payment_methods_info" => "Bir ödeme yöntemini devre dışı bırakmak / etkinleştirmek için tıklayın.", "cant_delete_payment_method_in_use" => "Kullanımda olan ödeme yöntemini devre dışı bırakamazsınız", + "add_custom_payment" => "Özel ödeme yöntemi ekle", + "payment_method_name" => "Ödeme Yöntemi Adı", + "payment_method_added_successfuly" => "Ödeme yöntemi başarıyla eklendi", "disable" => "Devre Dışı Bırak", "enable" => "Etkinleştir", "test" => "Test Et", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index 780743c51..859d7cbc0 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -144,6 +144,9 @@ "payment_methods" => "支付方式", "payment_methods_info" => "点击支付方式以禁用/启用。", "cant_delete_payment_method_in_use" => "不能禁用正在使用的支付方式", + "add_custom_payment" => "添加自定义支付方式", + "payment_method_name" => "支付方式名称", + "payment_method_added_successfuly" => "支付方式已成功添加", "disable" => "禁用", "enable" => "启用", "test" => "测试", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index 9bc7de6d2..5cb233208 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -137,6 +137,9 @@ "payment_methods" => "付款方式", "payment_methods_info" => "點選付款方式以停用/啟用。", "cant_delete_payment_method_in_use" => "無法停用正在使用的付款方式", + "add_custom_payment" => "新增自訂付款方式", + "payment_method_name" => "付款方式名稱", + "payment_method_added_successfuly" => "付款方式已成功新增", "disable" => "停用", "enable" => "啟用", "test" => "測試", diff --git a/includes/version.php b/includes/version.php index 9678a216c..0385f0593 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 a1420f8c7..3f6a587fc 100644 --- a/index.php +++ b/index.php @@ -81,7 +81,8 @@ $print[$id]['currency_code'] = $currencies[$subscription['currency_id']]['code']; $currencyId = $subscription['currency_id']; $print[$id]['next_payment'] = date('M d, Y', strtotime($subscription['next_payment'])); - $print[$id]['payment_method_icon'] = "images/uploads/icons/" . $payment_methods[$paymentMethodId]['icon']; + $paymentIconFolder = $paymentMethodId <= 31 ? 'images/uploads/icons/' : 'images/uploads/logos/'; + $print[$id]['payment_method_icon'] = $paymentIconFolder . $payment_methods[$paymentMethodId]['icon']; $print[$id]['payment_method_name'] = $payment_methods[$paymentMethodId]['name']; $print[$id]['payment_method_id'] = $paymentMethodId; $print[$id]['category_id'] = $subscription['category_id']; diff --git a/scripts/settings.js b/scripts/settings.js index d33cd46d3..57ae7b218 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -449,6 +449,171 @@ function togglePayment(paymentId) { }); } +function handleFileSelect(event) { + const fileInput = event.target; + const iconPreview = document.querySelector('.icon-preview'); + const iconImg = iconPreview.querySelector('img'); + const iconUrl = document.querySelector("#icon-url"); + iconUrl.value = ""; + + if (fileInput.files && fileInput.files[0]) { + const reader = new FileReader(); + + reader.onload = function (e) { + iconImg.src = e.target.result; + iconImg.style.display = 'block'; + }; + + reader.readAsDataURL(fileInput.files[0]); + } +} + +function setSearchButtonStatus() { + + const nameInput = document.querySelector("#paymentname"); + const hasSearchTerm = nameInput.value.trim().length > 0; + const iconSearchButton = document.querySelector("#icon-search-button"); + if (hasSearchTerm) { + iconSearchButton.classList.remove("disabled"); + } else { + iconSearchButton.classList.add("disabled"); + } + +} + +function searchPaymentIcon() { + const nameInput = document.querySelector("#paymentname"); + const searchTerm = nameInput.value.trim(); + if (searchTerm !== "") { + const iconSearchPopup = document.querySelector("#icon-search-results"); + iconSearchPopup.classList.add("is-open"); + const imageSearchUrl = `endpoints/payments/search.php?search=${searchTerm}`; + fetch(imageSearchUrl) + .then(response => response.json()) + .then(data => { + if (data.imageUrls) { + displayImageResults(data.imageUrls); + } else if (data.error) { + console.error(data.error); + } + }) + .catch(error => { + console.error(translate('error_fetching_image_results'), error); + }); + } else { + nameInput.focus(); + } +} + +function displayImageResults(imageSources) { + const iconResults = document.querySelector("#icon-search-images"); + iconResults.innerHTML = ""; + + imageSources.forEach(src => { + const img = document.createElement("img"); + img.src = src; + img.onclick = function() { + selectWebIcon(src); + }; + img.onerror = function() { + this.parentNode.removeChild(this); + }; + iconResults.appendChild(img); + }); +} + +function selectWebIcon(url) { + closeIconSearch(); + const iconPreview = document.querySelector("#form-icon"); + const iconUrl = document.querySelector("#icon-url"); + iconPreview.src = url; + iconPreview.style.display = 'block'; + iconUrl.value = url; +} + +function closeIconSearch() { + const iconSearchPopup = document.querySelector("#icon-search-results"); + iconSearchPopup.classList.remove("is-open"); + const iconResults = document.querySelector("#icon-search-images"); + iconResults.innerHTML = ""; +} + +function resetFormIcon() { + const iconPreview = document.querySelector("#form-icon"); + iconPreview.src = ""; + iconPreview.style.display = 'none'; +} + +function reloadPaymentMethods() { + const paymentsContainer = document.querySelector("#payments-list"); + const paymentMethodsEndpoint = "endpoints/payments/get.php"; + + fetch(paymentMethodsEndpoint) + .then(response => response.text()) + .then(data => { + paymentsContainer.innerHTML = data; + }); +} + +function addPaymentMethod() { + closeIconSearch(); + const addPaymentMethodEndpoint = "endpoints/payments/add.php"; + const paymentMethodForm = document.querySelector("#payments-form"); + const submitButton = document.querySelector("#add-payment-button"); + + submitButton.disabled = true; + const formData = new FormData(paymentMethodForm); + + fetch(addPaymentMethodEndpoint, { + method: "POST", + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + paymentMethodForm.reset(); + resetFormIcon(); + reloadPaymentMethods(); + } else { + showErrorMessage(data.errorMessage); + } + submitButton.disabled = false; + }) + .catch(error => { + showErrorMessage(translate('unknown_error')); + submitButton.disabled = false; + }); + +} + +function deletePaymentMethod(paymentId) { + fetch(`endpoints/payments/delete.php?id=${paymentId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id: paymentId }), + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + var paymentToRemove = document.querySelector('.payments-payment[data-paymentid="' + paymentId + '"]'); + if (paymentToRemove) { + paymentToRemove.remove(); + } + } else { + showErrorMessage(data.errorMessage); + } + }) + .catch((error) => { + console.error('Error:', error); + }); +} + + + document.addEventListener('DOMContentLoaded', function() { document.getElementById("userForm").addEventListener("submit", function(event) { @@ -478,7 +643,17 @@ document.addEventListener('DOMContentLoaded', function() { .catch(error => { showErrorMessage(translate('unknown_error')); }); - }); + }); + + var removePaymentButtons = document.querySelectorAll(".delete-payment-method"); + removePaymentButtons.forEach(function(button) { + button.addEventListener('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + let paymentId = event.target.getAttribute('data-paymentid'); + deletePaymentMethod(paymentId); + }); + }); }); diff --git a/settings.php b/settings.php index 4d03bb438..390741f0d 100644 --- a/settings.php +++ b/settings.php @@ -2,6 +2,11 @@ require_once 'includes/header.php'; ?> +