Remove a bunch of trailing whitespace.
This commit is contained in:
		
							parent
							
								
									93bfe14d4f
								
							
						
					
					
						commit
						a3a6471ced
					
				| @ -39,7 +39,7 @@ RewriteCond %{REQUEST_URI} !^/css/custom/ | ||||
| RewriteRule ^css/tkr\.css$ public/css/tkr.css [L] | ||||
| 
 | ||||
| # 404 all other static files (images, js, fonts, etc.) | ||||
| # so those requests don't hit the PHP app  | ||||
| # so those requests don't hit the PHP app | ||||
| # (this is to reduce load on the PHP app from bots and scanners) | ||||
| RewriteRule \.(js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|mp3|mp4|avi|mov)$ - [R=404,L] | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,7 @@ A lightweight, HTML-only status feed for self-hosted personal websites. Written | ||||
| * PHP 8.2+ with the PDO and PDO_SQLITE extensions | ||||
|     * The PDO and PDO_SQLITE extensions are usually included by default | ||||
|     * This might work with earlier PHP versions, but I've only tested 8.2 | ||||
|      | ||||
| 
 | ||||
| ## Installation | ||||
| 
 | ||||
| 1. Download the latest tkr archive from https://subcultureofone.org/files/tkr/tkr.0.6.0.zip | ||||
|  | ||||
| @ -18,12 +18,12 @@ define('DB_FILE', DATA_DIR . '/tkr.sqlite'); | ||||
| // Define an exception for validation errors
 | ||||
| class SetupException extends Exception { | ||||
|     private $setupIssue; | ||||
|      | ||||
| 
 | ||||
|     public function __construct(string $message, string $setupIssue = '', int $code = 0, Throwable $previous = null) { | ||||
|         parent::__construct($message, $code, $previous); | ||||
|         $this->setupIssue = $setupIssue; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     public function getSetupIssue(): string { | ||||
|         return $this->setupIssue; | ||||
|     } | ||||
|  | ||||
| @ -39,7 +39,7 @@ RewriteCond %{REQUEST_URI} !^/css/custom/ | ||||
| RewriteRule ^css/tkr\.css$ public/css/tkr.css [L] | ||||
| 
 | ||||
| # 404 all other static files (images, js, fonts, etc.) | ||||
| # so those requests don't hit the PHP app  | ||||
| # so those requests don't hit the PHP app | ||||
| # (this is to reduce load on the PHP app from bots and scanners) | ||||
| RewriteRule \.(js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|mp3|mp4|avi|mov)$ - [R=404,L] | ||||
| 
 | ||||
|  | ||||
| @ -9,13 +9,13 @@ | ||||
|     # Replace localhost with your subdomain, e.g. tkr.my-domain.com | ||||
|     ServerName localhost | ||||
|     DocumentRoot /var/www/tkr/public | ||||
|   | ||||
| 
 | ||||
|     # Security headers | ||||
|     Header always set X-Frame-Options "SAMEORIGIN" | ||||
|     Header always set X-XSS-Protection "1; mode=block" | ||||
|     Header always set X-Content-Type-Options "nosniff" | ||||
|     Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" | ||||
|   | ||||
| 
 | ||||
|     # Block access to sensitive directories | ||||
|     <Directory "/var/www/tkr/storage"> | ||||
|         Require all denied | ||||
| @ -44,7 +44,7 @@ | ||||
|     Alias /css/tkr.css /var/www/tkr/public/css/tkr.css | ||||
| 
 | ||||
|     # 404 all non-css static files (images, js, fonts, etc.) | ||||
|     # so those requests don't hit the PHP app  | ||||
|     # so those requests don't hit the PHP app | ||||
|     # (this is to reduce load on the PHP app from bots and scanners) | ||||
|     <LocationMatch "\.(js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|mp3|mp4|avi|mov)$"> | ||||
|         <RequireAll> | ||||
| @ -69,7 +69,7 @@ | ||||
|         RewriteCond %{REQUEST_URI} !^/css/custom/ | ||||
|         RewriteRule ^css/tkr\.css$ css/tkr.css [L] | ||||
| 
 | ||||
|         # Everything else to front controller   | ||||
|         # Everything else to front controller | ||||
|         RewriteCond %{REQUEST_FILENAME} !-f | ||||
|         RewriteCond %{REQUEST_FILENAME} !-d | ||||
|         RewriteRule ^(.*)$ index.php [L] | ||||
|  | ||||
| @ -24,13 +24,13 @@ | ||||
|     # Replace with the actual paths to your cert and key | ||||
|     SSLCertificateFile /etc/letsencrypt/live/tkr.my-domain.com/fullchain.pem | ||||
|     SSLCertificateKeyFile /etc/letsencrypt/live/tkr.my-domain.com/privkey.pem | ||||
|   | ||||
| 
 | ||||
|     # Security headers | ||||
|     Header always set X-Frame-Options "SAMEORIGIN" | ||||
|     Header always set X-XSS-Protection "1; mode=block" | ||||
|     Header always set X-Content-Type-Options "nosniff" | ||||
|     Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" | ||||
|   | ||||
| 
 | ||||
|     # Block access to sensitive directories | ||||
|     <Directory "/var/www/tkr/storage"> | ||||
|         Require all denied | ||||
| @ -59,7 +59,7 @@ | ||||
|     Alias /css/tkr.css /var/www/tkr/public/css/tkr.css | ||||
| 
 | ||||
|     # 404 all non-css static files (images, js, fonts, etc.) | ||||
|     # so those requests don't hit the PHP app  | ||||
|     # so those requests don't hit the PHP app | ||||
|     # (this is to reduce load on the PHP app from bots and scanners) | ||||
|     <LocationMatch "\.(js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|mp3|mp4|avi|mov)$"> | ||||
|         <RequireAll> | ||||
| @ -84,7 +84,7 @@ | ||||
|         RewriteCond %{REQUEST_URI} !^/css/custom/ | ||||
|         RewriteRule ^css/tkr\.css$ css/tkr.css [L] | ||||
| 
 | ||||
|         # Everything else to front controller   | ||||
|         # Everything else to front controller | ||||
|         RewriteCond %{REQUEST_FILENAME} !-f | ||||
|         RewriteCond %{REQUEST_FILENAME} !-d | ||||
|         RewriteRule ^(.*)$ index.php [L] | ||||
|  | ||||
| @ -9,18 +9,18 @@ | ||||
|     # Replace localhost with your subdomain, e.g. tkr.my-domain.com | ||||
|     ServerName localhost | ||||
|     DocumentRoot /var/www/html | ||||
|      | ||||
| 
 | ||||
|     # Security headers | ||||
|     Header always set X-Frame-Options "SAMEORIGIN" | ||||
|     Header always set X-XSS-Protection "1; mode=block" | ||||
|     Header always set X-Content-Type-Options "nosniff" | ||||
|     Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" | ||||
|      | ||||
| 
 | ||||
|     # tkr Application at /tkr | ||||
|     # NOTE: If you change the directory name, | ||||
|     # remember to update all instances of /var/www/tkr in this file to match  | ||||
|     # remember to update all instances of /var/www/tkr in this file to match | ||||
|     Alias /tkr /var/www/tkr/public | ||||
|      | ||||
| 
 | ||||
|     # Block access to sensitive TKR directories | ||||
|     <Directory "/var/www/tkr/storage"> | ||||
|         Require all denied | ||||
| @ -34,28 +34,28 @@ | ||||
|     <Directory "/var/www/tkr/config"> | ||||
|         Require all denied | ||||
|     </Directory> | ||||
|      | ||||
| 
 | ||||
|     # 404 all non-css static files in /tkr (images, js, fonts, etc.) | ||||
|     # so those requests don't hit the PHP app  | ||||
|     # so those requests don't hit the PHP app | ||||
|     # (this is to reduce load on the PHP app from bots and scanners) | ||||
|     <LocationMatch "^/tkr/.*\.(js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|mp3|mp4|avi|mov)$"> | ||||
|         <RequireAll> | ||||
|             Require all denied | ||||
|         </RequireAll> | ||||
|     </LocationMatch> | ||||
|      | ||||
| 
 | ||||
|     # tkr application directory | ||||
|     <Directory "/var/www/tkr/public"> | ||||
|         Options -Indexes | ||||
|         AllowOverride None | ||||
|         Require all granted | ||||
|          | ||||
| 
 | ||||
|         RewriteEngine On | ||||
|          | ||||
| 
 | ||||
|         # Block direct PHP access | ||||
|         RewriteCond %{THE_REQUEST} \s/[^?\s]*\.php[\s?] [NC] | ||||
|         RewriteRule ^.*$ - [R=404,L] | ||||
|          | ||||
| 
 | ||||
|         # Serve the one static file that exists: css/tkr.css | ||||
|         # (Pass requests to css/custom/ through to the PHP app) | ||||
|         RewriteCond %{REQUEST_URI} !^/tkr/css/custom/ | ||||
| @ -66,7 +66,7 @@ | ||||
|         RewriteCond %{REQUEST_FILENAME} !-d | ||||
|         RewriteRule ^(.*)$ index.php [L] | ||||
|     </Directory> | ||||
|      | ||||
| 
 | ||||
|     # Error and access logs | ||||
|     ErrorLog ${APACHE_LOG_DIR}/my-domain_error.log | ||||
|     CustomLog ${APACHE_LOG_DIR}/my-domain_access.log combined | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
|     # Replace localhost with your subdomain, e.g. tkr.my-domain.com | ||||
|     ServerName localhost | ||||
|     DocumentRoot /var/www/html | ||||
|      | ||||
| 
 | ||||
|     # SSL Configuration | ||||
|     SSLEngine on | ||||
| 
 | ||||
| @ -25,18 +25,18 @@ | ||||
|     # Replace with the actual paths to your cert and key | ||||
|     SSLCertificateFile /etc/letsencrypt/live/my-domain.com/fullchain.pem | ||||
|     SSLCertificateKeyFile /etc/letsencrypt/live/my-domain.com/privkey.pem | ||||
|      | ||||
| 
 | ||||
|     # Security headers | ||||
|     Header always set X-Frame-Options "SAMEORIGIN" | ||||
|     Header always set X-XSS-Protection "1; mode=block" | ||||
|     Header always set X-Content-Type-Options "nosniff" | ||||
|     Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" | ||||
|      | ||||
| 
 | ||||
|     # tkr Application at /tkr | ||||
|     # NOTE: If you change the directory name, | ||||
|     # remember to update all instances of /var/www/tkr in this file to match  | ||||
|     # remember to update all instances of /var/www/tkr in this file to match | ||||
|     Alias /tkr /var/www/tkr/public | ||||
|      | ||||
| 
 | ||||
|     # Block access to sensitive TKR directories | ||||
|     <Directory "/var/www/tkr/storage"> | ||||
|         Require all denied | ||||
| @ -50,28 +50,28 @@ | ||||
|     <Directory "/var/www/tkr/config"> | ||||
|         Require all denied | ||||
|     </Directory> | ||||
|      | ||||
| 
 | ||||
|     # 404 all non-css static files in /tkr (images, js, fonts, etc.) | ||||
|     # so those requests don't hit the PHP app  | ||||
|     # so those requests don't hit the PHP app | ||||
|     # (this is to reduce load on the PHP app from bots and scanners) | ||||
|     <LocationMatch "^/tkr/.*\.(js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|mp3|mp4|avi|mov)$"> | ||||
|         <RequireAll> | ||||
|             Require all denied | ||||
|         </RequireAll> | ||||
|     </LocationMatch> | ||||
|      | ||||
| 
 | ||||
|     # tkr application directory | ||||
|     <Directory "/var/www/tkr/public"> | ||||
|         Options -Indexes | ||||
|         AllowOverride None | ||||
|         Require all granted | ||||
|          | ||||
| 
 | ||||
|         RewriteEngine On | ||||
|          | ||||
| 
 | ||||
|         # Block direct PHP access | ||||
|         RewriteCond %{THE_REQUEST} \s/[^?\s]*\.php[\s?] [NC] | ||||
|         RewriteRule ^.*$ - [R=404,L] | ||||
|          | ||||
| 
 | ||||
|         # Serve the one static file that exists: css/tkr.css | ||||
|         # (Pass requests to css/custom/ through to the PHP app) | ||||
|         RewriteCond %{REQUEST_URI} !^/tkr/css/custom/ | ||||
| @ -82,7 +82,7 @@ | ||||
|         RewriteCond %{REQUEST_FILENAME} !-d | ||||
|         RewriteRule ^(.*)$ index.php [L] | ||||
|     </Directory> | ||||
|      | ||||
| 
 | ||||
|     # Error and access logs | ||||
|     ErrorLog ${APACHE_LOG_DIR}/my-domain_error.log | ||||
|     CustomLog ${APACHE_LOG_DIR}/my-domain_access.log combined | ||||
|  | ||||
| @ -10,7 +10,7 @@ server { | ||||
|     # replace localhost with your subdomain | ||||
|     # e.g. tkr.my-domain.com | ||||
|     server_name localhost; | ||||
|      | ||||
| 
 | ||||
|     root /var/www/tkr/public; | ||||
|     index index.php; | ||||
| 
 | ||||
| @ -63,7 +63,7 @@ server { | ||||
|             fastcgi_pass php:9000; | ||||
|             fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|             include fastcgi_params; | ||||
|              | ||||
| 
 | ||||
|             fastcgi_param REQUEST_METHOD $request_method; | ||||
|             fastcgi_param REQUEST_URI $request_uri; | ||||
|             fastcgi_param QUERY_STRING $query_string; | ||||
| @ -73,7 +73,7 @@ server { | ||||
|         # (these are bots and scanners) | ||||
|         location ~ ^/.+\.php$ { | ||||
|             return 404; | ||||
|         }  | ||||
|         } | ||||
| 
 | ||||
|         # forward other requests to the fallback block, | ||||
|         # which sends them to php-fpm for handling | ||||
| @ -88,7 +88,7 @@ server { | ||||
|         fastcgi_pass php:9000; | ||||
|         fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|         include fastcgi_params; | ||||
|          | ||||
| 
 | ||||
|         fastcgi_param REQUEST_METHOD $request_method; | ||||
|         fastcgi_param REQUEST_URI $request_uri; | ||||
|         fastcgi_param QUERY_STRING $query_string; | ||||
|  | ||||
| @ -17,7 +17,7 @@ server { | ||||
|     # Replace with the actual paths to your cert and key | ||||
|     ssl_certificate /etc/letsencrypt/live/tkr.my-domain.com/fullchain.pem; | ||||
|     ssl_certificate_key /etc/letsencrypt/live/tkr.my-domain.com/privkey.pem; | ||||
|      | ||||
| 
 | ||||
|     root /var/www/tkr/public; | ||||
|     index index.php; | ||||
| 
 | ||||
| @ -61,7 +61,7 @@ server { | ||||
|             fastcgi_pass php:9000; | ||||
|             fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|             include fastcgi_params; | ||||
|              | ||||
| 
 | ||||
|             fastcgi_param REQUEST_METHOD $request_method; | ||||
|             fastcgi_param REQUEST_URI $request_uri; | ||||
|             fastcgi_param QUERY_STRING $query_string; | ||||
| @ -71,7 +71,7 @@ server { | ||||
|         # (these are bots and scanners) | ||||
|         location ~ ^/.+\.php$ { | ||||
|             return 404; | ||||
|         }  | ||||
|         } | ||||
| 
 | ||||
|         # forward other requests to the fallback block, | ||||
|         # which sends them to php-fpm for handling | ||||
| @ -86,7 +86,7 @@ server { | ||||
|         fastcgi_pass php:9000; | ||||
|         fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|         include fastcgi_params; | ||||
|          | ||||
| 
 | ||||
|         fastcgi_param REQUEST_METHOD $request_method; | ||||
|         fastcgi_param REQUEST_URI $request_uri; | ||||
|         fastcgi_param QUERY_STRING $query_string; | ||||
|  | ||||
| @ -12,7 +12,7 @@ server { | ||||
|     # replace localhost with your subdomain | ||||
|     # e.g. tkr.my-domain.com | ||||
|     server_name localhost; | ||||
|      | ||||
| 
 | ||||
|     root /var/www/html; | ||||
|     index index.html; | ||||
| 
 | ||||
| @ -65,7 +65,7 @@ server { | ||||
|             fastcgi_pass php:9000; | ||||
|             fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|             include fastcgi_params; | ||||
|              | ||||
| 
 | ||||
|             fastcgi_param REQUEST_METHOD $request_method; | ||||
|             fastcgi_param REQUEST_URI $request_uri; | ||||
|             fastcgi_param QUERY_STRING $query_string; | ||||
| @ -75,7 +75,7 @@ server { | ||||
|         # (these are bots and scanners) | ||||
|         location ~ ^/tkr/.+\.php$ { | ||||
|             return 404; | ||||
|         }  | ||||
|         } | ||||
| 
 | ||||
|         # forward other requests to the fallback block, | ||||
|         # which sends them to php-fpm for handling | ||||
| @ -87,7 +87,7 @@ server { | ||||
|         fastcgi_pass php:9000; | ||||
|         fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|         include fastcgi_params; | ||||
|          | ||||
| 
 | ||||
|         fastcgi_param REQUEST_METHOD $request_method; | ||||
|         fastcgi_param REQUEST_URI $request_uri; | ||||
|         fastcgi_param QUERY_STRING $query_string; | ||||
|  | ||||
| @ -8,11 +8,11 @@ | ||||
| server { | ||||
|     listen 443 ssl; | ||||
|     listen [::]:443 ssl; | ||||
|      | ||||
| 
 | ||||
|     # Replace localhost with your domain | ||||
|     # e.g. my-domain.com | ||||
|     server_name localhost; | ||||
|      | ||||
| 
 | ||||
|     root /var/www/html; | ||||
|     index index.html; | ||||
| 
 | ||||
| @ -56,7 +56,7 @@ server { | ||||
|             fastcgi_pass php:9000; | ||||
|             fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|             include fastcgi_params; | ||||
|              | ||||
| 
 | ||||
|             fastcgi_param REQUEST_METHOD $request_method; | ||||
|             fastcgi_param REQUEST_URI $request_uri; | ||||
|             fastcgi_param QUERY_STRING $query_string; | ||||
| @ -66,7 +66,7 @@ server { | ||||
|         # (these are bots and scanners) | ||||
|         location ~ ^/tkr/.+\.php$ { | ||||
|             return 404; | ||||
|         }  | ||||
|         } | ||||
| 
 | ||||
|         # forward other requests to the fallback block, | ||||
|         # which sends them to php-fpm for handling | ||||
| @ -78,7 +78,7 @@ server { | ||||
|         fastcgi_pass php:9000; | ||||
|         fastcgi_param SCRIPT_FILENAME /var/www/tkr/public/index.php; | ||||
|         include fastcgi_params; | ||||
|          | ||||
| 
 | ||||
|         fastcgi_param REQUEST_METHOD $request_method; | ||||
|         fastcgi_param REQUEST_URI $request_uri; | ||||
|         fastcgi_param QUERY_STRING $query_string; | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|     --color-primary-light: #bae6fd; | ||||
|     --color-primary-lighter: #ebf8ff; | ||||
|     --color-primary-lightest: #f0f9ff; | ||||
|      | ||||
| 
 | ||||
|     /* Text colors */ | ||||
|     --color-text-primary: #374151; | ||||
|     --color-text-secondary: #1e40af; | ||||
| @ -16,25 +16,25 @@ | ||||
|     --color-text-muted: gray; | ||||
|     --color-text-black: black; | ||||
|     --color-text-dark: #333; | ||||
|      | ||||
| 
 | ||||
|     /* Background colors */ | ||||
|     --color-bg-body: whitesmoke; | ||||
|     --color-bg-white: white; | ||||
|     --color-bg-light: #fefefe; | ||||
|     --color-bg-file: #f8fafc; | ||||
|      | ||||
| 
 | ||||
|     /* Border colors */ | ||||
|     --color-border-light: #e5e7eb; | ||||
|     --color-border-medium: #d1d5db; | ||||
|     --color-border-file: #cbd5e0; | ||||
|      | ||||
| 
 | ||||
|     /* State colors */ | ||||
|     --color-required: #dc2626; | ||||
|     --color-hover-light: #dbeafe; | ||||
|     --color-hover-medium: #bfdbfe; | ||||
|     --color-emoji-bg: #ddeeff; | ||||
|     --color-emoji-border: #339; | ||||
|      | ||||
| 
 | ||||
|     /* Shadow colors */ | ||||
|     --shadow-primary: rgba(66, 153, 225, 0.1); | ||||
|     --shadow-primary-strong: rgba(66, 153, 225, 0.3); | ||||
| @ -108,9 +108,9 @@ h1.site-description { | ||||
|     width: auto; | ||||
| } | ||||
| 
 | ||||
| input[type="text"],  | ||||
| input[type="number"],  | ||||
| input[type="password"],  | ||||
| input[type="text"], | ||||
| input[type="number"], | ||||
| input[type="password"], | ||||
| textarea, | ||||
| select { | ||||
|     width: 100%; | ||||
| @ -124,9 +124,9 @@ select { | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| input[type="text"]:focus,  | ||||
| input[type="number"]:focus,  | ||||
| input[type="password"]:focus,  | ||||
| input[type="text"]:focus, | ||||
| input[type="number"]:focus, | ||||
| input[type="password"]:focus, | ||||
| textarea:focus, | ||||
| select:focus { | ||||
|     outline: none; | ||||
| @ -205,14 +205,14 @@ label.description { | ||||
| 
 | ||||
| .navbar { | ||||
|   overflow: visible; | ||||
|   background: var(--color-bg-white);  | ||||
|   background: var(--color-bg-white); | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
| 
 | ||||
| .navbar a { | ||||
|   font-size: 16px; | ||||
|   color: var(--color-text-secondary);  | ||||
|   color: var(--color-text-secondary); | ||||
|   text-align: center; | ||||
|   padding: 14px 16px; | ||||
|   text-decoration: none; | ||||
| @ -229,9 +229,9 @@ label.description { | ||||
|     box-shadow: 0 0 0 2px var(--shadow-primary); | ||||
| } | ||||
| 
 | ||||
| /*  | ||||
| /* | ||||
|    Using details/summary tags to build dropdowm nmenus. | ||||
|    They have to be clicked open and closed, but they allow  | ||||
|    They have to be clicked open and closed, but they allow | ||||
|    me to have semantically appropriate pure HTML dropdowms | ||||
|    that work on mobile devices. | ||||
| */ | ||||
| @ -241,7 +241,7 @@ label.description { | ||||
| } | ||||
| 
 | ||||
| .dropdown summary.dropbtn { | ||||
|   font-size: 16px;   | ||||
|   font-size: 16px; | ||||
|   border: none; | ||||
|   border-radius: 0px; | ||||
|   outline: none; | ||||
| @ -424,13 +424,13 @@ label.description { | ||||
| 
 | ||||
| /* Styling for flash messages */ | ||||
| .flash-messages { | ||||
|     background: var(--color-bg-white);  | ||||
|     background: var(--color-bg-white); | ||||
|     margin-top: 10px; | ||||
|     padding: 15px; | ||||
|     border-radius: 8px; | ||||
|     box-shadow: 0 2px 10px var(--shadow-primary); | ||||
| } | ||||
|          | ||||
| 
 | ||||
| .flash-message { | ||||
|     padding: 12px 16px; | ||||
|     margin: 5px 0; | ||||
| @ -462,7 +462,7 @@ label.description { | ||||
|     border-left-color: var(--color-flash-info-border-left); | ||||
|     color: var(--color-flash-info); | ||||
| } | ||||
|          | ||||
| 
 | ||||
| .fieldset-items { | ||||
|     margin-bottom: 14px; | ||||
|     display: grid; | ||||
| @ -532,7 +532,7 @@ label.description { | ||||
| } | ||||
| 
 | ||||
| .tick-time { | ||||
|     color: var(--color-text-muted);  | ||||
|     color: var(--color-text-muted); | ||||
|     font-size: 0.8em; | ||||
|     margin-bottom: 0.4em; | ||||
| } | ||||
| @ -543,9 +543,9 @@ label.description { | ||||
|     display: block; | ||||
| } | ||||
| 
 | ||||
| .tick-pagination a {  | ||||
|     margin: 0 5px;  | ||||
|     text-decoration: none;  | ||||
| .tick-pagination a { | ||||
|     margin: 0 5px; | ||||
|     text-decoration: none; | ||||
| } | ||||
| 
 | ||||
| .emoji-option input { | ||||
| @ -616,7 +616,7 @@ label.description { | ||||
|     box-shadow: 0 0 0 2px var(--shadow-primary); | ||||
| } | ||||
| 
 | ||||
| /*  | ||||
| /* | ||||
|     Responsive layout - adjusts from 1 to 2 columns based on screen width | ||||
|         - min-width makes the mobile (stacked) view the default | ||||
|         - 600px covers most mobile devices in portrait mode | ||||
| @ -633,7 +633,7 @@ label.description { | ||||
|         padding-top: 10px; | ||||
|         margin-bottom: 0; | ||||
|     } | ||||
|              | ||||
| 
 | ||||
|     .home-container { | ||||
|         grid-template-columns: 1fr 2fr; | ||||
|         grid-gap: 2em; | ||||
| @ -646,7 +646,7 @@ label.description { | ||||
|         gap: 16px; | ||||
|         align-items: start; | ||||
|     } | ||||
|             | ||||
| 
 | ||||
|     .file-info { | ||||
|         grid-column: 2; | ||||
|     } | ||||
| @ -654,7 +654,7 @@ label.description { | ||||
|     .navbar { | ||||
|         flex-wrap: wrap; | ||||
|     } | ||||
|              | ||||
| 
 | ||||
|     .dropdown-menu { | ||||
|         position: fixed; | ||||
|         left: 1em; | ||||
|  | ||||
| @ -51,13 +51,13 @@ class AdminController extends Controller { | ||||
|         // handle form submission
 | ||||
|         if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||
|             $errors = []; | ||||
|          | ||||
| 
 | ||||
|             // UserModel profile
 | ||||
|             $username        = trim($_POST['username'] ?? ''); | ||||
|             $displayName     = trim($_POST['display_name'] ?? ''); | ||||
|             $about           = trim($_POST['about'] ?? ''); | ||||
|             $website         = trim($_POST['website'] ?? ''); | ||||
|          | ||||
| 
 | ||||
|             // Site settings
 | ||||
|             $siteTitle       = trim($_POST['site_title']) ?? ''; | ||||
|             $siteDescription = trim($_POST['site_description']) ?? ''; | ||||
| @ -68,7 +68,7 @@ class AdminController extends Controller { | ||||
|             // Password
 | ||||
|             $password                = $_POST['password'] ?? ''; | ||||
|             $confirmPassword         = $_POST['confirm_password'] ?? ''; | ||||
|          | ||||
| 
 | ||||
|             // Validate user profile
 | ||||
|             if (!$username) { | ||||
|                 $errors[] = "Username is required."; | ||||
| @ -87,7 +87,7 @@ class AdminController extends Controller { | ||||
|                     $errors[] = "URL must start with http:// or https://."; | ||||
|                 } | ||||
|             } | ||||
|          | ||||
| 
 | ||||
|             // Validate site settings
 | ||||
|             if (!$siteTitle) { | ||||
|                 $errors[] = "Site title is required."; | ||||
| @ -98,12 +98,12 @@ class AdminController extends Controller { | ||||
|             if ($itemsPerPage < 1 || $itemsPerPage > 50) { | ||||
|                 $errors[] = "Items per page must be a number between 1 and 50."; | ||||
|             } | ||||
|          | ||||
| 
 | ||||
|             // If a password was sent, make sure it matches the confirmation
 | ||||
|             if ($password && !($password === $confirmPassword)){ | ||||
|                 $errors[] = "Passwords do not match"; | ||||
|             } | ||||
|          | ||||
| 
 | ||||
|             // Validation complete
 | ||||
|             if (empty($errors)) { | ||||
|                 // Update site settings
 | ||||
| @ -112,21 +112,21 @@ class AdminController extends Controller { | ||||
|                 $config->baseUrl = $baseUrl; | ||||
|                 $config->basePath = $basePath; | ||||
|                 $config->itemsPerPage = $itemsPerPage; | ||||
|              | ||||
| 
 | ||||
|                 // Save site settings and reload config from database
 | ||||
|                 // TODO - raise and handle exception on failure
 | ||||
|                 $config = $config->save(); | ||||
|              | ||||
| 
 | ||||
|                 // Update user profile
 | ||||
|                 $user->username = $username; | ||||
|                 $user->displayName = $displayName; | ||||
|                 $user->about = $about; | ||||
|                 $user->website = $website; | ||||
|              | ||||
| 
 | ||||
|                 // Save user profile and reload user from database
 | ||||
|                 // TODO - raise and handle exception on failure
 | ||||
|                 $user = $user->save(); | ||||
|              | ||||
| 
 | ||||
|                 // Update the password if one was sent
 | ||||
|                 // TODO - raise and handle exception on failure
 | ||||
|                 if($password){ | ||||
|  | ||||
| @ -19,13 +19,13 @@ class AuthController extends Controller { | ||||
|         if ($_SERVER['REQUEST_METHOD'] === 'POST') { | ||||
|             $username = $_POST['username'] ?? ''; | ||||
|             $password = $_POST['password'] ?? ''; | ||||
|   | ||||
| 
 | ||||
|             // TODO: move into user model
 | ||||
|             global $db; | ||||
|             $stmt = $db->prepare("SELECT id, username, password_hash FROM user WHERE username = ?"); | ||||
|             $stmt->execute([$username]); | ||||
|             $user = $stmt->fetch(); | ||||
|          | ||||
| 
 | ||||
|             if ($user && password_verify($password, $user['password_hash'])) { | ||||
|                 session_regenerate_id(true); | ||||
|                 // TODO: move into session.php
 | ||||
|  | ||||
| @ -24,5 +24,5 @@ class Controller { | ||||
| 
 | ||||
|         extract($vars, EXTR_SKIP); | ||||
|         include $templatePath; | ||||
|     }   | ||||
|     } | ||||
| } | ||||
| @ -76,7 +76,7 @@ class CssController extends Controller { | ||||
|             http_response_code(400); | ||||
|             exit("Cannot delete default theme"); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         // Get the data for the selected CSS file
 | ||||
|         $cssId = $_POST['selectCssFile']; | ||||
|         $cssModel = new CssModel(); | ||||
| @ -87,7 +87,7 @@ class CssController extends Controller { | ||||
|             http_response_code(400); | ||||
|             exit("No entry found for css id $cssId"); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         // get the filename
 | ||||
|         $cssFilename = $cssRow["filename"]; | ||||
| 
 | ||||
| @ -151,7 +151,7 @@ class CssController extends Controller { | ||||
|             // Validate file extension
 | ||||
|             $filename = $file['name']; | ||||
|             $fileExtension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); | ||||
|              | ||||
| 
 | ||||
|             if ($fileExtension !== 'css') { | ||||
|                 throw new Exception('File must have a .css extension'); | ||||
|             } | ||||
| @ -186,7 +186,7 @@ class CssController extends Controller { | ||||
|             // Add upload to database
 | ||||
|             $cssModel = new CssModel(); | ||||
|             $cssModel->save($safeFilename, $description); | ||||
|          | ||||
| 
 | ||||
|             // Set success flash message
 | ||||
|             Session::setFlashMessage('success', 'Theme uploaded as ' . $safeFilename); | ||||
| 
 | ||||
| @ -200,11 +200,11 @@ class CssController extends Controller { | ||||
|     private function validateCssContent($content) { | ||||
|         // Remove comments
 | ||||
|         $content = preg_replace('/\/\*.*?\*\//s', '', $content); | ||||
|          | ||||
| 
 | ||||
|         // Basic CSS validation - check for balanced braces
 | ||||
|         $openBraces = substr_count($content, '{'); | ||||
|         $closeBraces = substr_count($content, '}'); | ||||
|          | ||||
| 
 | ||||
|         if ($openBraces !== $closeBraces) { | ||||
|             throw new Exception('Invalid CSS: Unbalanced braces detected'); | ||||
|         } | ||||
| @ -261,7 +261,7 @@ class CssController extends Controller { | ||||
|         // Remove path information and dangerous characters
 | ||||
|         $fileName = basename($originalName); | ||||
|         $fileName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $fileName); | ||||
|          | ||||
| 
 | ||||
|         return $fileName; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
|                 'config' => $config, | ||||
|                 'moodPicker' => $moodPicker, | ||||
|             ]; | ||||
|              | ||||
| 
 | ||||
|             $this->render("mood.php", $vars); | ||||
|         } | ||||
| 
 | ||||
| @ -33,7 +33,7 @@ | ||||
|                 // set or clear the mood
 | ||||
|                 $user->mood = $mood; | ||||
|                 $user = $user->save(); | ||||
|              | ||||
| 
 | ||||
|                 // go back to the index and show the updated mood
 | ||||
|                 header('Location: ' . $config->basePath); | ||||
|                 exit; | ||||
|  | ||||
| @ -4,7 +4,7 @@ class TickController extends Controller{ | ||||
|     // every tick is identified by its timestamp
 | ||||
|     public function index(string $year, string $month, string $day, string $hour, string $minute, string $second){ | ||||
|         $model = new TickModel(); | ||||
|         $tick = $model->get($year, $month, $day, $hour, $minute, $second);        | ||||
|         $tick = $model->get($year, $month, $day, $hour, $minute, $second); | ||||
|         $this->render('tick.php', $tick); | ||||
|     } | ||||
| } | ||||
| @ -24,7 +24,7 @@ class Util { | ||||
|             }, | ||||
|             $text | ||||
|         ); | ||||
|     }     | ||||
|     } | ||||
| 
 | ||||
|     // For relative time display, compare the stored time to the current time
 | ||||
|     // and display it as "X seconds/minutes/hours/days etc." ago
 | ||||
| @ -59,6 +59,6 @@ class Util { | ||||
|         [$year, $month, $day] = $dateParts; | ||||
|         [$hour, $minute, $second] = $timeParts; | ||||
| 
 | ||||
|         return "$year/$month/$day/$hour/$minute/$second";         | ||||
|         return "$year/$month/$day/$hour/$minute/$second"; | ||||
|     } | ||||
| } | ||||
| @ -36,11 +36,11 @@ class ConfigModel { | ||||
|         if (empty($this->cssId)) { | ||||
|             return null; | ||||
|         } | ||||
|      | ||||
| 
 | ||||
|         // Fetch filename from css table using cssId
 | ||||
|         $cssModel = new CssModel(); | ||||
|         $cssRecord = $cssModel->getById($this->cssId); | ||||
|      | ||||
| 
 | ||||
|         return $cssRecord ? $cssRecord['filename'] : null; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,7 @@ class TickModel { | ||||
|             // split the path to the current file into the date components
 | ||||
|             $pathParts = explode('/', str_replace('\\', '/', $file)); | ||||
| 
 | ||||
|             // assign the different components to the appropriate part of the date 
 | ||||
|             // assign the different components to the appropriate part of the date
 | ||||
|             $year = $pathParts[count($pathParts) - 3]; | ||||
|             $month = $pathParts[count($pathParts) - 2]; | ||||
|             $day = pathinfo($pathParts[count($pathParts) - 1], PATHINFO_FILENAME); | ||||
| @ -75,18 +75,18 @@ class TickModel { | ||||
|         $content = $time . "|" . $tick . "\n"; | ||||
|         file_put_contents($filename, $content, FILE_APPEND); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     public static function get(string $y, string $m, string $d, string $H, string $i, string $s): array{ | ||||
|         $tickTime = new DateTime("$y-$m-$d $H:$i:$s"); | ||||
|         $timestamp = "$H:$i:$s"; | ||||
|         $file = TICKS_DIR . "/$y/$m/$d.txt"; | ||||
|              | ||||
| 
 | ||||
|         if (!file_exists($file)) { | ||||
|             http_response_code(404); | ||||
|             echo "Tick not found: $file."; | ||||
|             exit; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); | ||||
|         foreach ($lines as $line) { | ||||
|             if (str_starts_with($line, $timestamp)) { | ||||
|  | ||||
| @ -46,7 +46,7 @@ class UserModel { | ||||
|    // loading the password into memory
 | ||||
|    public function set_password(string $password): void { | ||||
|         global $db; | ||||
|          | ||||
| 
 | ||||
|         $hash = password_hash($password, PASSWORD_DEFAULT); | ||||
|         $stmt = $db->prepare("UPDATE user SET password_hash=? WHERE id=1"); | ||||
|         $stmt->execute([$hash]); | ||||
|  | ||||
| @ -12,7 +12,7 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n"; | ||||
| <channel> | ||||
|     <title><?php echo Util::escape_xml($config->siteTitle . 'RSS Feed') ?></title>
 | ||||
|     <link><?php echo Util::escape_xml($config->baseUrl . $config->basePath)?></link>
 | ||||
|     <atom:link href="<?php echo Util::escape_xml($config->baseUrl . $config->basePath. 'feed/rss')?>"  | ||||
|     <atom:link href="<?php echo Util::escape_xml($config->baseUrl . $config->basePath. 'feed/rss')?>" | ||||
|                rel="self" | ||||
|                type="application/rss+xml" /> | ||||
|     <description><?php echo Util::escape_xml($config->siteDescription) ?></description>
 | ||||
|  | ||||
| @ -18,11 +18,11 @@ | ||||
|         <link rel="alternate" | ||||
|               type="application/rss+xml" | ||||
|               title="<?php echo Util::escape_html($config->siteTitle) ?> RSS Feed" | ||||
|               href="<?php echo Util::escape_html($config->baseUrl . $config->basePath)?>feed/rss/">    | ||||
|               href="<?php echo Util::escape_html($config->baseUrl . $config->basePath)?>feed/rss/"> | ||||
|         <link rel="alternate" | ||||
|               type="application/atom+xml" | ||||
|               title="<?php echo Util::escape_html($config->siteTitle) ?> Atom Feed" | ||||
|               href="<?php echo Util::escape_html($config->baseUrl . $config->basePath)?>feed/atom/">    | ||||
|               href="<?php echo Util::escape_html($config->baseUrl . $config->basePath)?>feed/atom/"> | ||||
|     </head> | ||||
|     <body> | ||||
| <?php include TEMPLATES_DIR . '/partials/navbar.php'?>
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
|         <h1><?php if ($isSetup): ?>Setup<?php else: ?>Admin<?php endif; ?></h1>
 | ||||
|         <div> | ||||
|             <form | ||||
|                 action="<?php echo $config->basePath . ($isSetup ? 'setup' : 'admin') ?>"   | ||||
|                 action="<?php echo $config->basePath . ($isSetup ? 'setup' : 'admin') ?>" | ||||
|                 method="post"> | ||||
|                 <input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>"> | ||||
|                 <fieldset> | ||||
| @ -16,7 +16,7 @@ | ||||
|                             value="<?= Util::escape_html($user->username) ?>" | ||||
|                             required> | ||||
|                         <label>Display name <span class=required>*</span></label> | ||||
|                         <input type="text"  | ||||
|                         <input type="text" | ||||
|                                name="display_name" | ||||
|                                value="<?= Util::escape_html($user->displayName) ?>" | ||||
|                                required> | ||||
| @ -36,7 +36,7 @@ | ||||
|                         <label>Title <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             name="site_title" | ||||
|                             value="<?= Util::escape_html($config->siteTitle) ?>"  | ||||
|                             value="<?= Util::escape_html($config->siteTitle) ?>" | ||||
|                             required> | ||||
|                         <label>Description <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
| @ -47,7 +47,7 @@ | ||||
|                             name="base_url" | ||||
|                             value="<?= Util::escape_html($config->baseUrl) ?>" | ||||
|                             required> | ||||
|                         <label>Base path <span class=required>*</span></label>  | ||||
|                         <label>Base path <span class=required>*</span></label> | ||||
|                         <input type="text" | ||||
|                             name="base_path" | ||||
|                             value="<?= Util::escape_html($config->basePath) ?>" | ||||
| @ -62,7 +62,7 @@ | ||||
|                 <fieldset> | ||||
|                     <legend>Change password</legend> | ||||
|                     <div class="fieldset-items"> | ||||
|                         <label>New password  | ||||
|                         <label>New password | ||||
|                             <?php if($isSetup): ?><span class=required>*</span><?php endif; ?>
 | ||||
|                         </label> | ||||
|                         <input type="password" | ||||
|  | ||||
| @ -11,14 +11,14 @@ | ||||
|                         <select id="selectCssFile" name="selectCssFile" value=<?= $config->cssId ?>>
 | ||||
|                             <option value="">Default</option> | ||||
| <?php foreach ($customCss as $cssFile): ?>
 | ||||
|     <?php  | ||||
|     <?php | ||||
|         if ($cssFile['id'] == $config->cssId){ | ||||
|             $cssDescription = $cssFile['description']; | ||||
|             $selected = "selected"; | ||||
|         } | ||||
|     ?>
 | ||||
| 
 | ||||
|                             <option value=<?= $cssFile['id'] ?> 
 | ||||
|                             <option value=<?= $cssFile['id'] ?>
 | ||||
|                                     <?= isset($selected) ? $selected : ""?>>
 | ||||
|                                     <?=Util::escape_html($cssFile['filename'])?>
 | ||||
|                             </option> | ||||
| @ -40,9 +40,9 @@ | ||||
|                     <div class="fieldset-items"> | ||||
|                         <input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>"> | ||||
|                         <label for="uploadCssFile">Select File to Upload</label> | ||||
|                         <input type="file"  | ||||
|                                id="uploadCssFile"  | ||||
|                                name="uploadCssFile"  | ||||
|                         <input type="file" | ||||
|                                id="uploadCssFile" | ||||
|                                name="uploadCssFile" | ||||
|                                accept=".css"> | ||||
|                         <div class="file-info"> | ||||
|                             <strong>File Requirements:</strong><br> | ||||
| @ -51,8 +51,8 @@ | ||||
|                             • Will be scanned for malicious content | ||||
|                         </div> | ||||
|                         <label for="description">Description (optional)</label> | ||||
|                         <textarea id="description"  | ||||
|                                   name="description"  | ||||
|                         <textarea id="description" | ||||
|                                   name="description" | ||||
|                                   placeholder="Describe this CSS file..."></textarea> | ||||
|                         <div></div> | ||||
|                         <button type="submit" name="action" value="upload">Upload CSS File</button> | ||||
|  | ||||
| @ -31,9 +31,9 @@ | ||||
|                     <div class="fieldset-items"> | ||||
|                         <?php foreach ($emojiList as $emojiItem): ?>
 | ||||
|                             <div class="emoji-checkbox-item"> | ||||
|                                 <input type="checkbox"  | ||||
|                                        id="delete_emoji_<?= Util::escape_html($emojiItem['id']) ?>"  | ||||
|                                        name="delete_emoji_ids[]"  | ||||
|                                 <input type="checkbox" | ||||
|                                        id="delete_emoji_<?= Util::escape_html($emojiItem['id']) ?>" | ||||
|                                        name="delete_emoji_ids[]" | ||||
|                                        value="<?= Util::escape_html($emojiItem['id']) ?>"> | ||||
|                                 <label for="delete_emoji_<?= Util::escape_html($emojiItem['id']) ?>"> | ||||
|                                     <span class="emoji-display"><?= Util::escape_html($emojiItem['emoji']) ?></span>
 | ||||
|  | ||||
| @ -34,7 +34,7 @@ | ||||
|                         <input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>"> | ||||
|                         <textarea name="tick" | ||||
|                                   placeholder="What's ticking?" | ||||
|                                   minlength="1"  | ||||
|                                   minlength="1" | ||||
|                                   maxlength="200" | ||||
|                                   rows="3"></textarea> | ||||
|                         <button type="submit" class="submit-btn">Tick</button> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user