Compare commits

...

10 Commits

Author SHA1 Message Date
github-actions[bot]
5fff7c59eb
chore(main): release 1.23.0 (#289)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-26 17:42:01 +02:00
Miguel Ribeiro
7b509d2b3d
feat: backup and restore (#288) 2024-04-26 17:41:19 +02:00
github-actions[bot]
65cc376dff
chore(main): release 1.22.0 (#287)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-20 15:00:13 +02:00
Miguel Ribeiro
b80ab4bdc6
feat: option to hide disabled subscriptions (#286) 2024-04-20 14:58:46 +02:00
github-actions[bot]
27f84ff3d5
chore(main): release 1.21.1 (#284) 2024-04-19 17:54:14 +02:00
Miguel Ribeiro
769f8a0587
fix: small layout issues 2024-04-19 17:51:15 +02:00
github-actions[bot]
80678acfa4
chore(main): release 1.21.0 (#282)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-19 14:24:58 +02:00
Miguel Ribeiro
70e42349ca
feat: add themes and custom color options
feat: add italian translation
2024-04-19 14:22:07 +02:00
Luca
2c186b4818
Italian translation (#280) 2024-04-19 10:27:59 +02:00
github-actions[bot]
16bc737d67
chore(main): release 1.20.2 (#274)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-11 22:12:39 +02:00
104 changed files with 1837 additions and 198 deletions

2
.tmp/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -1,5 +1,41 @@
# Changelog
## [1.23.0](https://github.com/ellite/Wallos/compare/v1.22.0...v1.23.0) (2024-04-26)
### Features
* backup and restore ([#288](https://github.com/ellite/Wallos/issues/288)) ([7b509d2](https://github.com/ellite/Wallos/commit/7b509d2b3d769e14a9cb4fd183395dcecc9d993b))
## [1.22.0](https://github.com/ellite/Wallos/compare/v1.21.1...v1.22.0) (2024-04-20)
### Features
* option to hide disabled subscriptions ([#286](https://github.com/ellite/Wallos/issues/286)) ([b80ab4b](https://github.com/ellite/Wallos/commit/b80ab4bdc662c3e80a2fd42b8b286b69beac441c))
## [1.21.1](https://github.com/ellite/Wallos/compare/v1.21.0...v1.21.1) (2024-04-19)
### Bug Fixes
* small layout issues ([769f8a0](https://github.com/ellite/Wallos/commit/769f8a0587941bffd0d7463b7e7ffeb38a70e301))
## [1.21.0](https://github.com/ellite/Wallos/compare/v1.20.2...v1.21.0) (2024-04-19)
### Features
* add italian translation ([70e4234](https://github.com/ellite/Wallos/commit/70e42349caee5d6647b6b704643fe2b5e26dff4e))
* add themes and custom color options ([70e4234](https://github.com/ellite/Wallos/commit/70e42349caee5d6647b6b704643fe2b5e26dff4e))
## [1.20.2](https://github.com/ellite/Wallos/compare/v1.20.1...v1.20.2) (2024-04-11)
### Bug Fixes
* encoding for url and notes ([#273](https://github.com/ellite/Wallos/issues/273)) ([ad86eb5](https://github.com/ellite/Wallos/commit/ad86eb5b9c6e60004de2795170032d62b33ddcfb))
## [1.20.1](https://github.com/ellite/Wallos/compare/v1.20.0...v1.20.1) (2024-04-09)

View File

@ -6,11 +6,11 @@ WORKDIR /var/www/html
# Update packages and install dependencies
RUN apk upgrade --no-cache && \
apk add --no-cache sqlite-dev libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev freetype freetype-dev curl autoconf libgomp icu-dev nginx dcron tzdata imagemagick imagemagick-dev && \
apk add --no-cache sqlite-dev libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev freetype freetype-dev curl autoconf libgomp icu-dev nginx dcron tzdata imagemagick imagemagick-dev libzip-dev && \
docker-php-ext-install pdo pdo_sqlite && \
docker-php-ext-enable pdo pdo_sqlite && \
docker-php-ext-configure gd --with-freetype --with-jpeg && \
docker-php-ext-install -j$(nproc) gd intl && \
docker-php-ext-install -j$(nproc) gd intl zip && \
apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \
pecl install imagick && \
docker-php-ext-enable imagick && \

View File

@ -1,7 +1,7 @@
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./images/wallossolidwhite.png">
<source media="(prefers-color-scheme: light)" srcset="./images/wallossolid.png">
<img alt="Wallos" src="./images/wallossolid.png">
<source media="(prefers-color-scheme: dark)" srcset="./images/siteicons/blue/walloswhite.png">
<source media="(prefers-color-scheme: light)" srcset="./images/siteicons/blue/wallos.png">
<img alt="Wallos" src="./images/siteicons/blue/wallos.png">
</picture>
Wallos: Open-Source Personal Subscription Tracker
@ -61,6 +61,7 @@ See instructions to run Wallos below.
- intl
- openssl
- sqlite3
- zip
#### Docker

71
endpoints/db/backup.php Normal file
View File

@ -0,0 +1,71 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
function addFolderToZip($dir, $zipArchive, $zipdir = ''){
if (is_dir($dir)) {
if ($dh = opendir($dir)) {
//Add the directory
if(!empty($zipdir)) $zipArchive->addEmptyDir($zipdir);
while (($file = readdir($dh)) !== false) {
// Skip '.' and '..'
if ($file == "." || $file == "..") {
continue;
}
//If it's a folder, run the function again!
if(is_dir($dir . $file)){
$newdir = $dir . $file . '/';
addFolderToZip($newdir, $zipArchive, $zipdir . $file . '/');
}else{
//Add the files
$zipArchive->addFile($dir . $file, $zipdir . $file);
}
}
}
} else {
die(json_encode([
"success" => false,
"message" => "Directory does not exist: $dir"
]));
}
}
$zip = new ZipArchive();
$filename = "backup_" . uniqid() . ".zip";
$zipname = "../../.tmp/" . $filename;
if ($zip->open($zipname, ZipArchive::CREATE)!==TRUE) {
die(json_encode([
"success" => false,
"message" => translate('cannot_open_zip', $i18n)
]));
}
addFolderToZip('../../db/', $zip);
addFolderToZip('../../images/uploads/', $zip);
$numberOfFilesAdded = $zip->numFiles;
if ($zip->close() === false) {
die(json_encode([
"success" => false,
"message" => "Failed to finalize the zip file"
]));
} else {
flush();
die(json_encode([
"success" => true,
"message" => "Zip file created successfully",
"numFiles" => $numberOfFilesAdded,
"file" => $filename
]));
}
?>

112
endpoints/db/import.php Normal file
View File

@ -0,0 +1,112 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
$result = $db->query("SELECT COUNT(*) as count FROM user");
$row = $result->fetchArray(SQLITE3_NUM);
if ($row[0] > 0) {
die(json_encode([
"success" => false,
"message" => "Denied"
]));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$fileTmpName = $file['tmp_name'];
$fileError = $file['error'];
if ($fileError === 0) {
$fileDestination = '../../.tmp/restore.zip';
move_uploaded_file($fileTmpName, $fileDestination);
$zip = new ZipArchive();
if ($zip->open($fileDestination) === true) {
$zip->extractTo('../../.tmp/restore/');
$zip->close();
} else {
die(json_encode([
"success" => false,
"message" => "Failed to extract the uploaded file"
]));
}
if (file_exists('../../.tmp/restore/wallos.db')) {
if (file_exists('../../db/wallos.db')) {
unlink('../../db/wallos.db');
}
rename('../../.tmp/restore/wallos.db', '../../db/wallos.db');
if (file_exists('../../.tmp/restore/logos/')) {
$dir = '../../images/uploads/logos/';
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ( $ri as $file ) {
if ( $file->isDir() ) {
rmdir($file->getPathname());
} else {
unlink($file->getPathname());
}
}
$dir = new RecursiveDirectoryIterator('../../.tmp/restore/logos/');
$ite = new RecursiveIteratorIterator($dir);
$allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'];
foreach ($ite as $filePath) {
if (in_array(pathinfo($filePath, PATHINFO_EXTENSION), $allowedExtensions)) {
$destination = str_replace('../../.tmp/restore/', '../../images/uploads/', $filePath);
$destinationDir = pathinfo($destination, PATHINFO_DIRNAME);
if (!is_dir($destinationDir)) {
mkdir($destinationDir, 0755, true);
}
copy($filePath, $destination);
}
}
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('../../.tmp', RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
$removeFunction = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
$removeFunction($fileinfo->getRealPath());
}
echo json_encode([
"success" => true,
"message" => translate("success", $i18n)
]);
} else {
die(json_encode([
"success" => false,
"message" => "wallos.db does not exist in the backup file"
]));
}
} else {
echo json_encode([
"success" => false,
"message" => "Failed to upload file"
]);
}
} else {
echo json_encode([
"success" => false,
"message" => "No file uploaded"
]);
}
} else {
echo json_encode([
"success" => false,
"message" => "Invalid request method"
]);
}
?>

109
endpoints/db/restore.php Normal file
View File

@ -0,0 +1,109 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$fileTmpName = $file['tmp_name'];
$fileError = $file['error'];
if ($fileError === 0) {
$fileDestination = '../../.tmp/restore.zip';
move_uploaded_file($fileTmpName, $fileDestination);
$zip = new ZipArchive();
if ($zip->open($fileDestination) === true) {
$zip->extractTo('../../.tmp/restore/');
$zip->close();
} else {
die(json_encode([
"success" => false,
"message" => "Failed to extract the uploaded file"
]));
}
if (file_exists('../../.tmp/restore/wallos.db')) {
if (file_exists('../../db/wallos.db')) {
unlink('../../db/wallos.db');
}
rename('../../.tmp/restore/wallos.db', '../../db/wallos.db');
if (file_exists('../../.tmp/restore/logos/')) {
$dir = '../../images/uploads/logos/';
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ( $ri as $file ) {
if ( $file->isDir() ) {
rmdir($file->getPathname());
} else {
unlink($file->getPathname());
}
}
$dir = new RecursiveDirectoryIterator('../../.tmp/restore/logos/');
$ite = new RecursiveIteratorIterator($dir);
$allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'];
foreach ($ite as $filePath) {
if (in_array(pathinfo($filePath, PATHINFO_EXTENSION), $allowedExtensions)) {
$destination = str_replace('../../.tmp/restore/', '../../images/uploads/', $filePath);
$destinationDir = pathinfo($destination, PATHINFO_DIRNAME);
if (!is_dir($destinationDir)) {
mkdir($destinationDir, 0755, true);
}
copy($filePath, $destination);
}
}
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('../../.tmp', RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
$removeFunction = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
$removeFunction($fileinfo->getRealPath());
}
echo json_encode([
"success" => true,
"message" => translate("success", $i18n)
]);
} else {
die(json_encode([
"success" => false,
"message" => "wallos.db does not exist in the backup file"
]));
}
} else {
echo json_encode([
"success" => false,
"message" => "Failed to upload file"
]);
}
} else {
echo json_encode([
"success" => false,
"message" => "No file uploaded"
]);
}
} else {
echo json_encode([
"success" => false,
"message" => "Invalid request method"
]);
}
?>

View File

@ -0,0 +1,34 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
$color = $data['color'];
$stmt = $db->prepare('UPDATE settings SET color_theme = :color');
$stmt->bindParam(':color', $color, SQLITE3_TEXT);
if ($stmt->execute()) {
die(json_encode([
"success" => true,
"message" => translate("success", $i18n)
]));
} else {
die(json_encode([
"success" => false,
"message" => translate("error", $i18n)
]));
}
}
?>

View File

@ -0,0 +1,41 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
$main_color = $data['mainColor'];
$accent_color = $data['accentColor'];
$hover_color = $data['hoverColor'];
$stmt = $db->prepare('DELETE FROM custom_colors');
$stmt->execute();
$stmt = $db->prepare('INSERT INTO custom_colors (main_color, accent_color, hover_color) VALUES (:main_color, :accent_color, :hover_color)');
$stmt->bindParam(':main_color', $main_color, SQLITE3_TEXT);
$stmt->bindParam(':accent_color', $accent_color, SQLITE3_TEXT);
$stmt->bindParam(':hover_color', $hover_color, SQLITE3_TEXT);
if ($stmt->execute()) {
die(json_encode([
"success" => true,
"message" => translate("success", $i18n)
]));
} else {
die(json_encode([
"success" => false,
"message" => translate("error", $i18n)
]));
}
}
?>

View File

@ -0,0 +1,33 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$postData = file_get_contents("php://input");
$data = json_decode($postData, true);
$hide_disabled = $data['value'];
$stmt = $db->prepare('UPDATE settings SET hide_disabled = :hide_disabled');
$stmt->bindParam(':hide_disabled', $hide_disabled, SQLITE3_INTEGER);
if ($stmt->execute()) {
die(json_encode([
"success" => true,
"message" => translate("success", $i18n)
]));
} else {
die(json_encode([
"success" => false,
"message" => translate("error", $i18n)
]));
}
}
?>

View File

@ -0,0 +1,28 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
if ($_SERVER["REQUEST_METHOD"] === "DELETE") {
$stmt = $db->prepare('DELETE FROM custom_colors');
if ($stmt->execute()) {
die(json_encode([
"success" => true,
"message" => translate("success", $i18n)
]));
} else {
die(json_encode([
"success" => false,
"message" => translate("error", $i18n)
]));
}
}
?>

View File

@ -1,48 +0,0 @@
<?php
require_once '../../includes/connect_endpoint.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
require_once '../../includes/getdbkeys.php';
$query = "SELECT * FROM subscriptions";
$result = $db->query($query);
if ($result) {
$subscriptions = array();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
// Map foreign keys to their corresponding values
$row['currency'] = $currencies[$row['currency_id']];
$row['payment_method'] = $payment_methods[$row['payment_method_id']];
$row['payer_user'] = $members[$row['payer_user_id']];
$row['category'] = $categories[$row['category_id']];
$row['cycle'] = $cycles[$row['cycle']];
$row['frequency'] = $frequencies[$row['frequency']];
$subscriptions[] = $row;
}
// Output JSON
$json = json_encode($subscriptions, JSON_PRETTY_PRINT);
// Set headers for file download
header('Content-Type: application/json');
header('Content-Disposition: attachment; filename="subscriptions.json"');
header('Pragma: no-cache');
header('Expires: 0');
// Output JSON for download
echo $json;
} else {
echo json_encode(array('error' => 'Failed to fetch subscriptions.'));
}
?>

View File

@ -14,6 +14,11 @@
$theme = $settings['theme'];
}
$colorTheme = "blue";
if (isset($settings['color_theme'])) {
$colorTheme = $settings['color_theme'];
}
if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === true) {
$sort = "next_payment";
$order = "ASC";
@ -63,8 +68,11 @@
}
}
$defaultLogo = $theme == "light" ? "images/wallos.png" : "images/walloswhite.png";
$defaultLogo = $theme == "light" ? "images/siteicons/" . $colorTheme . "/wallos.png" : "images/siteicons/" . $colorTheme . "/walloswhite.png";
foreach ($subscriptions as $subscription) {
if ($subscription['inactive'] == 1 && isset($settings['hideDisabledSubscriptions']) && $settings['hideDisabledSubscriptions'] === 'true') {
continue;
}
$id = $subscription['id'];
$print[$id]['id'] = $id;
$print[$id]['logo'] = $subscription['logo'] != "" ? "images/uploads/logos/".$subscription['logo'] : $defaultLogo;
@ -97,7 +105,7 @@
}
if (isset($print)) {
printSubscriptions($print, $sort, $categories, $members, $i18n);
printSubscriptions($print, $sort, $categories, $members, $i18n, $colorTheme);
}
if (count($subscriptions) == 0) {

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -8,6 +8,12 @@
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
$result = $stmt->execute();
$userData = $result->fetchArray(SQLITE3_ASSOC);
if ($userData === false) {
header('Location: logout.php');
exit();
}
if ($userData['avatar'] == "") {
$userData['avatar'] = "0";
}

View File

@ -24,5 +24,11 @@
<div class="progress success"></div>
</div>
<?php
if (isset($db)) {
$db->close();
}
?>
</body>
</html>

View File

@ -7,9 +7,19 @@ if ($settings) {
$cookieExpire = time() + (30 * 24 * 60 * 60);
setcookie('theme', $settings['dark_theme'] ? 'dark': 'light', $cookieExpire);
$settings['theme'] = $settings['dark_theme'] ? 'dark': 'light';
$settings['color_theme'] = $settings['color_theme'] ? $settings['color_theme'] : "blue";
$settings['showMonthlyPrice'] = $settings['monthly_price'] ? 'true': 'false';
$settings['convertCurrency'] = $settings['convert_currency'] ? 'true': 'false';
$settings['removeBackground'] = $settings['remove_background'] ? 'true': 'false';
$settings['hideDisabledSubscriptions'] = $settings['hide_disabled'] ? 'true': 'false';
}
$query = "SELECT * FROM custom_colors";
$result = $db->query($query);
$customColors = $result->fetchArray(SQLITE3_ASSOC);
if ($customColors) {
$settings['customColors'] = $customColors;
}
?>

View File

@ -23,6 +23,10 @@
$theme = $settings['theme'];
}
$colorTheme = "blue";
if (isset($settings['color_theme'])) {
$colorTheme = $settings['color_theme'];
}
?>
<!DOCTYPE html>
<html>
@ -34,8 +38,12 @@
<link rel="icon" type="image/png" href="images/icon/favicon.ico" sizes="16x16">
<link rel="apple-touch-icon" sizes="180x180" href="images/icon/apple-touch-icon.png">
<link rel="manifest" href="manifest.json" crossorigin="use-credentials">
<link rel="stylesheet" href="styles/theme.css?<?= $version ?>">
<link rel="stylesheet" href="styles/styles.css?<?= $version ?>">
<link rel="stylesheet" href="styles/dark-theme.css?<?= $version ?>" id="dark-theme" <?= $theme == "light" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/themes/red.css?<?= $version ?>" id="red-theme" <?= $colorTheme != "red" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/themes/green.css?<?= $version ?>" id="green-theme" <?= $colorTheme != "green" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/themes/yellow.css?<?= $version ?>" id="yellow-theme" <?= $colorTheme != "yellow" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/barlow.css">
<link rel="stylesheet" href="styles/font-awesome.min.css">
<script type="text/javascript" src="scripts/all.js?<?= $version ?>"></script>
@ -43,11 +51,31 @@
<script type="text/javascript">
window.theme = "<?= $theme ?>";
window.lang = "<?=$lang ?>";
window.colorTheme = "<?= $colorTheme ?>";
</script>
<?php
if (isset($settings['customColors'])) {
?>
<style id="custom_theme_colors">
:root {
<?php if (isset($settings['customColors']['main_color']) && !empty($settings['customColors']['main_color'])): ?>
--main-color: <?= $settings['customColors']['main_color'] ?>;
<?php endif; ?>
<?php if (isset($settings['customColors']['accent_color']) && !empty($settings['customColors']['accent_color'])): ?>
--accent-color: <?= $settings['customColors']['accent_color'] ?>;
<?php endif; ?>
<?php if (isset($settings['customColors']['hover_color']) && !empty($settings['customColors']['hover_color'])): ?>
--hover-color: <?= $settings['customColors']['hover_color'] ?>;
<?php endif; ?>
}
</style>
<?php
}
?>
<script type="text/javascript" src="scripts/i18n/<?= $lang ?>.js?<?= $version ?>"></script>
<script type="text/javascript" src="scripts/i18n/getlang.js?<?= $version ?>"></script>
</head>
<body>
<body class="<?= $theme ?>">
<header>
<div class="contain">
<div class="logo">

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Die Passwörter stimmen nicht überein",
"registration_failed" => "Registrierung fehlgeschlagen, bitte erneut versuchen.",
"register" => "Registrieren",
"restore_database" => "Datenbank wiederherstellen",
// Login Page
'please_login' => "Bitte einloggen",
'stay_logged_in' => "Angemeldet bleiben (30 Tage)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Erhalte deinen kostenfreien Fixer API Key",
"get_key_alternative" => "Alternativ können Sie einen kostenlosen Fixer-Api-Schlüssel erhalten von",
"display_settings" => "Display-Einstellungen",
"theme_settings" => "Themen-Einstellungen",
"custom_colors" => "Benutzerdefinierte Farben",
"dark_theme" => "Dark Theme",
"switch_theme" => "Light / Dark Theme umschalten",
"calculate_monthly_price" => "Berechne und zeige monatlichen Preis für alle Abonnements an",
"convert_prices" => "Preise immer in meine Hauptwährung umrechnen und darin anzeigen (langsamer)",
"hide_disabled_subscriptions" => "Deaktivierte Abonnements verstecken",
"experimental_settings" => "Experimentelle Einstellungen",
"remove_background" => "Versuchen den Hintergrund von Logos aus der Bildersuche zu entfernen (experimentell)",
"experimental_info" => "Experimentelle Einstellungen funktionieren möglicherweise nicht perfekt.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Test",
"add" => "Hinzufügen",
"save" => "Speichern",
"export_subscriptions" => "Abonnements exportieren",
"export_to_json" => "Nach JSON exportieren",
"reset" => "Zurücksetzen",
"backup_and_restore" => "Backup und Wiederherstellung",
"backup" => "Backup",
"restore" => "Wiederherstellen",
"restore_info" => "Durch die Wiederherstellung der Datenbank werden alle aktuellen Daten überschrieben. Nach der Wiederherstellung werden Sie abgemeldet.",
// Filters menu
"filter" => "Filter",
"clear" => "Leeren",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
"registration_failed" => "Η εγγραφή απέτυχε, παρακαλώ προσπάθησε ξανά.",
"register" => "Εγγραφή",
"restore_database" => "Επαναφορά βάσης δεδομένων",
// Login Page
'please_login' => "Παρακαλώ συνδέσου",
'stay_logged_in' => "Μείνε συνδεδεμένος (30 ημέρες)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Απόκτησε ΔΩΡΕΑΝ Fixer API κλειδί",
"get_key_alternative" => "Εναλλακτικά, μπορείτε να λάβετε ένα δωρεάν κλειδί api fixer από το",
"display_settings" => "Ρυθμίσεις εμφάνισης",
"theme_settings" => "Ρυθμίσεις θέματος",
"custom_colors" => "Προσαρμοσμένα χρώματα",
"dark_theme" => "Dark Theme",
"switch_theme" => "Διακόπτης Light / Dark Theme",
"calculate_monthly_price" => "Υπολογισμός και εμφάνιση της μηνιαίας τιμής για όλες τις συνδρομές",
"convert_prices" => "Πάντα να μετατρέπει και να εμφανίζει τις τιμές στο κύριο νόμισμά μου (πιο αργό)",
"hide_disabled_subscriptions" => "Απόκρυψη απενεργοποιημένων συνδρομών",
"experimental_settings" => "Πειραματικές ρυθμίσεις",
"remove_background" => "Προσπάθεια αφαίρεσης του φόντου των λογότυπων από την αναζήτηση εικόνας (πειραματικά)",
"experimental_info" => "Οι πειραματικές ρυθμίσεις πιθανότατα δεν θα λειτουργούν τέλεια.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Δοκιμή",
"add" => "Προσθήκη",
"save" => "Αποθήκευση",
"export_subscriptions" => "Εξαγωγή συνδρομών",
"export_to_json" => "Εξαγωγή σε JSON",
"reset" => "Επαναφορά",
"backup_and_restore" => "Αντίγραφο ασφαλείας και επαναφορά",
"backup" => "Αντίγραφο ασφαλείας",
"restore" => "Επαναφορά",
"restore_info" => "Η επαναφορά της βάσης δεδομένων θα ακυρώσει όλα τα τρέχοντα δεδομένα. Μετά την επαναφορά θα αποσυνδεθείτε.",
// Filters menu
"filter" => "Φίλτρο",
"clear" => "Καθαρισμός",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Passwords do not match",
"registration_failed" => "Registration failed, please try again.",
"register" => "Register",
"restore_database" => "Restore Database",
// Login Page
'please_login' => "Please login",
'stay_logged_in' => "Stay logged in (30 days)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Get free Fixer API Key",
"get_key_alternative" => "Alternatively, you can get a free fixer api key from",
"display_settings" => "Display Settings",
"theme_settings" => "Theme Settings",
"custom_colors" => "Custom Colors",
"dark_theme" => "Dark Theme",
"switch_theme" => "Switch Light / Dark Theme",
"calculate_monthly_price" => "Calculate and show monthly price for all subscriptions",
"convert_prices" => "Always convert and show prices on my main currency (slower)",
"hide_disabled_subscriptions" => "Hide disabled subscriptions",
"experimental_settings" => "Experimental Settings",
"remove_background" => "Attempt to remove background of logos from image search (experimental)",
"experimental_info" => "Experimental settings will probably not work perfectly.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Test",
"add" => "Add",
"save" => "Save",
"export_subscriptions" => "Export Subscriptions",
"export_to_json" => "Export to JSON",
"reset" => "Reset",
"backup_and_restore" => "Backup and Restore",
"backup" => "Backup",
"restore" => "Restore",
"restore_info" => "Restoring the database will override all current data. You will be signed out after the restore.",
// Filters menu
"filter" => "Filter",
"clear" => "Clear",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Las contraseñas no coinciden",
"registration_failed" => "Error en el registro, por favor inténtalo de nuevo.",
"register" => "Registrar",
"restore_database" => "Restaurar Base de Datos",
// Login Page
'please_login' => "Por favor, inicia sesión",
'stay_logged_in' => "Mantener sesión iniciada (30 días)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Obtén una API Key de Fixer gratuita",
"get_key_alternative" => "También puede obtener una clave api gratuita de Fixer en",
"display_settings" => "Configuración de Pantalla",
"theme_settings" => "Configuración de Tema",
"custom_collors" => "Colores Personalizados",
"dark_theme" => "Tema Oscuro",
"switch_theme" => "Cambiar entre Tema Claro / Oscuro",
"calculate_monthly_price" => "Calcular y mostrar el precio mensual de todas las suscripciones",
"convert_prices" => "Convertir y mostrar siempre los precios en mi moneda principal (más lento)",
"hide_disabled_subscriptions" => "Ocultar suscripciones desactivadas",
"experimental_settings" => "Configuraciones Experimentales",
"remove_background" => "Intentar quitar el fondo de los logotipos de la búsqueda de imágenes (experimental)",
"experimental_info" => "Las configuraciones experimentales probablemente no funcionarán perfectamente.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Probar",
"add" => "Agregar",
"save" => "Guardar",
"export_subscriptions" => "Exportar suscripciones",
"export_to_json" => "Exportar a JSON",
"reset" => "Restablecer",
"backup_and_restore" => "Copia de Seguridad y Restauración",
"backup" => "Copia de Seguridad",
"restore" => "Restaurar",
"restore_info" => "La restauración de la base de datos anulará todos los datos actuales. Se cerrará la sesión después de la restauración.",
// Filters menu
"filter" => "Filtrar",
"clear" => "Limpiar",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Les mots de passe ne correspondent pas",
"registration_failed" => "L'inscription a échoué, veuillez réessayer.",
"register" => "S'inscrire",
"restore_database" => "Restaurer la base de données",
// Page de connexion
'please_login' => "Veuillez vous connecter",
'stay_logged_in' => "Rester connecté (30 jours)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Obtenez une clé API Fixer gratuite",
"get_key_alternative" => "Vous pouvez également obtenir une clé api de fixation gratuite auprès de",
"display_settings" => "Paramètres d'affichage",
"theme_settings" => "Paramètres de thème",
"custom_colors" => "Couleurs personnalisées",
"dark_theme" => "Thème sombre",
"switch_theme" => "Basculer entre le thème clair et sombre",
"calculate_monthly_price" => "Calculer et afficher le prix mensuel pour tous les abonnements",
"convert_prices" => "Convertir toujours et afficher les prix dans ma devise principale (plus lent)",
"hide_disabled_subscriptions" => "Masquer les abonnements désactivés",
"experimental_settings" => "Paramètres expérimentaux",
"remove_background" => "Tenter de supprimer l'arrière-plan des logos de la recherche d'images (expérimental)",
"experimental_info" => "Les paramètres expérimentaux ne fonctionneront probablement pas parfaitement.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Test",
"add" => "Ajouter",
"save" => "Enregistrer",
"export_subscriptions" => "Exporter les abonnements",
"export_to_json" => "Exporter en JSON",
"reset" => "Réinitialiser",
"backup_and_restore" => "Sauvegarde et restauration",
"backup" => "Sauvegarde",
"restore" => "Restauration",
"restore_info" => "La restauration de la base de données annulera toutes les données actuelles. Vous serez déconnecté après la restauration.",
// Menu des filtes
"filter" => "Filtre",
"clear" => "Effacer",

198
includes/i18n/it.php Normal file
View File

@ -0,0 +1,198 @@
<?php
$i18n = [
// Registration
'create_account' => 'Devi creare un account prima di poter accedere',
'username' => 'Nome utente',
'password' => 'Password',
'email' => 'Email',
'confirm_password' => 'Conferma password',
'main_currency' => 'Valuta principale',
'language' => 'Lingua',
'passwords_dont_match' => 'Le password non corrispondono',
'registration_failed' => 'Registrazione fallita, riprova.',
'register' => 'Registrati',
"restore_database" => 'Ripristina database',
// Login
'please_login' => 'Per favore, accedi',
'stay_logged_in' => 'Rimani connesso (30 giorni)',
'login' => 'Accedi',
'login_failed' => 'Le credenziali non sono corrette',
// Header
'subscriptions' => 'Abbonamenti',
'stats' => 'Statistiche',
'settings' => 'Impostazioni',
'about' => 'Informazioni',
'logout' => 'Esci',
// Subscriptions
'subscription' => 'Abbonamento',
'no_subscriptions_yet' => 'Non hai ancora nessun abbonamento',
'add_first_subscription' => 'Aggiungo il tuo primo abbonamento',
'new_subscription' => 'Nuovo abbonamento',
'search' => 'Cerca',
'sort' => 'Ordina',
'name' => 'Nome',
'last_added' => 'Ultimo aggiunto',
'price' => 'Prezzo',
'next_payment' => 'Prossimo pagamento',
'inactive' => 'Disattiva abbonamento',
'member' => 'Membro',
'category' => 'Categoria',
'payment_method' => 'Metodo di pagamento',
'Daily' => 'Quotidiano',
'Weekly' => 'Settimanale',
'Monthly' => 'Mensile',
'Yearly' => 'Annuale',
'daily' => 'Giorno/i',
'weekly' => 'Settimana/e',
'monthly' => 'Mese/i',
'yearly' => 'Anno/i',
'days' => 'giorni',
'weeks' => 'settimane',
'months' => 'mesi',
'years' => 'anni',
'external_url' => 'Apri URL esterno',
'empty_page' => 'Pagina vuota',
'clear_filters' => 'Pulisci filtri',
'no_matching_subscriptions' => 'Nessun abbonamento corrispondente',
// Add/Edit Subscription
'add_subscription' => 'Aggiungi abbonamento',
'edit_subscription' => 'Modifica abbonamento',
'subscription_name' => 'Nome abbonamento',
'logo_preview' => 'Anteprima del logo',
'search_logo' => 'Cerca il logo sul web',
'web_search' => 'Ricerca web',
'currency' => 'Valuta',
'payment_every' => 'Pagamento ogni',
'frequency' => 'Frequenza',
'cycle' => 'Ciclo',
'no_category' => 'Nessuna categoria',
'paid_by' => 'Pagato da',
'url' => 'URL',
'notes' => 'Note',
'enable_notifications' => 'Abilita notifiche per questo abbonamento',
'delete' => 'Cancella',
'cancel' => 'Annulla',
'upload_logo' => 'Carica logo',
// Statistics
'general_statistics' => 'Statistiche generali',
'active_subscriptions' => 'Abbonamenti attivi',
'inactive_subscriptions' => 'Abbonamenti inattivi',
'monthly_cost' => 'Costo mensile',
'yearly_cost' => 'Costo annuale',
'average_monthly' => "Costo medio mensile dell'abbonamento",
'most_expensive' => "Costo dell'abbonamento più elevato",
'amount_due' => 'Importo dovuto questo mese',
'monthly_savings' => 'Risparmi mensili (su abbonamenti inattivi)',
'split_views' => 'Visualizzazioni con grafici',
'category_split' => 'Suddivisione per categoria',
'household_split' => 'Suddivisione per nucleo familiare',
'payment_method_split' => 'Suddivisione per metodo di pagamento',
// About
'about_and_credits' => 'Informazioni e crediti',
'license' => 'Licenza',
'issues_and_requests' => 'Problemi e richieste',
'the_author' => "L'autore",
'icons' => 'Icone',
'payment_icons' => 'Icone di pagamento',
// Settings
'upload_avatar' => 'Carica avatar',
'file_type_error' => 'Il tipo di file fornito non è supportato.',
'user_details' => 'Dettagli utente',
'household' => 'Nucleo familiare',
'save_member' => 'Salva membro',
'delete_member' => 'Elimina membro',
'cant_delete_member' => 'Non è possibile eliminare il membro principale',
'cant_delete_member_in_use' => 'Non è possibile eliminare un membro che utilizza almeno un abbonamento',
'household_info' => 'Il campo e-mail consente ai membri del nucleo familiare di essere avvisati degli abbonamenti in procinto di scadere.',
'notifications' => 'Notifiche',
'enable_email_notifications' => 'Abilita le notifiche via e-mail',
'notify_me' => 'Avvisami',
'day_before' => 'giorno prima',
'days_before' => 'giorni prima',
'smtp_address' => 'Indirizzo SMTP',
'port' => 'Porta',
'tls' => 'TLS',
'ssl' => 'SSL',
'smtp_username' => 'Nome utente SMTP',
'smtp_password' => 'Password SMTP',
'from_email' => 'Da quale e-mail (Opzionale)',
'smtp_info' => 'La password SMTP viene memorizzata e trasmessa in chiaro. Per motivi di sicurezza, si prega di creare un account da utilizzare solo per questo.',
'categories' => 'Categorie',
'save_category' => 'Salva categoria',
'delete_category' => 'Elimina categoria',
'cant_delete_category_in_use' => 'Non è possibile eliminare una categoria in uso da almeno un abbonamento',
'currencies' => 'Valute',
'save_currency' => 'Salva valuta',
'delete_currency' => 'Elimina valuta',
'cant_delete_main_currency' => 'Impossibile eliminare la valuta principale',
'cant_delete_currency_in_use' => 'Non è possibile eliminare la valuta in uso da almeno un abbonamento',
'exchange_update' => "Tassi di cambio aggiornati l'ultima volta il",
'currency_info' => 'Trova le valute supportate e i codici valuta corretti su',
'currency_performance' => 'Per garantire prestazioni migliori, tieni solo le valute che utilizzi.',
'fixer_api_key' => 'Chiave API di Fixer',
'api_key' => 'Chiave API',
'provider' => 'Fornitore',
'fixer_info' => 'Se utilizzi più valute e desideri visualizzare statistiche e ordinamenti accurati sugli abbonamenti, è necessaria una chiave API (Gratuita) da Fixer.',
'get_key' => 'Ottieni la tua chiave su',
'get_free_fixer_api_key' => 'Ottieni gratuitamente la chiave API di Fixer',
'get_key_alternative' => 'In alternativa, puoi ottenere gratuitamente una chiave API di Fixer da',
'display_settings' => 'Impostazioni di visualizzazione',
"theme_settings" => 'Impostazioni del tema',
"custom_colors" => 'Colori personalizzati',
"dark_theme" => 'Tema scuro',
'switch_theme' => 'Cambia tema chiaro/scuro',
'calculate_monthly_price' => 'Calcola e mostra il prezzo mensile per tutti gli abbonamenti',
'convert_prices' => 'Converti sempre e mostra i prezzi nella mia valuta principale (più lento)',
"hide_disabled_subscriptions" => 'Nascondi gli abbonamenti disattivati',
'experimental_settings' => 'Impostazioni sperimentali',
'remove_background' => 'Prova a rimuovere lo sfondo dei loghi dalla ricerca delle immagini (sperimentale)',
'experimental_info' => 'Le impostazioni sperimentali potrebbero non funzioneranno perfettamente.',
'payment_methods' => 'Metodi di pagamento',
'payment_methods_info' => 'Fai clic su un metodo di pagamento per abilitarlo/disabilitarlo.',
'rename_payment_methods_info' => 'Fai clic sul nome di un metodo di pagamento per rinominarlo.',
'cant_delete_payment_method_in_use' => 'Non è possibile disabilitare un metodo di pagamento in uso',
'add_custom_payment' => 'Aggiungi metodo di pagamento personalizzato',
'payment_method_name' => 'Nome del metodo di pagamento',
'payment_method_added_successfuly' => 'Metodo di pagamento aggiunto con successo',
'payment_method_removed' => 'Metodo di pagamento rimosso',
'disable' => 'Disabilita',
'enable' => 'Abilita',
'rename_payment_method' => 'Rinomina metodo di pagamento',
'payment_renamed' => 'Metodo di pagamento rinominato',
'payment_not_renamed' => 'Metodo di pagamento non rinominato',
'test' => 'Test',
'add' => 'Aggiungi',
'save' => 'Salva',
"reset" => 'Ripristina',
"backup_and_restore" => 'Backup e ripristino',
"backup" => 'Backup',
"restore" => 'Ripristina',
"restore_info" => "Il ripristino del database annullerà tutti i dati correnti. Al termine del ripristino, l'utente verrà disconnesso.",
// Filters
'filter' => 'Filtra',
'clear' => 'Pulisci',
// Toast
'success' => 'Successo',
// Endpoint responses
'session_expired' => 'La tua sessione è scaduta. Effettua nuovamente il login',
'fields_missing' => 'Mancano alcuni campi',
'fill_all_fields' => 'Si prega di compilare tutti i campi',
'fill_mandatory_fields' => 'Si prega di compilare tutti i campi obbligatori',
'error' => 'Errore',
// Category
'failed_add_category' => 'Impossibile aggiungere la categoria',
'failed_edit_category' => 'Impossibile modificare la categoria',
];
?>

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "パスワードが違います",
"registration_failed" => "登録に失敗しました。もう一度お試しください。",
"register" => "登録する",
"restore_database" => "データベースをリストア",
// Login Page
'please_login' => "ログインしてください",
'stay_logged_in' => "ログインしたままにする (30日)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "無料のFixer APIキーを取得",
"get_key_alternative" => "または、以下のサイトから無料のフィクサーapiキーを入手することもできます。",
"display_settings" => "表示設定",
"theme_settings" => "テーマ設定",
"custom_colors" => "カスタムカラー",
"dark_theme" => "ダークテーマ",
"switch_theme" => "ライト/ダーク テーマの切り替え",
"calculate_monthly_price" => "すべての定期購入の月額料金を計算して表示する",
"convert_prices" => "常にメイン通貨で価格を換算して表示する (遅い)",
"hide_disabled_subscriptions" => "無効な定期購入を非表示にする",
"experimental_settings" => "実験的な設定",
"remove_background" => "画像検索からロゴの背景を削除する (実験)",
"experimental_info" => "実験的な設定は、おそらく完全には機能しません。",
@ -161,8 +166,11 @@ $i18n = [
"test" => "テスト",
"add" => "追加",
"save" => "保存",
"export_subscriptions" => "購読をエクスポート",
"export_to_json" => "JSONにエクスポート",
"reset" => "リセット",
"backup_and_restore" => "バックアップとリストア",
"backup" => "バックアップ",
"restore" => "リストア",
"restore_info" => "データベースをリストアすると、現在のデータがすべて上書きされます。リストア後はサインアウトされます。",
// Filters menu
"filter" => "フィルタ",
"clear" => "クリア",

View File

@ -1,6 +1,6 @@
<?php
// File Name => Language Name
// File Name => Language Name
$languages = [
// English first
"en" => "English",
@ -9,12 +9,13 @@
"el" => "Ελληνικά",
"es" => "Español",
"fr" => "Français",
"it" => "Italiano",
"jp" => "日本語",
"pl" => "Polski",
"pl" => "Polski",
"pt" => "Português",
"pt_br" => "Português Brasileiro",
"sr" => "Српски",
"sr_lat" => "Srpski",
"sr_lat" => "Srpski",
"sr" => "Српски",
"tr" => "Türkçe",
"zh_cn" => "简体中文",
"zh_tw" => "繁體中文",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Hasła nie pasują",
"registration_failed" => "Rejestracja nie powiodła się, spróbuj ponownie.",
"register" => "Rejestracja",
"restore_database" => "Przywróć bazę danych",
// Login Page
'please_login' => "Proszę się zalogować",
'stay_logged_in' => "Pozostań zalogowany (30 dni)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Uzyskaj bezpłatny klucz API Fixer'a",
"get_key_alternative" => "Alternatywnie, możesz uzyskać darmowy klucz api fixer'a od",
"display_settings" => "Ustawienia wyświetlania",
"theme_settings" => "Ustawienia motywu",
"custom_colors" => "Kolory niestandardowe",
"dark_theme" => "Przełącz na jasny/ciemny motyw",
"switch_theme" => "Przełącz na jasny/ciemny motyw",
"calculate_monthly_price" => "Oblicz i pokaż miesięczną cenę wszystkich subskrypcji",
"convert_prices" => "Zawsze przeliczaj i pokazuj ceny w mojej głównej walucie (wolniej)",
"hide_disabled_subscriptions" => "Ukryj wyłączone subskrypcje",
"experimental_settings" => "Ustawienia eksperymentalne",
"remove_background" => "Próba usunięcia tła logo z wyszukiwania obrazów (eksperymentalnie)",
"experimental_info" => "Ustawienia eksperymentalne prawdopodobnie nie będą działać idealnie.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Test",
"add" => "Dodaj",
"save" => "Zapisz",
"export_subscriptions" => "Eksportuj subskrypcje",
"export_to_json" => "Eksportuj do JSON",
"reset" => "Resetuj",
"backup_and_restore" => "Kopia zapasowa i przywracanie",
"backup" => "Kopia zapasowa",
"restore" => "Przywróć",
"restore_info" => "Przywrócenie bazy danych zastąpi wszystkie bieżące dane. Po przywróceniu zostaniesz wylogowany.",
// Filters menu
"filter" => "Filtr",
"clear" => "Wyczyść",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "As passwords não coincidem",
"registration_failed" => "O registo falhou. Tente novamente",
"register" => "Registar",
"restore_database" => "Restaurar base de dados",
// Login Page
'please_login' => "Por favor inicie sessão",
'stay_logged_in' => "Manter sessão (30 dias)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Obtenha a sua API Key grátis do Fixer",
"get_key_alternative" => "Como alternativa obtenha a sua API Key em",
"display_settings" => "Definições de visualização",
"theme_settings" => "Definições de Tema",
"custom_colors" => "Cores Personalizadas",
"dark_theme" => "Tema Escuro",
"switch_theme" => "Trocar Tema Claro / Escuro",
"calculate_monthly_price" => "Calcular e mostrar preço mensal para todas as subscrições",
"convert_prices" => "Converter e mostrar todas as subscrições na moeda principal (mais lento)",
"hide_disabled_subscriptions" => "Esconder subscrições desactivadas",
"experimental_settings" => "Definições Experimentais",
"remove_background" => "Tentar remover o fundo dos logos na pesquisa de imagem (experimental)",
"experimental_info" => "Definições experimentais provavelmente não funcionarão correctamente.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Testar",
"add" => "Adicionar",
"save" => "Guardar",
"export_subscriptions" => "Exportar Subscrições",
"export_to_json" => "Exportar para JSON",
"reset" => "Repor",
"backup_and_restore" => "Backup e Restauro",
"backup" => "Backup",
"restore" => "Restauro",
"restore_info" => "O restauro da base de dados apagará todos os dados actuais. A sua sessão irá terminar após o restauro.",
// Filters menu
"filter" => "Filtro",
"clear" => "Limpar",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "As senhas não são iguais",
"registration_failed" => "O registro falhou. Por favor, tente novamente",
"register" => "Registrar",
"restore_database" => "Restaurar banco de dados",
// Login Page
'please_login' => "Por favor, faça o login",
'stay_logged_in' => "Me manter logado (30 dias)",
@ -137,9 +138,13 @@ $i18n = [
"get_free_fixer_api_key" => "Obtenha a sua chave API do Fixer gratuitamente",
"get_key_alternative" => "Como alternativa, você pode obter uma chave de API grátis em",
"display_settings" => "Configurações de visualização",
"theme_settings" => "Configurações de tema",
"custom_colors" => "Cores personalizadas",
"dark_theme" => "Tema Escuro",
"switch_theme" => "Alternar entre tema Claro / Escuro",
"calculate_monthly_price" => "Calcular e exibir o custo mensal para todas as assinaturas",
"convert_prices" => "Sempre converter e exibir preços na moeda principal (mais lento)",
"hide_disabled_subscriptions" => "Ocultar assinaturas desativadas",
"experimental_settings" => "Configurações experimentais",
"remove_background" => "Tentar remover o fundo de logos na pesquisa de imagem",
"experimental_info" => "As configurações experimentais provavelmente não funcionarão corretamente",
@ -159,8 +164,11 @@ $i18n = [
"test" => "Testar",
"add" => "Adicionar",
"save" => "Salvar",
"export_subscriptions" => "Exportar assinaturas",
"export_to_json" => "Exportar para JSON",
"reset" => "Redefinir",
"backup_and_restore" => "Backup e Restauração",
"backup" => "Backup",
"restore" => "Restaurar",
"restore_info" => "A restauração do banco de dados substituirá todos os dados atuais. Você será desconectado após a restauração.",
// Filters menu
"filter" => "Filtrar",
"clear" => "Limpar",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Лозинке се не поклапају",
"registration_failed" => "Регистрација није успела, покушајте поново.",
"register" => "Региструј се",
"restore_database" => "Врати базу података",
// Страница за пријаву
'please_login' => "Молимо вас да се пријавите",
'stay_logged_in' => "Остани пријављен (30 дана)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Добијте бесплатни Fixer API кључ",
"get_key_alternative" => "Алтернативно, можете добити бесплатни Fixer API кључ са",
"display_settings" => "Подешавања приказа",
"theme_settings" => "Подешавања теме",
"custom_colors" => "Прилагођене боје",
"dark_theme" => "Тамна тема",
"switch_theme" => "Промени светлу / тамну тему",
"calculate_monthly_price" => "Израчунајте и прикажите месечну цену за све претплате",
"convert_prices" => "Увек конвертујте и прикажите цене на мојој главној валути (спорије)",
"hide_disabled_subscriptions" => "Сакриј онемогућене претплате",
"experimental_settings" => "Експериментална подешавања",
"remove_background" => "Покушајте уклонити позадину логотипа са слика претраге (експериментално)",
"experimental_info" => "Експериментална подешавања вероватно неће радити савршено.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Тест",
"add" => "Додај",
"save" => "Сачувај",
"export_subscriptions" => "Извоз претплата",
"export_to_json" => "Извоз у JSON формат",
"reset" => "Ресетуј",
"backup_and_restore" => "Бекап и ресторе",
"backup" => "Бекап",
"restore" => "Ресторе",
"restore_info" => "Враћање базе података ће заменити све тренутне податке. Бићете одјављени након враћања.",
// Мени са филтерима
"filter" => "Филтер",
"clear" => "Очисти",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Lozinke se ne poklapaju",
"registration_failed" => "Registracija nije uspela, pokušajte ponovo.",
"register" => "Registruj se",
"restore_database" => "Vrati bazu podataka",
// Stranica za prijavu
'please_login' => "Molimo vas da se prijavite",
'stay_logged_in' => "Ostani prijavljen (30 dana)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Pronađite besplatni Fixer API ključ",
"get_key_alternative" => "Alternativno, možete dobiti besplatni Fixer API ključ na",
"display_settings" => "Podešavanja prikaza",
"theme_settings" => "Podešavanja teme",
"custom_colors" => "Prilagođene boje",
"dark_theme" => "Tamna tema",
"switch_theme" => "Promeni svetli / tamni temu",
"calculate_monthly_price" => "Izračunaj i prikaži mesečnu cenu za sve pretplate",
"convert_prices" => "Uvek konvertuj i prikaži cene u mojoj glavnoj valuti (sporije)",
"hide_disabled_subscriptions" => "Sakrij onemogućene pretplate",
"experimental_settings" => "Eksperimentalna podešavanja",
"remove_background" => "Pokušajte ukloniti pozadinu logotipa sa pretrage slika (eksperimentalno)",
"experimental_info" => "Eksperimentalna podešavanja verovatno neće savršeno funkcionisati.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Test",
"add" => "Dodaj",
"save" => "Sačuvaj",
"export_subscriptions" => "Izvezi pretplate",
"export_to_json" => "Izvezi u JSON format",
"reset" => "Resetuj",
"backup_and_restore" => "Backup i restore",
"backup" => "Backup",
"restore" => "Restore",
"restore_info" => "Vraćanje baze podataka će zameniti sve trenutne podatke. Bićete odjavljeni nakon vraćanja.",
// Meni sa filterima
"filter" => "Filter",
"clear" => "Očisti",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "Şifreler eşleşmiyor",
"registration_failed" => "Kayıt başarısız, lütfen tekrar deneyin.",
"register" => "Kayıt Ol",
"restore_database" => "Veritabanını geri yükle",
// Login Page
'please_login' => "Lütfen giriş yapın",
'stay_logged_in' => "Oturumu açık tut (30 gün)",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "Ücretsiz Fixer API Anahtarı alın",
"get_key_alternative" => "Alternatif olarak, şu adresten ücretsiz bir fixer api anahtarı edinebilirsiniz",
"display_settings" => "Görüntüleme Ayarları",
"theme_settings" => "Tema Ayarları",
"custom_colors" => "Özel Renkler",
"dark_theme" => "Koyu Temayı",
"switch_theme" => "ık / Koyu Temayı Değiştir",
"calculate_monthly_price" => "Tüm aboneliklerin aylık fiyatını hesaplayın ve gösterin",
"convert_prices" => "Fiyatları her zaman ana para birimimde dönüştürün ve gösterin (daha yavaş)",
"hide_disabled_subscriptions" => "Devre dışı bırakılan abonelikleri gizle",
"experimental_settings" => "Deneysel Ayarlar",
"remove_background" => "Görsel aramadan logoların arka planını kaldırmayı deneyin (deneysel)",
"experimental_info" => "Deneysel ayarlar muhtemelen mükemmel çalışmayacak.",
@ -161,8 +166,11 @@ $i18n = [
"test" => "Test Et",
"add" => "Ekle",
"save" => "Kaydet",
"export_subscriptions" => "Abonelikleri Dışa Aktar",
"export_to_json" => "JSON'a dışa aktar",
"reset" => "Sıfırla",
"backup_and_restore" => "Yedekle ve Geri Yükle",
"backup" => "Yedekle",
"restore" => "Geri Yükle",
"restore_info" => "Veritabanının geri yüklenmesi tüm mevcut verileri geçersiz kılacaktır. Geri yüklemeden sonra oturumunuz kapatılacaktır.",
// Filters menu
"filter" => "Filtre",
"clear" => "Temizle",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "密码不匹配",
"registration_failed" => "注册失败,请重试。",
"register" => "注册",
"restore_database" => "恢复数据库",
// 登录页面
'please_login' => "请登录",
@ -146,9 +147,13 @@ $i18n = [
"get_free_fixer_api_key" => "申请免费 Fixer API 密钥",
"get_key_alternative" => "或者,您也可以从以下网站获取免费的修复程序 api 密钥",
"display_settings" => "显示设置",
"theme_settings" => "主题设置",
"custom_colors" => "自定义颜色",
"dark_theme" => "深色主题",
"switch_theme" => "切换浅色/深色主题",
"calculate_monthly_price" => "计算并显示所有订阅的月价格",
"convert_prices" => "始终按我的主要货币转换和显示价格(较慢)",
"hide_disabled_subscriptions" => "隐藏已停用的订阅",
"experimental_settings" => "实验性设置",
"remove_background" => "尝试从图片搜索中移除标志的背景(实验性)",
"experimental_info" => "实验性设置,可能存在问题。",
@ -168,9 +173,11 @@ $i18n = [
"test" => "测试",
"add" => "添加",
"save" => "保存",
"export_subscriptions" => "导出订阅",
"export_to_json" => "导出为 JSON",
"reset" => "重置",
"backup_and_restore" => "备份和恢复",
"backup" => "备份",
"restore" => "恢复",
"restore_info" => "还原数据库将覆盖所有当前数据。还原后,您将退出登录。",
// Filters menu
"filter" => "筛选",
"clear" => "清除",

View File

@ -12,6 +12,7 @@ $i18n = [
"passwords_dont_match" => "密碼不一致",
"registration_failed" => "註冊失敗,請再試一次。",
"register" => "註冊",
"restore_database" => "還原資料庫",
// 登入頁面
'please_login' => "請先登入",
'stay_logged_in' => "保持登入 30 天",
@ -139,9 +140,13 @@ $i18n = [
"get_free_fixer_api_key" => "申請免費的 Fixer API 金鑰",
"get_key_alternative" => "或者,您可以從以下網址取得一個免費的修復 api 金鑰",
"display_settings" => "顯示設定",
"theme_settings" => "主題設定",
"custom_colors" => "自訂顏色",
"dark_theme" => "深色主題",
"switch_theme" => "切換淺色/深色主題",
"calculate_monthly_price" => "計算並顯示所有訂閱的每月價格",
"convert_prices" => "始終按照我的主要貨幣單位轉換和顯示價格(較慢)",
"hide_disabled_subscriptions" => "隱藏已停用的訂閱",
"experimental_settings" => "實驗性設定",
"remove_background" => "嘗試從圖片搜尋中移除圖示的背景顏色(實驗性)",
"experimental_info" => "實驗性設定,可能存在問題。",
@ -161,8 +166,11 @@ $i18n = [
"test" => "測試",
"add" => "新增",
"save" => "儲存",
"export_subscriptions" => "匯出訂閱",
"export_to_json" => "匯出為 JSON 檔案",
"reset" => "重設",
"backup_and_restore" => "備份與還原",
"backup" => "備份",
"restore" => "還原",
"restore_info" => "復原資料庫將覆蓋所有目前資料。 恢復後您將被註銷。",
// Filters menu
"filter" => "篩選",
"clear" => "清除",

View File

@ -56,7 +56,7 @@
}
}
function printSubscriptions($subscriptions, $sort, $categories, $members, $i18n) {
function printSubscriptions($subscriptions, $sort, $categories, $members, $i18n, $colorTheme) {
if ($sort === "price") {
usort($subscriptions, function($a, $b) {
return $a['price'] < $b['price'] ? 1 : -1;
@ -110,14 +110,14 @@
</span>
<span class="actions">
<button class="image-button medium" onClick="openEditSubscription(event, <?= $subscription['id'] ?>)" name="edit">
<img src="images/siteicons/edit.png" title="<?= translate('edit_subscription', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/edit.png" title="<?= translate('edit_subscription', $i18n) ?>">
</button>
</span>
</div>
<div class="subscription-secondary">
<span class="name"><img src="images/siteicons/subscription.png" alt="<?= translate('subscription', $i18n) ?>" /><?= $subscription['name'] ?></span>
<span class="payer_user" title="<?= translate('paid_by', $i18n) ?>"><img src="images/siteicons/payment.png" alt="<?= translate('paid_by', $i18n) ?>" /><?= $members[$subscription['payer_user_id']]['name'] ?></span>
<span class="category" title="<?= translate('category', $i18n) ?>" ><img src="images/siteicons/category.png" alt="<?= translate('category', $i18n) ?>" /><?= $categories[$subscription['category_id']]['name'] ?></span>
<span class="name"><img src="images/siteicons/<?= $colorTheme ?>/subscription.png" alt="<?= translate('subscription', $i18n) ?>" /><?= $subscription['name'] ?></span>
<span class="payer_user" title="<?= translate('paid_by', $i18n) ?>"><img src="images/siteicons/<?= $colorTheme ?>/payment.png" alt="<?= translate('paid_by', $i18n) ?>" /><?= $members[$subscription['payer_user_id']]['name'] ?></span>
<span class="category" title="<?= translate('category', $i18n) ?>" ><img src="images/siteicons/<?= $colorTheme ?>/category.png" alt="<?= translate('category', $i18n) ?>" /><?= $categories[$subscription['category_id']]['name'] ?></span>
<?php
if ($subscription['url'] != "") {
$url = $subscription['url'];
@ -125,7 +125,7 @@
$url = "https://" . $url;
}
?>
<span class="url" title="<?= translate('external_url', $i18n) ?>"><a href="<?= $url ?>" target="_blank"><img src="images/siteicons/web.png" alt="<?= translate('url', $i18n) ?>" /></a></span>
<span class="url" title="<?= translate('external_url', $i18n) ?>"><a href="<?= $url ?>" target="_blank"><img src="images/siteicons/<?= $colorTheme ?>/web.png" alt="<?= translate('url', $i18n) ?>" /></a></span>
<?php
}
?>
@ -135,7 +135,7 @@
?>
<div class="subscription-notes">
<span class="notes">
<img src="images/siteicons/notes.png" alt="<?= translate('notes', $i18n) ?>" />
<img src="images/siteicons/<?= $colorTheme ?>/notes.png" alt="<?= translate('notes', $i18n) ?>" />
<?= $subscription['notes'] ?>
</span>
</div>

View File

@ -1,3 +1,3 @@
<?php
$version = "v1.20.2";
$version = "v1.23.0";
?>

View File

@ -37,7 +37,7 @@
}
$headerClass = count($subscriptions) > 0 ? "main-actions" : "main-actions hidden";
$defaultLogo = $theme == "light" ? "images/wallos.png" : "images/walloswhite.png";
$defaultLogo = $theme == "light" ? "images/siteicons/" . $colorTheme . "/wallos.png" : "images/siteicons/" . $colorTheme . "/walloswhite.png";
?>
<style>
.logo-preview:after {
@ -156,6 +156,9 @@
<div class="subscriptions" id="subscriptions">
<?php
foreach ($subscriptions as $subscription) {
if ($subscription['inactive'] == 1 && isset($settings['hideDisabledSubscriptions']) && $settings['hideDisabledSubscriptions'] === 'true') {
continue;
}
$id = $subscription['id'];
$print[$id]['id'] = $id;
$print[$id]['logo'] = $subscription['logo'] != "" ? "images/uploads/logos/".$subscription['logo'] : $defaultLogo;
@ -188,7 +191,7 @@
}
if (isset($print)) {
printSubscriptions($print, $sort, $categories, $members, $i18n);
printSubscriptions($print, $sort, $categories, $members, $i18n, $colorTheme);
}
$db->close();
@ -224,7 +227,7 @@
<input type="file" id="logo" name="logo" accept="image/jpeg, image/png, image/gif, image/webp" onchange="handleFileSelect(event)" class="hidden-input">
<input type="hidden" id="logo-url" name="logo-url">
<div id="logo-search-button" class="image-button medium disabled" title="<?= translate('search_logo', $i18n) ?>" onClick="searchLogo()">
<img src="images/siteicons/websearch.png">
<img src="images/siteicons/<?= $colorTheme ?>/websearch.png">
</div>
<input type="hidden" id="id" name="id">
<div id="logo-search-results" class="logo-search">
@ -351,9 +354,9 @@
</div>
<div class="buttons">
<input type="button" value="<?= translate('delete', $i18n) ?>" class="warning-button left" id="deletesub" style="display: none">
<input type="button" value="<?= translate('cancel', $i18n) ?>" class="secondary-button" onClick="closeAddSubscription()">
<input type="submit" value="<?= translate('save', $i18n) ?>" id="save-button">
<input type="button" value="<?= translate('delete', $i18n) ?>" class="warning-button left thin" id="deletesub" style="display: none">
<input type="button" value="<?= translate('cancel', $i18n) ?>" class="secondary-button thin" onClick="closeAddSubscription()">
<input type="submit" value="<?= translate('save', $i18n) ?>" class="thin" id="save-button">
</div>
</form>
</section>

View File

@ -25,6 +25,11 @@ if (isset($_COOKIE['theme'])) {
$theme = $_COOKIE['theme'];
}
$colorTheme = "blue";
if (isset($_COOKIE['colorTheme'])) {
$colorTheme = $_COOKIE['colorTheme'];
}
$loginFailed = false;
if (isset($_POST['username']) && isset($_POST['password'])) {
$username = $_POST['username'];
@ -80,7 +85,11 @@ if (isset($_POST['username']) && isset($_POST['password'])) {
<link rel="icon" type="image/png" href="images/icon/favicon.ico" sizes="16x16">
<link rel="apple-touch-icon" sizes="180x180" href="images/icon/apple-touch-icon.png">
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="styles/theme.css?<?= $version ?>">
<link rel="stylesheet" href="styles/login.css?<?= $version ?>">
<link rel="stylesheet" href="styles/themes/red.css?<?= $version ?>" id="red-theme" <?= $colorTheme != "red" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/themes/green.css?<?= $version ?>" id="green-theme" <?= $colorTheme != "green" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/themes/yellow.css?<?= $version ?>" id="yellow-theme" <?= $colorTheme != "yellow" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/barlow.css">
<link rel="stylesheet" href="styles/login-dark-theme.css?<?= $version ?>" id="dark-theme" <?= $theme == "light" ? "disabled" : "" ?>>
</head>
@ -90,9 +99,9 @@ if (isset($_POST['username']) && isset($_POST['password'])) {
<header>
<?php
if ($theme == "light") {
?> <img src="images/wallossolid.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" /> <?php
?> <img src="images/siteicons/<?= $colorTheme ?>/wallos.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" width="215" /> <?php
} else {
?> <img src="images/wallossolidwhite.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" /> <?php
?> <img src="images/siteicons/<?= $colorTheme ?>/walloswhite.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" width="215" /> <?php
}
?>
<p>

25
migrations/000014.php Normal file
View File

@ -0,0 +1,25 @@
<?php
// This migration adds a "color_theme" column to the settings table and sets it to blue as default.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('settings') where name='color_theme'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;
if ($columnRequired) {
$db->exec("ALTER TABLE settings ADD COLUMN color_theme TEXT DEFAULT 'blue'");
$db->exec('UPDATE settings SET `color_theme` = "blue"');
}
// This migrations adds custom_colors table to the database, so the user can set custom accent colors to the application
$customColorsTableQuery = $db->query("SELECT * FROM sqlite_master WHERE type='table' AND name='custom_colors'");
$customColorsTableRequired = $customColorsTableQuery->fetchArray(SQLITE3_ASSOC) === false;
if ($customColorsTableRequired) {
$db->exec("CREATE TABLE custom_colors (
main_color TEXT NOT NULL,
accent_color TEXT NOT NULL,
hover_color TEXT NOT NULL
)");
}

11
migrations/000015.php Normal file
View File

@ -0,0 +1,11 @@
<?php
// This migration adds a "hide_disabled" column to the settings table and sets to false as default.
/** @noinspection PhpUndefinedVariableInspection */
$columnQuery = $db->query("SELECT * FROM pragma_table_info('settings') where name='hide_disabled'");
$columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false;
if ($columnRequired) {
$db->exec("ALTER TABLE settings ADD COLUMN hide_disabled BOOLEAN DEFAULT 0");
$db->exec('UPDATE settings SET `hide_disabled` = 0');
}

View File

@ -26,6 +26,11 @@ if (isset($_COOKIE['theme'])) {
$theme = $_COOKIE['theme'];
}
$colorTheme = "blue";
if (isset($_COOKIE['colorTheme'])) {
$colorTheme = $_COOKIE['colorTheme'];
}
$currencies = array();
$query = "SELECT * FROM currencies";
$result = $db->query($query);
@ -94,8 +99,12 @@ if (isset($_POST['username'])) {
<title>Wallos - Subscription Tracker</title>
<link rel="icon" type="image/png" href="images/icon/favicon.ico" sizes="16x16">
<link rel="apple-touch-icon" sizes="180x180" href="images/icon/apple-touch-icon.png">
<link rel="manifest" href="manifes.json">
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="styles/theme.css?<?= $version ?>">
<link rel="stylesheet" href="styles/login.css?<?= $version ?>">
<link rel="stylesheet" href="styles/themes/red.css?<?= $version ?>" id="red-theme" <?= $colorTheme != "red" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/themes/green.css?<?= $version ?>" id="green-theme" <?= $colorTheme != "green" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/themes/yellow.css?<?= $version ?>" id="yellow-theme" <?= $colorTheme != "yellow" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/login-dark-theme.css?<?= $version ?>" id="dark-theme" <?= $theme == "light" ? "disabled" : "" ?>>
<link rel="stylesheet" href="styles/barlow.css">
<script type="text/javascript" src="scripts/registration.js?<?= $version ?>"></script>
@ -106,9 +115,9 @@ if (isset($_POST['username'])) {
<header>
<?php
if ($theme == "light") {
?> <img src="images/wallossolid.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" /> <?php
?> <img src="images/siteicons/<?= $colorTheme ?>/wallos.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" width="215" /> <?php
} else {
?> <img src="images/wallossolidwhite.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" /> <?php
?> <img src="images/siteicons/<?= $colorTheme ?>/walloswhite.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" width="215" /> <?php
}
?>
<p>
@ -179,8 +188,14 @@ if (isset($_POST['username'])) {
<input type="submit" value="<?= translate('register', $i18n) ?>">
</div>
</form>
<div class="separator">
<input type="button" class="secondary-button" value="<?= translate('restore_database', $i18n) ?>" id="restoreDB" onClick="openRestoreDBFileSelect()" />
<input type="file" name="restoreDBFile" id="restoreDBFile" style="display: none;" onChange="restoreDB()" accept=".zip">
</div>
</section>
</div>
<?php
require_once 'includes/footer.php';
?>
</body>
</html>

View File

@ -37,7 +37,7 @@ function fillEditFormFields(subscription) {
const formTitle = document.querySelector("#form-title");
formTitle.textContent = translate('edit_subscription');
const logo = document.querySelector("#form-logo");
const defaultLogo = window.theme && window.theme == "light" ? "images/wallos.png" : "images/walloswhite.png";
const defaultLogo = window.theme && window.theme == "light" ? "images/siteicons/" + colorTheme + "/wallos.png" : "images/siteicons/" + colorTheme + "/walloswhite.png";
const logoFile = subscription.logo !== null ? "images/uploads/logos/" + subscription.logo : defaultLogo;
logo.src = logoFile;
logo.style = 'display: block';

37
scripts/i18n/it.js Normal file
View File

@ -0,0 +1,37 @@
let i18n = {
// Dashboard
error_reloading_subscription: "Errore nel ricaricare l'abbonamento:",
error_fetching_image_results: "Errore nel recupero delle immagini:",
subscription_deleted: "Abbonamento eliminato",
error_deleting_subscription: "Errore nell'eliminare l'abbonamento",
failed_to_load_subscription: "Caricamento dell'abbonamento non riuscito",
edit_subscription: "Modifica abbonamento",
add_subscription: "Aggiungi abbonamento",
confirm_delete_subscription: "Sei sicuro di voler eliminare questo abbonamento?",
// Settings
network_response_error: "Si è verificato un errore nella risposta del server",
failed_add_member: "Impossibile aggiungere il membro",
member: "Membro",
email: "Email",
save_member: "Salva membro",
delete_member: "Elimina membro",
failed_remove_member: "Impossibile rimuovere il membro",
failed_save_member: "Impossibile salvare il membro",
failed_add_category: "Impossibile aggiungere la categoria",
category: "Categoria",
save_category: "Salva categoria",
delete_category: "Elimina categoria",
failed_remove_category: "Impossibile rimuovere la categoria",
currency: "Valuta",
currency_code: "Codice valuta",
save_currency: "Salva valuta",
delete_currency: "Elimina valuta",
failed_remove_currency: "Impossibile rimuovere la valuta",
failed_save_currency: "Impossibile salvare la valuta",
cant_disable_payment_in_use: "Impossibile disabilitare il pagamento in uso",
failed_save_payment_method: "Impossibile salvare il metodo di pagamento",
unknown_error: "Errore sconosciuto, si prega di riprovare.",
error_saving_notification_data: "Errore nel salvataggio delle impostazioni di notifica",
error_sending_notification: "Errore nell'invio della notifica",
}

View File

@ -62,6 +62,101 @@ function runDatabaseMigration() {
});
}
function showErrorMessage(message) {
const toast = document.querySelector(".toast#errorToast");
(closeIcon = document.querySelector(".close-error")),
(errorMessage = document.querySelector(".errorMessage")),
(progress = document.querySelector(".progress.error"));
let timer1, timer2;
errorMessage.textContent = message;
toast.classList.add("active");
progress.classList.add("active");
timer1 = setTimeout(() => {
toast.classList.remove("active");
closeIcon.removeEventListener("click", () => {});
}, 5000);
timer2 = setTimeout(() => {
progress.classList.remove("active");
}, 5300);
closeIcon.addEventListener("click", () => {
toast.classList.remove("active");
setTimeout(() => {
progress.classList.remove("active");
}, 300);
clearTimeout(timer1);
clearTimeout(timer2);
closeIcon.removeEventListener("click", () => {});
});
}
function showSuccessMessage(message) {
const toast = document.querySelector(".toast#successToast");
(closeIcon = document.querySelector(".close-success")),
(successMessage = document.querySelector(".successMessage")),
(progress = document.querySelector(".progress.success"));
let timer1, timer2;
successMessage.textContent = message;
toast.classList.add("active");
progress.classList.add("active");
timer1 = setTimeout(() => {
toast.classList.remove("active");
closeIcon.removeEventListener("click", () => {});
}, 5000);
timer2 = setTimeout(() => {
progress.classList.remove("active");
}, 5300);
closeIcon.addEventListener("click", () => {
toast.classList.remove("active");
setTimeout(() => {
progress.classList.remove("active");
}, 300);
clearTimeout(timer1);
clearTimeout(timer2);
closeIcon.removeEventListener("click", () => {});
});
}
function openRestoreDBFileSelect() {
document.getElementById('restoreDBFile').click();
};
function restoreDB() {
const input = document.getElementById('restoreDBFile');
const file = input.files[0];
if (!file) {
console.error('No file selected');
return;
}
const formData = new FormData();
formData.append('file', file);
fetch('endpoints/db/import.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message)
window.location.href = 'logout.php';
} else {
showErrorMessage(data.message);
}
})
.catch(error => showErrorMessage('Error:', error));
}
window.onload = function () {
restoreFormFields();
removeFromStorage();

View File

@ -108,7 +108,7 @@ function addMemberButton(memberId) {
};
let editImage = document.createElement("img");
editImage.src = "images/siteicons/save.png";
editImage.src = "images/siteicons/" + colorTheme + "/save.png";
editImage.title = translate('save_member');
editLink.appendChild(editImage);
@ -121,7 +121,7 @@ function addMemberButton(memberId) {
};
let deleteImage = document.createElement("img");
deleteImage.src = "images/siteicons/delete.png";
deleteImage.src = "images/siteicons/" + colorTheme + "/delete.png";
deleteImage.title = translate('delete_member');
deleteLink.appendChild(deleteImage);
@ -237,7 +237,7 @@ function addCategoryButton(categoryId) {
};
let editImage = document.createElement("img");
editImage.src = "images/siteicons/save.png";
editImage.src = "images/siteicons/" + colorTheme + "/save.png";
editImage.title = translate('save_category');
editLink.appendChild(editImage);
@ -250,7 +250,7 @@ function addCategoryButton(categoryId) {
};
let deleteImage = document.createElement("img");
deleteImage.src = "images/siteicons/delete.png";
deleteImage.src = "images/siteicons/" + colorTheme + "/delete.png";
deleteImage.title = translate('delete_category');
deleteLink.appendChild(deleteImage);
@ -375,7 +375,7 @@ function addCurrencyButton(currencyId) {
};
let editImage = document.createElement("img");
editImage.src = "images/siteicons/save.png";
editImage.src = "images/siteicons/" + colorTheme + "/save.png";
editImage.title = translate('save_currency');
editLink.appendChild(editImage);
@ -388,7 +388,7 @@ function addCurrencyButton(currencyId) {
};
let deleteImage = document.createElement("img");
deleteImage.src = "images/siteicons/delete.png";
deleteImage.src = "images/siteicons/" + colorTheme + "/delete.png";
deleteImage.title = translate('delete_currency');
deleteLink.appendChild(deleteImage);
@ -937,6 +937,8 @@ function switchTheme() {
const themeChoice = darkThemeCss.disabled ? 'light' : 'dark';
document.cookie = `theme=${themeChoice}; expires=Fri, 31 Dec 9999 23:59:59 GMT`;
document.body.className = themeChoice;
const button = document.getElementById("switchTheme");
button.disabled = true;
@ -999,8 +1001,71 @@ function setRemoveBackground() {
storeSettingsOnDB('remove_background', value);
}
function exportToJson() {
window.location.href = "endpoints/subscriptions/export.php";
function setHideDisabled() {
const hideDisabledCheckbox = document.querySelector("#hidedisabled");
const value = hideDisabledCheckbox.checked;
storeSettingsOnDB('hide_disabled', value);
}
function backupDB() {
const button = document.getElementById("backupDB");
button.disabled = true;
fetch('endpoints/db/backup.php')
.then(response => response.json())
.then(data => {
if (data.success) {
const link = document.createElement('a');
const filename = data.file;
link.href = '.tmp/' + filename;
link.download = 'backup.zip';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
button.disabled = false;
} else {
showErrorMessage(data.errorMessage);
button.disabled = false;
}
})
.catch(error => {
showErrorMessage(error);
button.disabled = false;
});
}
function openRestoreDBFileSelect() {
document.getElementById('restoreDBFile').click();
};
function restoreDB() {
const input = document.getElementById('restoreDBFile');
const file = input.files[0];
if (!file) {
console.error('No file selected');
return;
}
const formData = new FormData();
formData.append('file', file);
fetch('endpoints/db/restore.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message)
window.location.href = 'logout.php';
} else {
showErrorMessage(data.message);
}
})
.catch(error => showErrorMessage('Error:', error));
}
function saveCategorySorting() {
@ -1040,3 +1105,127 @@ var sortable = Sortable.create(el, {
saveCategorySorting();
},
});
function setTheme(themeColor) {
var currentTheme = 'blue';
var themeIds = ['red-theme', 'green-theme', 'yellow-theme'];
themeIds.forEach(function(id) {
var themeStylesheet = document.getElementById(id);
if (themeStylesheet && !themeStylesheet.disabled) {
currentTheme = id.replace('-theme', '');
themeStylesheet.disabled = true;
}
});
if (themeColor !== "blue") {
var enableTheme = document.getElementById(themeColor + '-theme');
enableTheme.disabled = false;
}
var images = document.querySelectorAll('img');
images.forEach(function(img) {
if (img.src.includes('siteicons/' + currentTheme)) {
img.src = img.src.replace(currentTheme, themeColor);
}
});
var labels = document.querySelectorAll('.theme-preview');
labels.forEach(function(label) {
label.classList.remove('is-selected');
});
var targetLabel = document.querySelector(`.theme-preview.${themeColor}`);
if (targetLabel) {
targetLabel.classList.add('is-selected');
}
document.cookie = `colorTheme=${themeColor}; expires=Fri, 31 Dec 9999 23:59:59 GMT`;
fetch('endpoints/settings/colortheme.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ color: themeColor })
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message);
} else {
showErrorMessage(data.message);
}
})
.catch(error => {
showErrorMessage(translate('unknown_error'));
});
}
function resetCustomColors() {
const button = document.getElementById("reset-colors");
button.disabled = true;
fetch('endpoints/settings/resettheme.php', {
method: 'DELETE',
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message);
const custom_theme_colors = document.getElementById('custom_theme_colors');
if (custom_theme_colors) {
custom_theme_colors.remove();
}
document.documentElement.style.removeProperty('--main-color');
document.documentElement.style.removeProperty('--accent-color');
document.documentElement.style.removeProperty('--hover-color');
document.getElementById("mainColor").value = "#FFFFFF";
document.getElementById("accentColor").value = "#FFFFFF";
document.getElementById("hoverColor").value = "#FFFFFF";
} else {
showErrorMessage(data.message);
}
button.disabled = false;
})
.catch(error => {
showErrorMessage(translate('unknown_error'));
button.disabled = false;
});
}
function saveCustomColors() {
const button = document.getElementById("save-colors");
button.disabled = true;
const mainColor = document.getElementById("mainColor").value;
const accentColor = document.getElementById("accentColor").value;
const hoverColor = document.getElementById("hoverColor").value;
fetch('endpoints/settings/customtheme.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ mainColor: mainColor, accentColor: accentColor, hoverColor: hoverColor })
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message);
document.documentElement.style.setProperty('--main-color', mainColor);
document.documentElement.style.setProperty('--accent-color', accentColor);
document.documentElement.style.setProperty('--hover-color', hoverColor);
} else {
showErrorMessage(data.message);
}
button.disabled = false;
})
.catch(error => {
showErrorMessage(translate('unknown_error'));
button.disabled = false;
});
}

View File

@ -14,6 +14,9 @@ self.addEventListener('install', function(event) {
'styles/login.css',
'styles/font-awesome.min.css',
'styles/barlow.css',
'styles/themes/red.css',
'styles/themes/green.css',
'styles/themes/yellow.css',
'webfonts/fa-solid-900.woff2',
'webfonts/fa-solid-900.ttf',
'scripts/common.js',
@ -26,6 +29,7 @@ self.addEventListener('install', function(event) {
'scripts/i18n/el.js',
'scripts/i18n/es.js',
'scripts/i18n/fr.js',
'scripts/i18n/it.js',
'scripts/i18n/jp.js',
'scripts/i18n/pl.js',
'scripts/i18n/pt.js',
@ -39,8 +43,14 @@ self.addEventListener('install', function(event) {
'images/icon/favicon.ico',
'images/icon/android-chrome-192x192.png',
'images/screenshots/desktop.png',
'images/wallossolid.png',
'images/wallossolidwhite.png',
'images/siteicons/blue/wallos.png',
'images/siteicons/blue/walloswhite.png',
'images/siteicons/green/wallos.png',
'images/siteicons/green/walloswhite.png',
'images/siteicons/red/wallos.png',
'images/siteicons/red/walloswhite.png',
'images/siteicons/yellow/wallos.png',
'images/siteicons/yellow/walloswhite.png',
'images/siteimages/empty.png',
'images/avatars/1.svg',
'images/avatars/2.svg',
@ -51,19 +61,46 @@ self.addEventListener('install', function(event) {
'images/avatars/7.svg',
'images/avatars/8.svg',
'images/avatars/9.svg',
'images/siteicons/edit.png',
'images/siteicons/websearch.png',
'images/siteicons/save.png',
'images/siteicons/delete.png',
'images/siteicons/category.png',
'images/siteicons/check.png',
'images/siteicons/editavatar.png',
'images/siteicons/notes.png',
'images/siteicons/payment.png',
'images/siteicons/plusicon.png',
'images/siteicons/sort.png',
'images/siteicons/subscription.png',
'images/siteicons/web.png',
'images/siteicons/blue/category.png',
'images/siteicons/blue/check.png',
'images/siteicons/blue/delete.png',
'images/siteicons/blue/edit.png',
'images/siteicons/blue/notes.png',
'images/siteicons/blue/payment.png',
'images/siteicons/blue/save.png',
'images/siteicons/blue/subscription.png',
'images/siteicons/blue/web.png',
'images/siteicons/blue/websearch.png',
'images/siteicons/red/category.png',
'images/siteicons/red/check.png',
'images/siteicons/red/delete.png',
'images/siteicons/red/edit.png',
'images/siteicons/red/notes.png',
'images/siteicons/red/payment.png',
'images/siteicons/red/save.png',
'images/siteicons/red/subscription.png',
'images/siteicons/red/web.png',
'images/siteicons/red/websearch.png',
'images/siteicons/green/category.png',
'images/siteicons/green/check.png',
'images/siteicons/green/delete.png',
'images/siteicons/green/edit.png',
'images/siteicons/green/notes.png',
'images/siteicons/green/payment.png',
'images/siteicons/green/save.png',
'images/siteicons/green/subscription.png',
'images/siteicons/green/web.png',
'images/siteicons/green/websearch.png',
'images/siteicons/yellow/category.png',
'images/siteicons/yellow/check.png',
'images/siteicons/yellow/delete.png',
'images/siteicons/yellow/edit.png',
'images/siteicons/yellow/notes.png',
'images/siteicons/yellow/payment.png',
'images/siteicons/yellow/save.png',
'images/siteicons/yellow/subscription.png',
'images/siteicons/yellow/web.png',
'images/siteicons/yellow/websearch.png',
'images/siteicons/pwa/stats.png',
'images/siteicons/pwa/settings.png',
'images/siteicons/pwa/about.png',

View File

@ -104,7 +104,7 @@
</div>
</div>
<div class="buttons">
<input type="submit" value="<?= translate('save', $i18n) ?>" id="userSubmit"/>
<input type="submit" value="<?= translate('save', $i18n) ?>" id="userSubmit" class="thin"/>
</div>
</div>
</form>
@ -142,19 +142,19 @@
}
?>
<button class="image-button medium" onClick="editMember(<?= $member['id'] ?>)" name="save">
<img src="images/siteicons/save.png" title="<?= translate('save_member', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/save.png" title="<?= translate('save_member', $i18n) ?>">
</button>
<?php
if ($member['id'] != 1) {
?>
<button class="image-button medium" onClick="removeMember(<?= $member['id'] ?>)">
<img src="images/siteicons/delete.png" title="<?= translate('delete_member', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/delete.png" title="<?= translate('delete_member', $i18n) ?>">
</button>
<?php
} else {
?>
<button class="image-button medium disabled">
<img src="images/siteicons/delete.png" title="<?= translate('cant_delete_member', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/delete.png" title="<?= translate('cant_delete_member', $i18n) ?>">
</button>
<?php
}
@ -170,7 +170,7 @@
<p>
</div>
<div class="buttons">
<input type="submit" value="<?= translate('add', $i18n) ?>" id="addMember" onClick="addMemberButton()"/>
<input type="submit" value="<?= translate('add', $i18n) ?>" id="addMember" onClick="addMemberButton()" class="thin"/>
</div>
</div>
</section>
@ -248,8 +248,8 @@
<p>
</div>
<div class="buttons">
<input type="button" class="secondary-button" value="<?= translate('test', $i18n) ?>" id="testNotifications" onClick="testNotificationButton()"/>
<input type="submit" value="<?= translate('save', $i18n) ?>" id="saveNotifications" onClick="saveNotificationsButton()"/>
<input type="button" class="secondary-button thin" value="<?= translate('test', $i18n) ?>" id="testNotifications" onClick="testNotificationButton()"/>
<input type="submit" value="<?= translate('save', $i18n) ?>" id="saveNotifications" onClick="saveNotificationsButton()" class="thin"/>
</div>
</div>
</section>
@ -292,19 +292,19 @@
<div class="drag-icon"></div>
<input type="text" name="category" value="<?= $category['name'] ?>" placeholder="Category">
<button class="image-button medium" onClick="editCategory(<?= $category['id'] ?>)" name="save">
<img src="images/siteicons/save.png" title="<?= translate('save_category', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/save.png" title="<?= translate('save_category', $i18n) ?>">
</button>
<?php
if ($canDelete) {
?>
<button class="image-button medium" onClick="removeCategory(<?= $category['id'] ?>)">
<img src="images/siteicons/delete.png" title="<?= translate('delete_category', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/delete.png" title="<?= translate('delete_category', $i18n) ?>">
</button>
<?php
} else {
?>
<button class="image-button medium disabled">
<img src="images/siteicons/delete.png" title="<?= translate('cant_delete_category_in_use', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/delete.png" title="<?= translate('cant_delete_category_in_use', $i18n) ?>">
</button>
<?php
}
@ -316,7 +316,7 @@
?>
</div>
<div class="buttons">
<input type="submit" value="<?= translate('add', $i18n) ?>" id="addCategory" onClick="addCategoryButton()"/>
<input type="submit" value="<?= translate('add', $i18n) ?>" id="addCategory" onClick="addCategoryButton()" class="thin"/>
</div>
</div>
</section>
@ -375,20 +375,20 @@
<input type="text" name="currency" value="<?= $currency['name'] ?>" placeholder="Currency Name">
<input type="text" name="code" value="<?= $currency['code'] ?>" placeholder="Currency Code" <?= !$canDelete ? 'disabled' : '' ?>>
<button class="image-button medium" onClick="editCurrency(<?= $currency['id'] ?>)" name="save">
<img src="images/siteicons/save.png" title="<?= translate('save_currency', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/save.png" title="<?= translate('save_currency', $i18n) ?>">
</button>
<?php
if ($canDelete) {
?>
<button class="image-button medium" onClick="removeCurrency(<?= $currency['id'] ?>)">
<img src="images/siteicons/delete.png" title="<?= translate('delete_currency', $i18n) ?>">
<img src="images/siteicons/<?= $colorTheme ?>/delete.png" title="<?= translate('delete_currency', $i18n) ?>">
</button>
<?php
} else {
$cantDeleteMessage = $isMainCurrency ? translate('cant_delete_main_currency', $i18n) : translate('cant_delete_currency_in_use', $i18n);
?>
<button class="image-button medium disabled">
<img src="images/siteicons/delete.png" title="<?= $cantDeleteMessage ?>">
<img src="images/siteicons/<?= $colorTheme ?>/delete.png" title="<?= $cantDeleteMessage ?>">
</button>
<?php
}
@ -400,7 +400,7 @@
?>
</div>
<div class="buttons">
<input type="submit" value="<?= translate('add', $i18n) ?>" id="addCurrency" onClick="addCurrencyButton()"/>
<input type="submit" value="<?= translate('add', $i18n) ?>" id="addCurrency" onClick="addCurrencyButton()" class="thin"/>
</div>
<div class="settings-notes">
<p>
@ -478,7 +478,7 @@
</p>
</div>
<div class="buttons">
<input type="submit" value="<?= translate('save', $i18n) ?>" id="addFixerKey" onClick="addFixerKeyButton()"/>
<input type="submit" value="<?= translate('save', $i18n) ?>" id="addFixerKey" onClick="addFixerKeyButton()" class="thin"/>
</div>
</div>
</section>
@ -555,7 +555,7 @@
<input type="file" id="paymenticon" name="paymenticon" accept="image/jpeg, image/png, image/gif, image/webp" onchange="handleFileSelect(event)" class="hidden-input">
<input type="hidden" id="icon-url" name="icon-url">
<div id="icon-search-button" class="image-button medium disabled" title="<?= translate('search_logo', $i18n) ?>" onClick="searchPaymentIcon()">
<img src="images/siteicons/websearch.png">
<img src="images/siteicons/<?= $colorTheme ?>/websearch.png">
</div>
<div id="icon-search-results" class="icon-search">
<header>
@ -571,14 +571,73 @@
</div>
</section>
<section class="account-section">
<header>
<h2><?= translate('theme_settings', $i18n) ?></h2>
</header>
<div class="account-settings-theme">
<div>
<div class="theme-selector">
<div class="theme">
<label for="theme-blue" class="theme-preview blue <?= $settings['color_theme'] == 'blue' ? 'is-selected' : '' ?>">
<input type="radio" name="theme" id="theme-blue" value="blue" onClick="setTheme('blue')" <?= $settings['color_theme'] == 'blue' ? 'checked' : '' ?>>
<span class="main-color"></span>
<span class="accent-color"></span>
<span class="hover-color"></span>
</label>
</div>
<div class="theme">
<label for="theme-green" class="theme-preview green <?= $settings['color_theme'] == 'green' ? 'is-selected' : '' ?>">
<input type="radio" name="theme" id="theme-green" value="green" onClick="setTheme('green')" <?= $settings['color_theme'] == 'green' ? 'checked' : '' ?>>
<span class="main-color"></span>
<span class="accent-color"></span>
<span class="hover-color"></span>
</label>
</div>
<div class="theme">
<label for="theme-red" class="theme-preview red <?= $settings['color_theme'] == 'red' ? 'is-selected' : '' ?>">
<input type="radio" name="theme" id="theme-red" value="red" onClick="setTheme('red')" <?= $settings['color_theme'] == 'red' ? 'checked' : '' ?>>
<span class="main-color"></span>
<span class="accent-color"></span>
<span class="hover-color"></span>
</label>
</div>
<div class="theme">
<label for="theme-yellow" class="theme-preview yellow <?= $settings['color_theme'] == 'yellow' ? 'is-selected' : '' ?>">
<input type="radio" name="theme" id="theme-yellow" value="yellow" onClick="setTheme('yellow')" <?= $settings['color_theme'] == 'yellow' ? 'checked' : '' ?>>
<span class="main-color"></span>
<span class="accent-color"></span>
<span class="hover-color"></span>
</label>
</div>
</div>
</div>
<div>
<h2><?= translate('custom_colors', $i18n) ?></h2>
<div class="form-group-inline wrap">
<div class="color-picker-wrapper wrap">
<input type="color" id="mainColor" name="mainColor" value="<?= isset($settings['customColors']['main_color']) ? $settings['customColors']['main_color'] : '#FFFFFF' ?>" class="color-picker fa-solid fa-eye-dropper">
<input type="color" id="accentColor" name="accentColor" value="<?= isset($settings['customColors']['accent_color']) ? $settings['customColors']['accent_color'] : '#FFFFFF' ?>" class="color-picker fa-solid fa-eye-dropper">
<input type="color" id="hoverColor" name="hoverColor" value="<?= isset($settings['customColors']['hover_color']) ? $settings['customColors']['hover_color'] : '#FFFFFF' ?>" class="color-picker fa-solid fa-eye-dropper">
</div>
<div class="color-picker-wrapper wrap">
<input type="button" value="<?= translate('reset', $i18n) ?>" onClick="resetCustomColors()" class="secondary-button thin" id="reset-colors">
<input type="button" value="<?= translate('save', $i18n) ?>" onClick="saveCustomColors()" class="buton thin" id="save-colors">
</div>
</div>
</div>
<h2><?= translate('dark_theme', $i18n) ?></h2>
<div>
<input id="switchTheme" type="button" value="<?= translate('switch_theme', $i18n) ?>" onClick="switchTheme()" class="button thin">
</div>
</div>
</section>
<section class="account-section">
<header>
<h2><?= translate('display_settings', $i18n) ?></h2>
</header>
<div class="account-settings-list">
<div>
<input id="switchTheme" type="button" value="<?= translate('switch_theme', $i18n) ?>" onClick="switchTheme()">
</div>
<div>
<div class="form-group-inline">
<input type="checkbox" id="monthlyprice" name="monthlyprice" onChange="setShowMonthlyPrice()" <?php if ($settings['monthly_price']) echo 'checked'; ?>>
@ -591,6 +650,12 @@
<label for="convertcurrency"><?= translate('convert_prices', $i18n) ?></label>
</div>
</div>
<div>
<div class="form-group-inline">
<input type="checkbox" id="hidedisabled" name="hidedisabled" onChange="setHideDisabled()" <?php if ($settings['hide_disabled']) echo 'checked'; ?>>
<label for="hidedisabled"><?= translate('hide_disabled_subscriptions', $i18n) ?></label>
</div>
</div>
</div>
</section>
@ -616,11 +681,23 @@
<section class="account-section">
<header>
<h2><?= translate('export_subscriptions', $i18n) ?></h2>
<h2><?= translate('backup_and_restore', $i18n) ?></h2>
</header>
<div>
<input type="button" class="button" value="<?= translate('export_to_json', $i18n) ?>" id="exportToJson" onClick="exportToJson()"/>
<div>
<div class="form-group-inline">
<div>
<input type="button" class="button thin" value="<?= translate('backup', $i18n) ?>" id="backupDB" onClick="backupDB()"/>
</div>
<div>
<input type="button" class="secondary-button thin" value="<?= translate('restore', $i18n) ?>" id="restoreDB" onClick="openRestoreDBFileSelect()" />
<input type="file" name="restoreDBFile" id="restoreDBFile" style="display: none;" onChange="restoreDB()" accept=".zip">
</div>
</div>
<div class="settings-notes">
<p>
<i class="fa-solid fa-circle-info"></i>
<?= translate('restore_info', $i18n) ?>
</p>
</div>
</section>
</section>

View File

@ -8,7 +8,7 @@ body > header {
}
header .logo .logo-image {
background-image: url("../images/wallossolidwhite.png");
background-image: url("../images/siteicons/blue/walloswhite.png");
}
.split-header > h2 .header-subtitle {
@ -96,6 +96,10 @@ input[type="button"].secondary-button:hover {
background-color: #111;
}
input[type="color"] {
background-color: #F2F2F2;
}
.avatar-select .avatar-list .remove-avatar {
background-color: #222;
}
@ -133,7 +137,7 @@ input[type="button"].secondary-button:hover {
}
.logo-preview:after {
color: #007bff;
color: var(--main-color);
}
.sort-options > ul > li {

View File

@ -90,11 +90,12 @@ select {
outline: none;
}
input[type="submit"] {
input[type="submit"],
input[type="button"] {
width: 100%;
padding: 15px;
font-size: 16px;
background-color: #007bff;
background-color: var(--main-color);
color: #fff;
border: none;
border-radius: 8px;
@ -102,7 +103,21 @@ input[type="submit"] {
}
input[type="submit"]:hover {
background-color: #0056b3;
background-color: var(--hover-color);
}
input[type="button"].secondary-button,
button.button.secondary-button {
background-color: #FFFFFF;
color: var(--main-color);
border: 2px solid var(--main-color);
}
input[type="button"].secondary-button:hover,
button.button.secondary-button:hover {
background-color: #EEEEEE;
color: var(--hover-color);
border-color: var(--hover-color);
}
input[type="checkbox"] {
@ -120,6 +135,134 @@ input[type="checkbox"] {
.error {
display: block;
color: red;
color: var(--error-color);
margin-bottom: 20px;
}
.separator {
border-top: 1px solid #ccc;
padding-top: 20px;
}
/* TOAST MESSAGE */
.toast {
position: fixed;
bottom: 25px;
right: 30px;
border-radius: 12px;
border: 1px solid #eeeeee;
background: #fff;
padding: 20px 35px 20px 25px;
box-shadow: 0 6px 20px -5px rgba(0, 0, 0, 0.1);
overflow: hidden;
transform: translateX(calc(100% + 30px));
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.35);
box-sizing: border-box;
}
@media (max-width: 768px) {
.toast {
bottom: 0px;
right: 0px;
left: 0px;
width: 100%;
}
}
.toast.active {
transform: translateX(0%);
}
.toast .toast-content {
display: flex;
align-items: center;
}
.toast-content .toast-icon {
display: flex;
align-items: center;
justify-content: center;
height: 35px;
min-width: 35px;
color: #fff;
font-size: 20px;
border-radius: 50%;
}
.toast-content .toast-icon.error {
background-color: var(--error-color);
}
.toast-content .toast-icon.success {
background-color: var(--success-color);
}
.toast-content .message {
display: flex;
flex-direction: column;
margin: 0 20px;
}
.toast-content .message .text {
font-size: 16px;
font-weight: 400;
color: #666666;
}
.toast-content .message .text.text-1 {
font-weight: 600;
color: #333;
}
.toast .close {
position: absolute;
top: 10px;
right: 15px;
padding: 5px;
cursor: pointer;
opacity: 0.7;
}
.toast .close:hover {
opacity: 1;
}
.toast .progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
width: 100%;
}
.toast .progress:before {
content: "";
position: absolute;
bottom: 0;
right: 0;
height: 100%;
width: 100%;
}
.toast .progress.error:before {
background-color: var(--error-color);
}
.toast .progress.success:before {
background-color: var(--success-color);
}
.progress.active:before {
animation: progress 5s linear forwards;
}
@keyframes progress {
100% {
right: 100%;
}
}
/* TOAST END */

View File

@ -52,7 +52,7 @@ h2, h3 {
}
body > header {
border-bottom: 7px solid #007bff;
border-bottom: 7px solid var(--main-color);
background-color: white;
}
@ -67,7 +67,7 @@ header .logo .logo-image {
height: 50px;
width: 134px;
margin-right: 10px;
background-image: url("../images/wallossolid.png");
background-image: url("../images/siteicons/blue/wallos.png");
background-size: 100%;
}
@ -193,8 +193,8 @@ main > .contain {
cursor: pointer;
user-select: none;
color: #fff;
border: 1px solid #007bff;
background-color: #007bff;
border: 1px solid var(--main-color);
background-color: var(--main-color);
padding: 15px 30px;
font-size: 1rem;
border-radius: 8px;
@ -203,8 +203,8 @@ main > .contain {
}
.button:hover {
background-color: #0056b3;
border-color: #0056b3;
background-color: var(--hover-color);
border-color: var(--hover-color);
}
.button.thin {
@ -229,7 +229,7 @@ main > .contain {
margin-top: -35px;
position: relative;
z-index: 2;
color: #007bff;
color: var(--main-color);
font-size: 20px;
}
@ -603,17 +603,17 @@ header #avatar {
justify-content: center;
width: 60px;
height: 60px;
border: 1px solid #007bff;
border: 1px solid var(--main-color);
border-radius: 50%;
cursor: pointer;
margin: 0px;
box-sizing: border-box;
color: #007bff;
color: var(--main-color);
}
.avatar-select label.add-avatar:hover {
border-color: #8FBFFA;
color: #8FBFFA;
border-color: var(--accent-color);
color: var(--accent-color);
}
.avatar-select .avatar-list .remove-avatar {
@ -667,7 +667,7 @@ header #avatar {
}
.image-button > i {
color: #0056b3;
color: var(--hover-color);
font-size: 28px;
padding: 2px;
}
@ -709,7 +709,7 @@ header #avatar {
flex-direction: row;
align-items: center;
gap: 8px;
background-color: #8FBFFA;
background-color: var(--accent-color);
padding: 6px 12px;
border-radius: 8px;
transition: filter 300ms;
@ -766,17 +766,17 @@ header #avatar {
.settings-notes > p > span > a {
margin-left: 5px;
font-size: 13px;
color: #8FBFFA;
color: var(--accent-color);
}
.credits-list > p > span > a:visited,
.settings-notes > p > span > a:visited {
color: #8FBFFA;
color: var(--accent-color);
}
.settings-notes > p > i,
.account-section .notes > p > i {
color: #007bff;
color: var(--main-color);
margin-right: 5px;
}
@ -844,6 +844,19 @@ select {
box-sizing: border-box;
}
input[type="color"] {
height: 46px;
width: 46px;
background-color: #222;
border: 1px solid var(--hover-color);
outline: none;
box-sizing: border-box;
cursor: pointer;
font-size: 16px;
position: relative;
border-radius: 5px;
}
.one-third {
max-width: 33%;
}
@ -872,28 +885,34 @@ input[type="text"].short {
input[type="submit"],
input[type="button"],
button.button {
padding: 12px 30px;
padding: 15px 30px;
font-size: 16px;
background-color: #007bff;
background-color: var(--main-color);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
box-sizing: border-box;
border: 2px solid #007bff;
border: 2px solid var(--main-color);
}
input[type="submit"].thin,
input[type="button"].thin,
button.button.thin {
padding: 13px 30px;
}
input[type="button"].secondary-button,
button.button.secondary-button {
background-color: #FFFFFF;
color: #007bff;
color: var(--main-color);
}
input[type="button"].secondary-button:hover,
button.button.secondary-button:hover {
background-color: #EEEEEE;
color: #0056b3;
border-color: #0056b3;
color: var(--hover-color);
border-color: var(--hover-color);
}
input[type="button"].warning-button {
@ -907,8 +926,8 @@ input[type="button"].warning-button:hover {
}
input[type="submit"]:hover {
background-color: #0056b3;
border-color: #0056b3;
background-color: var(--hover-color);
border-color: var(--hover-color);
}
input[type="submit"]:disabled,
@ -1113,7 +1132,7 @@ input[type="text"]:disabled {
justify-content: center;
align-items: center;
height: 100%;
color: #0056b3;
color: var(--hover-color);
font-size: 16px;
text-align: center;
}
@ -1133,6 +1152,7 @@ input[type="text"]:disabled {
display: block;
cursor: pointer;
overflow: hidden;
flex-shrink: 0;
}
.icon-preview:after {
@ -1141,7 +1161,7 @@ input[type="text"]:disabled {
justify-content: center;
align-items: center;
height: 100%;
color: #0056b3;
color: var(--hover-color);
font-size: 16px;
text-align: center;
}
@ -1281,11 +1301,11 @@ input[type="text"]:disabled {
}
.toast-content .toast-icon.error {
background-color: #f45a40;
background-color: var(--error-color);
}
.toast-content .toast-icon.success {
background-color: #188823;
background-color: var(--success-color);
}
@ -1338,11 +1358,11 @@ input[type="text"]:disabled {
}
.toast .progress.error:before {
background-color: #f45a40;
background-color: var(--error-color);
}
.toast .progress.success:before {
background-color: #188823;
background-color: var(--success-color);
}
.progress.active:before {
@ -1401,7 +1421,7 @@ input[type="text"]:disabled {
.statistic > span {
font-size: 42px;
color: #0056b3;
color: var(--hover-color);
}
.statistic > .title {
@ -1411,7 +1431,7 @@ input[type="text"]:disabled {
.statistic > .subtitle {
font-size: 25px;
color: #8FBFFA;
color: var(--accent-color);
margin-top: 10px;
text-align: center;
}
@ -1486,8 +1506,8 @@ input[type="text"]:disabled {
.sortable-list .sortable-ghost {
border-radius: 16px;
background-color: #c1d9f7;
border: 1px solid #8FBFFA;
background-color: rgba(var(--accent-color-rgb), 0.6);
border: 1px solid var(--accent-color);
}
/* Fitler dropdown */
@ -1567,7 +1587,7 @@ input[type="text"]:disabled {
}
.filtermenu-content .filter-title.filter-clear {
color: #0056b3;
color: var(--hover-color);
font-weight: normal;
border-bottom: none;
}
@ -1588,3 +1608,115 @@ input[type="text"]:disabled {
.filtermenu-submenu-content.is-open {
display: block;
}
/* Theme Selector */
.theme-selector {
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.theme-preview {
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
cursor: pointer;
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px 15px 20px 10px;
}
.theme-preview.is-selected {
border: 1px solid var(--main-color);
}
.theme-preview:hover {
background-color: rgba(var(--accent-color-rgb), 0.6);
}
.theme-preview > .main-color,
.theme-preview > .accent-color,
.theme-preview > .hover-color {
display: inline-block;
width: 25px;
height: 25px;
border: 1px solid #FFF;
}
.theme-preview.blue > .main-color {
background-color: #007bff;
}
.theme-preview.blue > .accent-color {
background-color: #8fbffa;
}
.theme-preview.blue > .hover-color {
background-color: #0056b3;
}
.theme-preview.green > .main-color {
background-color: #6B8E23;
}
.theme-preview.green > .accent-color {
background-color: #9ACD32;
}
.theme-preview.green > .hover-color {
background-color: #556B2F;
}
.theme-preview.red > .main-color {
background-color: #f45a40;
}
.theme-preview.red > .accent-color {
background-color: #f79988;
}
.theme-preview.red > .hover-color {
background-color: #c73f29;
}
.theme-preview.yellow > .main-color {
background-color: #ffae00;
}
.theme-preview.yellow > .accent-color {
background-color: #faea8f;
}
.theme-preview.yellow > .hover-color {
background-color: #cd930c;
}
.color-picker-wrapper {
display: flex;
flex-direction: row;
gap: 16px;
}
.color-picker {
flex-shrink: 0;
}
.color-picker::before {
color: var(--hover-color);
position: absolute;
top: -5px;
right: -5px;
border: 1px solid;
border-radius: 15px;
background-color: white;
padding: 4px;
}
.wrap {
flex-wrap: wrap;
}

Some files were not shown because too many files have changed in this diff Show More