In medias res
This commit is contained in:
		
						commit
						074c9cbad9
					
				
					 36 changed files with 3945 additions and 0 deletions
				
			
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					config/php/fpm/app.sixfold.org-secrets.ini
 | 
				
			||||||
 | 
					db
 | 
				
			||||||
 | 
					www/assets/avatars
 | 
				
			||||||
 | 
					www/assets/docs
 | 
				
			||||||
							
								
								
									
										76
									
								
								config/apache/app.sixfold.org.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								config/apache/app.sixfold.org.conf
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,76 @@
 | 
				
			||||||
 | 
					<VirtualHost *:80>
 | 
				
			||||||
 | 
						ServerName app.sixfold.org
 | 
				
			||||||
 | 
						RedirectPermanent	/ https://app.sixfold.org/
 | 
				
			||||||
 | 
					</VirtualHost>
 | 
				
			||||||
 | 
					<VirtualHost *:443>
 | 
				
			||||||
 | 
						ServerName app.sixfold.org
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ServerAdmin sixfold@sixfold.org
 | 
				
			||||||
 | 
						DocumentRoot /var/www/html/app.sixfold.org/www
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ErrorLog ${APACHE_LOG_DIR}/app.sixfold.org-error.log
 | 
				
			||||||
 | 
						CustomLog ${APACHE_LOG_DIR}/app.sixfold.org-access.log combined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ErrorDocument 404 /errors/404
 | 
				
			||||||
 | 
						ErrorDocument 503 /errors/503
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RewriteEngine On
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<Files "errors/([0-9]+).php">
 | 
				
			||||||
 | 
							RewriteCond %{ENV:REDIRECT_STATUS} =""
 | 
				
			||||||
 | 
							RewriteRule .* - [R=404,L]
 | 
				
			||||||
 | 
						</Files>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Received: /filename.php and /filename.php exists in filesystem; Result: 301 redirect to /filename and restart request
 | 
				
			||||||
 | 
						RewriteCond %{REQUEST_FILENAME} \.php$
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} -f
 | 
				
			||||||
 | 
						RewriteRule ^/(.+)\.php$	/$1 [R=301,L]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Received: /filename and /filename.php exists in filesystem; Result: change /filename to /filename.php and continue processing
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-d
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI}.php -f
 | 
				
			||||||
 | 
						RewriteRule ^(.+)$							$1.php [QSA]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Received: /filename and /filename.xml exists in filesystem; Result: rewrite to /filename.xml
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}.xml -f
 | 
				
			||||||
 | 
						RewriteRule (.*) $1.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Received: /filename and /filename.xhtml exists in filesystem; Result: rewrite to /filename.xhtml
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}.xhtml -f
 | 
				
			||||||
 | 
						RewriteRule (.*) $1.xhtml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Redirect index pages
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.php -f
 | 
				
			||||||
 | 
						RewriteRule (.*)	$1/index.php
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					        # Remove trailing slashes
 | 
				
			||||||
 | 
					        RewriteRule                             ^/(.+?)/$                                       /$1 [R=301,L]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
 | 
				
			||||||
 | 
						RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME}/index.xml -f
 | 
				
			||||||
 | 
						RewriteRule (.*)	$1/index.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RewriteCond %{QUERY_STRING} hash=([a-z0-9]+) [NC]
 | 
				
			||||||
 | 
						RewriteRule ^/docs /docs/%1? [L,R=301]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Sixfold rewrites
 | 
				
			||||||
 | 
						RewriteRule ^/games/([0-9]+)$ /games/game.php?game=$1
 | 
				
			||||||
 | 
						RewriteRule ^/games/([0-9]+)/feedback$ /games/feedback.php?game=$1
 | 
				
			||||||
 | 
						RewriteRule ^/games/([0-9]+)/results$ /games/results.php?game=$1
 | 
				
			||||||
 | 
						RewriteRule ^/games/([0-9]+)/submit$ /games/submit.php?game=$1
 | 
				
			||||||
 | 
						RewriteRule ^/games/([0-9]+)/update$ /games/update.php?game=$1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RewriteRule ^/doc/([a-z0-9]+)$ /docs/$1 [L,R=301]
 | 
				
			||||||
 | 
						RewriteRule ^/docs/([a-z0-9]+)$ /docs/index.php?hash=$1 [L,QSA]
 | 
				
			||||||
 | 
						RewriteRule ^/members/([^\.]+?)$ /members/member.php?handle=$1 [L]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# PayPal order processing rewrites
 | 
				
			||||||
 | 
						RewriteRule /api/orders$ /games/process-order.php
 | 
				
			||||||
 | 
						RewriteRule /api/orders/(.*)/capture$ /games/process-order.php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SSLCertificateFile /etc/letsencrypt/live/app.sixfold.org/fullchain.pem
 | 
				
			||||||
 | 
						SSLCertificateKeyFile /etc/letsencrypt/live/app.sixfold.org/privkey.pem
 | 
				
			||||||
 | 
						Include /etc/letsencrypt/options-ssl-apache.conf
 | 
				
			||||||
 | 
					</VirtualHost>
 | 
				
			||||||
							
								
								
									
										26
									
								
								config/php/fpm/app.sixfold.org.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								config/php/fpm/app.sixfold.org.ini
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					error_log = "/var/log/local/php-error.log"
 | 
				
			||||||
 | 
					display_errors = Off
 | 
				
			||||||
 | 
					display_startup_errors = Off
 | 
				
			||||||
 | 
					html_errors = Off
 | 
				
			||||||
 | 
					log_errors = On
 | 
				
			||||||
 | 
					short_open_tag = On
 | 
				
			||||||
 | 
					error_reporting = E_ALL
 | 
				
			||||||
 | 
					max_execution_time = 120
 | 
				
			||||||
 | 
					allow_url_fopen = false
 | 
				
			||||||
 | 
					allow_url_include = false
 | 
				
			||||||
 | 
					expose_php = Off
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					post_max_size = 10M
 | 
				
			||||||
 | 
					upload_max_filesize = 6M
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include_path = /var/www/html/app.sixfold.org/lib
 | 
				
			||||||
 | 
					auto_prepend_file = config.php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[session]
 | 
				
			||||||
 | 
					session.use_strict_mode = Off
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Date]
 | 
				
			||||||
 | 
					date.timezone = Etc/UTC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[app]
 | 
				
			||||||
 | 
					app.site_status = "live"
 | 
				
			||||||
							
								
								
									
										129
									
								
								lib/config.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								lib/config.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,129 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					session_set_cookie_params([
 | 
				
			||||||
 | 
					    "lifetime" => 0,
 | 
				
			||||||
 | 
					    "path" => "/",
 | 
				
			||||||
 | 
					    "domain" => $_SERVER["HTTP_HOST"],
 | 
				
			||||||
 | 
					    "secure" => false,
 | 
				
			||||||
 | 
					    "httponly" => true,
 | 
				
			||||||
 | 
					    "samesite" => "Strict", // Helps mitigate CSRF attacks
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					session_start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					define("ABS_PATH", $_SERVER["DOCUMENT_ROOT"]);
 | 
				
			||||||
 | 
					define("DIRECTORY_DOCS", $_SERVER["DOCUMENT_ROOT"] . "/assets/docs");
 | 
				
			||||||
 | 
					define("UPLOAD_MAX_FILESIZE", 1024 * 1000 * 6);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					define("TEST_COOKIE_NAME", get_cfg_var("secrets.test_cookie_name"));
 | 
				
			||||||
 | 
					define("TEST_COOKIE_VALUE", get_cfg_var("secrets.test_cookie_value"));
 | 
				
			||||||
 | 
					define("PAYPAL_CLIENT_ID", get_cfg_var("secrets.paypal.client_id"));
 | 
				
			||||||
 | 
					define("PAYPAL_CLIENT_SECRET", get_cfg_var("secrets.paypal.client_secret"));
 | 
				
			||||||
 | 
					define("PAYPAL_BASE_URL", get_cfg_var("secrets.paypal.base_url"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					define("CURRENT_URL", parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));
 | 
				
			||||||
 | 
					define("LOGGED_IN", isset($_SESSION["account"]));
 | 
				
			||||||
 | 
					define("IS_ADMIN", LOGGED_IN && $_SESSION["account"]->account_type === 9);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					define(
 | 
				
			||||||
 | 
					    "COOKIES_ENABLED",
 | 
				
			||||||
 | 
					    isset($_COOKIE[TEST_COOKIE_NAME]) &&
 | 
				
			||||||
 | 
					    $_COOKIE[TEST_COOKIE_NAME] == TEST_COOKIE_VALUE
 | 
				
			||||||
 | 
					        ? 1
 | 
				
			||||||
 | 
					        : 0
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setcookie(TEST_COOKIE_NAME, TEST_COOKIE_VALUE, [
 | 
				
			||||||
 | 
					    "expires" => 0,
 | 
				
			||||||
 | 
					    "path" => "/",
 | 
				
			||||||
 | 
					    "domain" => $_SERVER["HTTP_HOST"],
 | 
				
			||||||
 | 
					    "secure" => false,
 | 
				
			||||||
 | 
					    "httponly" => true,
 | 
				
			||||||
 | 
					    "samesite" => "Strict",
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$db = [
 | 
				
			||||||
 | 
					    "data" => new PDO(get_cfg_var("secrets.db_url")),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$stmt = $db["data"]->query("SELECT name, id FROM game_status");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					foreach ($stmt->fetchAll(PDO::FETCH_KEY_PAIR) as $name => $id) {
 | 
				
			||||||
 | 
					    define($name, $id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					unset($name, $id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$time_zone = new DateTimeZone("America/New_York");
 | 
				
			||||||
 | 
					$one_second = new DateInterval("PT1S");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function get_status_message($status_code)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if ($status_code === STATUS_ENROLLING) {
 | 
				
			||||||
 | 
					        return "Enrolling";
 | 
				
			||||||
 | 
					    } elseif ($status_code === STATUS_ROUND_ONE) {
 | 
				
			||||||
 | 
					        return "Round One";
 | 
				
			||||||
 | 
					    } elseif ($status_code === STATUS_ROUND_TWO) {
 | 
				
			||||||
 | 
					        return "Round Two";
 | 
				
			||||||
 | 
					    } elseif ($status_code === STATUS_ROUND_THREE) {
 | 
				
			||||||
 | 
					        return "Round Three";
 | 
				
			||||||
 | 
					    } elseif ($status_code === STATUS_REVIEW) {
 | 
				
			||||||
 | 
					        return "Reviewing Submissions";
 | 
				
			||||||
 | 
					    } elseif ($status_code === STATUS_DELAYED) {
 | 
				
			||||||
 | 
					        return "Delayed";
 | 
				
			||||||
 | 
					    } elseif ($status_code === STATUS_DONE) {
 | 
				
			||||||
 | 
					        return "Completed";
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return "Unknown Status";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function slugify($string)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    $rules = <<<RULES
 | 
				
			||||||
 | 
					    :: Any-Latin;
 | 
				
			||||||
 | 
					    :: NFD;
 | 
				
			||||||
 | 
					    :: [:Nonspacing Mark:] Remove;
 | 
				
			||||||
 | 
					    :: NFC;
 | 
				
			||||||
 | 
					    :: [^-[:^Punctuation:]] Remove;
 | 
				
			||||||
 | 
					    :: Lower();
 | 
				
			||||||
 | 
					    [:^L:] { [-] > ;
 | 
				
			||||||
 | 
					    [-] } [:^L:] > ;
 | 
				
			||||||
 | 
					    [-[:Separator:]]+ > '-';
 | 
				
			||||||
 | 
					RULES;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return \Transliterator::createFromRules($rules)->transliterate($string);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Verify if the submitted password is correct
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function password_check($account)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    global $db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (substr($account->password, 0, 9) === '$argon2id') { ?>
 | 
				
			||||||
 | 
					        <?php return password_verify(
 | 
				
			||||||
 | 
					            $_POST["password"],
 | 
				
			||||||
 | 
					            $account->password
 | 
				
			||||||
 | 
					        );} else {if (sha1($_POST["password"]) === $account->password):
 | 
				
			||||||
 | 
					            $stmt = $db["data"]->prepare('UPDATE members
 | 
				
			||||||
 | 
					            SET password = :password
 | 
				
			||||||
 | 
					            WHERE email = :email');
 | 
				
			||||||
 | 
					            $new_password = password_hash(
 | 
				
			||||||
 | 
					                $_POST["password"],
 | 
				
			||||||
 | 
					                PASSWORD_ARGON2ID
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $did_update = $stmt->execute([
 | 
				
			||||||
 | 
					                "email" => $account->email,
 | 
				
			||||||
 | 
					                "password" => $new_password,
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					            if (!$did_update) {
 | 
				
			||||||
 | 
					                http_response_code(500);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return $did_update;
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            http_response_code(401);
 | 
				
			||||||
 | 
					        endif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								lib/partials/assignments.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/partials/assignments.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,86 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					$assignments = new stdClass();
 | 
				
			||||||
 | 
					$assignments->params = [
 | 
				
			||||||
 | 
					    "game_id" => $game->id,
 | 
				
			||||||
 | 
					    "member_id" => 30436 ?? $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					    "round" => 1,
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$stmt_assignments = $db["data"]->prepare(
 | 
				
			||||||
 | 
					    "SELECT submissions.id, title, hash, score FROM assignments
 | 
				
			||||||
 | 
					    JOIN submissions ON submissions.id = assignments.submission_id
 | 
				
			||||||
 | 
					    WHERE assignments.game_id = :game_id AND assignments.member_id = :member_id
 | 
				
			||||||
 | 
					    AND round_number = :round"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					$stmt_assignments->execute($assignments->params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$assignments->round_1 = $stmt_assignments->fetchAll(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$assignments->params["round"] = 2;
 | 
				
			||||||
 | 
					$stmt_assignments->execute($assignments->params);
 | 
				
			||||||
 | 
					$assignments->round_2 = $stmt_assignments->fetchAll(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$assignments->params["round"] = 3;
 | 
				
			||||||
 | 
					$stmt_assignments->execute($assignments->params);
 | 
				
			||||||
 | 
					$assignments->round_3 = $stmt_assignments->fetchAll(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					<section class="flow">
 | 
				
			||||||
 | 
					    <h2>Your Assignments</h2>
 | 
				
			||||||
 | 
					    <div role="region" aria-labelledby="round-1-assignments-caption" tabindex="0">
 | 
				
			||||||
 | 
					        <table>
 | 
				
			||||||
 | 
					            <caption id="round-1-assignments-caption">Round One Assignments for <?= $game->name ?></caption>
 | 
				
			||||||
 | 
					        <thead>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="col">Title</th>
 | 
				
			||||||
 | 
					                <th scope="col">Score</th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </thead>
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            <?php foreach ($assignments->round_1 as $item): ?>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row"><a href="/docs/<?= $item->hash ?>"><?= $item->title ?></a></th>
 | 
				
			||||||
 | 
					                <td><?= $item->score ?? 'N/A' ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <?php endforeach; ?>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div role="region" aria-labelledby="round-2-assignments-caption" tabindex="0">
 | 
				
			||||||
 | 
					        <table>
 | 
				
			||||||
 | 
					            <caption id="round-2-assignments-caption">Round Two Assignments for <?= $game->name ?></caption>
 | 
				
			||||||
 | 
					        <thead>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="col">Title</th>
 | 
				
			||||||
 | 
					                <th scope="col">Score</th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </thead>
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            <?php foreach ($assignments->round_2 as $item): ?>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row"><a href="/docs/<?= $item->hash ?>"><?= $item->title ?></a></th>
 | 
				
			||||||
 | 
					                <td><?= $item->score ?? 'N/A' ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <?php endforeach; ?>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div role="region" aria-labelledby="round-3-assignments-caption" tabindex="0">
 | 
				
			||||||
 | 
					        <table>
 | 
				
			||||||
 | 
					            <caption id="round-3-assignments-caption">Round Three Assignments for <?= $game->name ?></caption>
 | 
				
			||||||
 | 
					        <thead>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="col">Title</th>
 | 
				
			||||||
 | 
					                <th scope="col">Score</th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </thead>
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            <?php foreach ($assignments->round_3 as $item): ?>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row"><a href="/docs/<?= $item->hash ?>"><?= $item->title ?></a></th>
 | 
				
			||||||
 | 
					                <td><?= $item->score ?? 'N/A' ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <?php endforeach; ?>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
							
								
								
									
										31
									
								
								lib/partials/feedback.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/partials/feedback.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					$stmt_fdbck = $db["data"]->prepare(
 | 
				
			||||||
 | 
					    "SELECT round_number, score, comment FROM assignments WHERE completed IS NOT NULL AND submission_id = :submission_id"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					$stmt_fdbck->execute([
 | 
				
			||||||
 | 
					    "submission_id" => $submission->id,
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					$feedback = $stmt_fdbck->fetchAll(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
					    <h3>Feedback</h3>
 | 
				
			||||||
 | 
					    <div role="region" aria-labelledby="feedback-caption" tabindex="0">
 | 
				
			||||||
 | 
					        <table>
 | 
				
			||||||
 | 
					            <caption id="feedback-caption">Feedback for "<?= $submission->title ?>"</caption>
 | 
				
			||||||
 | 
					        <thead>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="col">Score</th>
 | 
				
			||||||
 | 
					                <th scope="col">Comment</th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </thead>
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            <?php foreach ($feedback as $item): ?>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <td><?= $item->score ?></td>
 | 
				
			||||||
 | 
					                <td><?= ($item->comment) ? mb_convert_encoding($item->comment, 'Windows-1252','utf-8') : '<i>No comment was provided.</i>' ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <?php endforeach; ?>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										16
									
								
								lib/partials/footer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								lib/partials/footer.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					<footer id="footer">
 | 
				
			||||||
 | 
					    <nav>
 | 
				
			||||||
 | 
					        <ul role="list">
 | 
				
			||||||
 | 
					            <li><a href="https://sixfold.org/issues">Read Issues</a></li>
 | 
				
			||||||
 | 
					            <li><a href="https://sixfold.org/how-it-works/submission-guidelines">Submission Guidelines</a></li>
 | 
				
			||||||
 | 
					            <li><a href="https://sixfold.org/privacy">Privacy</a></li>
 | 
				
			||||||
 | 
					            <li><a href="https://sixfold.org/terms-and-conditions">Terms & Conditions</a></li>
 | 
				
			||||||
 | 
					            <li><a href="/account/logout">Log Out</a></li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					        <a href="/">
 | 
				
			||||||
 | 
					            <?= file_get_contents(ABS_PATH . '/assets/sixfold.svg') ?>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					</footer>
 | 
				
			||||||
							
								
								
									
										27
									
								
								lib/partials/head.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/partials/head.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					<!-- The Yesterweb is dead, long live the Yesterweb! -->
 | 
				
			||||||
 | 
					<html lang="en" dir="ltr">
 | 
				
			||||||
 | 
					    <head>
 | 
				
			||||||
 | 
					        <meta charset="UTF-8"/>
 | 
				
			||||||
 | 
					        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
 | 
				
			||||||
 | 
					        <title><?= $title ? "$title | " . "Sixfold" : "Sixfold" ?></title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <link rel="stylesheet" href="/assets/css/core.css" />
 | 
				
			||||||
 | 
					        <link rel="stylesheet" href="/assets/css/dark-mode.css" media="(prefers-color-scheme: dark)"/>
 | 
				
			||||||
 | 
					        <link rel="stylesheet" href="/assets/css/app.css" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <meta name="robots" content="index,follow"/>
 | 
				
			||||||
 | 
					        <meta name="googlebot" content="index,follow"/>
 | 
				
			||||||
 | 
					        <meta name="format-detection" content="telephone=no"/>
 | 
				
			||||||
 | 
					        <link rel="author" href="/humans.txt"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <meta property="og:site_name" content="Sixfold"/>
 | 
				
			||||||
 | 
					        <meta property="og:type" content="website"/>
 | 
				
			||||||
 | 
					        <meta property="og:url" content="https://app.sixfold.org<?= $_SERVER['REQUEST_URI'] ?>"/>
 | 
				
			||||||
 | 
					        <meta property="og:title" content="<?= $title ?>"/>
 | 
				
			||||||
 | 
					        <meta property="og:description" content="<?= $description ? $description : "Sixfold is a writer-voted journal of poetry and short stories." ?>"/>
 | 
				
			||||||
 | 
					        <meta name="description" content="<?= $description ? $description : "Sixfold is a writer-voted journal of poetry and short stories." ?>"/>
 | 
				
			||||||
 | 
					        <meta property="og:locale" content="en"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <meta name="color-scheme" content="light dark"/>
 | 
				
			||||||
 | 
					        <meta name="theme-color" content="darkolivegreen" />
 | 
				
			||||||
 | 
					    </head>
 | 
				
			||||||
							
								
								
									
										24
									
								
								lib/partials/header.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/partials/header.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					<header aria-label="Skip links">
 | 
				
			||||||
 | 
					    <ul role="list">
 | 
				
			||||||
 | 
					        <li>
 | 
				
			||||||
 | 
					            <a href="#main">Skip to content</a>
 | 
				
			||||||
 | 
					        </li>
 | 
				
			||||||
 | 
					        <li>
 | 
				
			||||||
 | 
					            <a href="#footer">Skip to footer</a>
 | 
				
			||||||
 | 
					        </li>
 | 
				
			||||||
 | 
					        <li>
 | 
				
			||||||
 | 
					            <a href="#a11y-options">Skip to style settings</a>
 | 
				
			||||||
 | 
					        </li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					</header>
 | 
				
			||||||
 | 
					<header>
 | 
				
			||||||
 | 
					   <a href="/"><?php include ABS_PATH . "/assets/lockup.svg" ?></a>
 | 
				
			||||||
 | 
					    <nav>
 | 
				
			||||||
 | 
					        <ul role="list">
 | 
				
			||||||
 | 
					            <li><a href="/" <?php if (CURRENT_URL === '/') { ?>aria-current="page"<?php } ?>>Dashboard</a></li>
 | 
				
			||||||
 | 
					            <li><a href="/games" <?php if (CURRENT_URL === '/games') { ?>aria-current="page"<?php } ?>>Games</a></li>
 | 
				
			||||||
 | 
					            <li><a href="/members" <?php if (CURRENT_URL === '/members') { ?>aria-current="page"<?php } ?>>Members</a></li>
 | 
				
			||||||
 | 
					            <li><a href="/account" <?php if (CURRENT_URL === '/account') { ?>aria-current="page"<?php } ?>>Account</a></li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					</header>
 | 
				
			||||||
							
								
								
									
										17
									
								
								lib/partials/login-form.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								lib/partials/login-form.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					<form action="/login" method="post" class="flow">
 | 
				
			||||||
 | 
					    <label>
 | 
				
			||||||
 | 
					        <span>Email address <small>(email@example.com)</small></span>
 | 
				
			||||||
 | 
					        <input type="text" name="email" value="<?= $_POST['email'] ?? '' ?>"/>
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					    <label>
 | 
				
			||||||
 | 
					        <span>Password</span>
 | 
				
			||||||
 | 
					        <input type="password" name="password" />
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
 | 
					    <input type="hidden" name="redirect" value="<?= $_SERVER['REQUEST_URI'] ?>" />
 | 
				
			||||||
 | 
					    <button type="submit">Log In</button>
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					<p><b>Forgot your password?</b> Send an email to <code>sixfold [at] sixfold.org</code>.</p>
 | 
				
			||||||
 | 
					<hr/>
 | 
				
			||||||
 | 
					<h2>Not a member yet?</h2>
 | 
				
			||||||
 | 
					<p>Signing up only takes a second and it's completely free. Join the completely writer-voted journal.</p>
 | 
				
			||||||
 | 
					<p><a class="call-to-action" href="/signup">Create an account</a></p>
 | 
				
			||||||
							
								
								
									
										33
									
								
								lib/partials/submission-info.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/partials/submission-info.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					$stmt_fdbck = $db["data"]->prepare(
 | 
				
			||||||
 | 
					    "SELECT round_number, score, comment FROM assignments WHERE completed IS NOT NULL AND submission_id = :submission_id"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					$stmt_fdbck->execute([
 | 
				
			||||||
 | 
					    "submission_id" => $submission->id,
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					$feedback = $stmt_fdbck->fetchAll(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <div role="region" aria-labelledby="details-caption" tabindex="0">
 | 
				
			||||||
 | 
					        <table>
 | 
				
			||||||
 | 
					            <caption id="details-caption">Submission details</caption>
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row">Title</th>
 | 
				
			||||||
 | 
					                <td><a href="/docs/<?= $submission->hash ?>"><?= $submission->title ?></a></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row" rowspan="3">Ranking</th>
 | 
				
			||||||
 | 
					                <th scope="row">Round One</th>
 | 
				
			||||||
 | 
					                <td><?= $submission->rank_round_1 ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row">Round Two</th>
 | 
				
			||||||
 | 
					                <td><?= $submission->rank_round_2 ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row">Round Three</th>
 | 
				
			||||||
 | 
					                <td><?= $submission->rank_round_3 ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
							
								
								
									
										225
									
								
								www/account/edit.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								www/account/edit.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,225 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$description = "Edit your account information.";
 | 
				
			||||||
 | 
					$title = "Edit Account";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function validate_fields($data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    global $db;
 | 
				
			||||||
 | 
					    $errors = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mb_strlen(trim($_POST["name"])) === 0) {
 | 
				
			||||||
 | 
					        $errors["name"] = "You must provide a name or pseudonym.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ($_SESSION["account"]->handle !== $_POST["handle"]) {
 | 
				
			||||||
 | 
					        $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					            "SELECT COUNT(*) FROM members WHERE UPPER(handle) = UPPER(:handle)"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        $stmt->execute([
 | 
				
			||||||
 | 
					            "handle" => $data["handle"],
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($stmt->fetch(PDO::FETCH_COLUMN) > 0) {
 | 
				
			||||||
 | 
					            $errors["handle"] = "That handle is taken.";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ($_SESSION["account"]->email !== $_POST["email"]) {
 | 
				
			||||||
 | 
					        $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					            "SELECT COUNT(*) FROM members WHERE email = :email"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        $stmt->execute([
 | 
				
			||||||
 | 
					            "email" => $data["email"],
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($stmt->fetch(PDO::FETCH_COLUMN) > 0) {
 | 
				
			||||||
 | 
					            $errors["email"] = "That email address is already in use.";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        $_POST["password"] &&
 | 
				
			||||||
 | 
					        $_POST["new-password"] &&
 | 
				
			||||||
 | 
					        !password_check($_SESSION["account"])
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $errors["password"] = "Your password is incorrect.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ($_POST["password"] && mb_strlen(trim($_POST["new-password"])) === 0) {
 | 
				
			||||||
 | 
					        $errors["new-password"] = "You can't have an empty password.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        $_POST["password"] &&
 | 
				
			||||||
 | 
					        $_POST["new-password"] &&
 | 
				
			||||||
 | 
					        $_POST["new-password"] !== $_POST["new-password-confirm"]
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $errors["new-password"] = "The newly-entered passwords do not match.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return $errors;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] === "POST"):
 | 
				
			||||||
 | 
					    $errors = validate_fields($_POST);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        !isset($errors["name"]) &&
 | 
				
			||||||
 | 
					        !isset($errors["handle"]) &&
 | 
				
			||||||
 | 
					        !isset($errors["biography"])
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					            "UPDATE members SET (name, handle, biography) = (:name, :handle, :biography) WHERE id = :id"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $stmt->execute([
 | 
				
			||||||
 | 
					            "id" => $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					            "name" => $_POST["name"],
 | 
				
			||||||
 | 
					            "handle" => $_POST["handle"],
 | 
				
			||||||
 | 
					            "biography" => $_POST["biography"] ?? null,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        ($_SESSION["account"]->email !== $_POST["email"] ||
 | 
				
			||||||
 | 
					            $_POST["password"]) &&
 | 
				
			||||||
 | 
					        !isset($errors["email"]) &&
 | 
				
			||||||
 | 
					        !isset($errors["new-password"]) &&
 | 
				
			||||||
 | 
					        !isset($errors["new-password"])
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					            "UPDATE members SET (email, password) = (:email, :password) WHERE id = :id"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $password = $_POST["new-password"]
 | 
				
			||||||
 | 
					            ? password_hash($_POST["new-password"], PASSWORD_ARGON2ID)
 | 
				
			||||||
 | 
					            : $_SESSION["account"]->password;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $stmt->execute([
 | 
				
			||||||
 | 
					            "id" => $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					            "email" => $_POST["email"],
 | 
				
			||||||
 | 
					            "password" => $password,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare("SELECT * FROM members WHERE id = :id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $results = $stmt->execute([
 | 
				
			||||||
 | 
					        "id" => $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $_SESSION["account"] = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (count($errors) > 0) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        $_SESSION["profile_updated"] = true;
 | 
				
			||||||
 | 
					        http_response_code(303);
 | 
				
			||||||
 | 
					        header("Location: /account/edit");
 | 
				
			||||||
 | 
					        die();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					endif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					        <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <?php if (!LOGGED_IN): ?>
 | 
				
			||||||
 | 
					    <p>You must log in to view this page.</p>
 | 
				
			||||||
 | 
					    <?php include "partials/login-form.php";else: ?>
 | 
				
			||||||
 | 
					    <?php if (http_response_code() === 400) { ?>
 | 
				
			||||||
 | 
					        <aside class='alert'>
 | 
				
			||||||
 | 
					            <p>We found <?= count(
 | 
				
			||||||
 | 
					                $errors
 | 
				
			||||||
 | 
					            ) ?> error(s) with your submission. Please correct the errors provided by each field.</p>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					    <?php } elseif (isset($_SESSION["profile_updated"])) {
 | 
				
			||||||
 | 
					        unset($_SESSION["profile_updated"]); ?>
 | 
				
			||||||
 | 
					        <aside class='alert'>
 | 
				
			||||||
 | 
					            <p>Your account has been sucessfully updated.</p>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					    <?php
 | 
				
			||||||
 | 
					    } ?>
 | 
				
			||||||
 | 
					    <p><i>The ability to upload a photo and add links to your profile will return soon; your existing photo and links are still visible.</i></p>
 | 
				
			||||||
 | 
					    <p><a href='/members/<?= $_SESSION["account"]
 | 
				
			||||||
 | 
					        ->handle ?>' class='call-to-action'>View your profile</a></p>
 | 
				
			||||||
 | 
					    <form action='<?= $_SERVER["REQUEST_URI"] ?>' method='post' class='flow'>
 | 
				
			||||||
 | 
					        <fieldset class='flow'>
 | 
				
			||||||
 | 
					            <legend>Personal Details</legend>
 | 
				
			||||||
 | 
					            <label>
 | 
				
			||||||
 | 
					                <span>Name</span>
 | 
				
			||||||
 | 
					                <?php if (
 | 
				
			||||||
 | 
					                    isset($errors) &&
 | 
				
			||||||
 | 
					                    isset($errors["name"])
 | 
				
			||||||
 | 
					                ) { ?><span><mark><?= $errors["name"] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                <input type='text' name='name' value='<?= $_POST["name"] ??
 | 
				
			||||||
 | 
					                    ($_SESSION["account"]->name ?? "") ?>'/>
 | 
				
			||||||
 | 
					            </label>
 | 
				
			||||||
 | 
					            <label>
 | 
				
			||||||
 | 
					                <span>Handle</span>
 | 
				
			||||||
 | 
					                <?php if (
 | 
				
			||||||
 | 
					                    isset($errors) &&
 | 
				
			||||||
 | 
					                    isset($errors["handle"])
 | 
				
			||||||
 | 
					                ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "handle"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                <input type='text' name='handle' value='<?= $_POST["handle"] ??
 | 
				
			||||||
 | 
					                    ($_SESSION["account"]->handle ?? "") ?>'/>
 | 
				
			||||||
 | 
					            </label>
 | 
				
			||||||
 | 
					            <label>
 | 
				
			||||||
 | 
					                <span>Biography</span>
 | 
				
			||||||
 | 
					                <textarea name='biography'><?= $_POST["biography"] ??
 | 
				
			||||||
 | 
					                    ($_SESSION["account"]->biography ?? "") ?></textarea>
 | 
				
			||||||
 | 
					            </label>
 | 
				
			||||||
 | 
					        </fieldset>
 | 
				
			||||||
 | 
					        <fieldset class='flow'>
 | 
				
			||||||
 | 
					            <legend>Account Security</legend>
 | 
				
			||||||
 | 
					            <label>
 | 
				
			||||||
 | 
					                <span>Email <small>(email@example.com)</small></span>
 | 
				
			||||||
 | 
					                <?php if (
 | 
				
			||||||
 | 
					                    isset($errors) &&
 | 
				
			||||||
 | 
					                    isset($errors["email"])
 | 
				
			||||||
 | 
					                ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "email"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                <input type='text' name='email' value='<?= $_POST["email"] ??
 | 
				
			||||||
 | 
					                    ($_SESSION["account"]->email ?? "") ?>'/>
 | 
				
			||||||
 | 
					            </label>
 | 
				
			||||||
 | 
					            <fieldset class='flow'>
 | 
				
			||||||
 | 
					                <legend>Change Password</legend>
 | 
				
			||||||
 | 
					                <label>
 | 
				
			||||||
 | 
					                    <span>Current Password</span>
 | 
				
			||||||
 | 
					                    <?php if (
 | 
				
			||||||
 | 
					                        isset($errors) &&
 | 
				
			||||||
 | 
					                        isset($errors["password"])
 | 
				
			||||||
 | 
					                    ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "password"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                    <input type='password' name='password' autocomplete="current-password"/>
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					                <label>
 | 
				
			||||||
 | 
					                    <span>New Password</span>
 | 
				
			||||||
 | 
					                    <?php if (
 | 
				
			||||||
 | 
					                        isset($errors) &&
 | 
				
			||||||
 | 
					                        isset($errors["new-password"])
 | 
				
			||||||
 | 
					                    ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "new-password"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                    <input type='password' name='new-password' autocomplete="new-password"/>
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					                <label>
 | 
				
			||||||
 | 
					                    <span>Confirm New Password</span>
 | 
				
			||||||
 | 
					                    <input type='password' name='new-password-confirm' autocomplete="new-password"/>
 | 
				
			||||||
 | 
					                </label>
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					        </fieldset>
 | 
				
			||||||
 | 
					        <button type='submit'>Save changes</button>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					    <?php endif; ?>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										23
									
								
								www/account/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								www/account/index.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$description = "View and update your account information.";
 | 
				
			||||||
 | 
					$title = "Your Account";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					        <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <?php if (!LOGGED_IN): ?>
 | 
				
			||||||
 | 
					    <p>You must log in to view this page.</p>
 | 
				
			||||||
 | 
					    <?php include "partials/login-form.php";else: ?>
 | 
				
			||||||
 | 
					<ul>
 | 
				
			||||||
 | 
					    <li><a href='/account/edit'>Edit profile</a></li>
 | 
				
			||||||
 | 
					    <li><a href='/account/logout'>Log out</a></li>
 | 
				
			||||||
 | 
					</ul>
 | 
				
			||||||
 | 
					    <?php endif; ?>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										8
									
								
								www/account/logout.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								www/account/logout.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					session_start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					session_unset();
 | 
				
			||||||
 | 
					session_destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					http_response_code(303);
 | 
				
			||||||
 | 
					header('Location: /login');
 | 
				
			||||||
							
								
								
									
										95
									
								
								www/assets/css/app.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								www/assets/css/app.css
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,95 @@
 | 
				
			||||||
 | 
					header {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby="results-caption"] {
 | 
				
			||||||
 | 
					    /* max-height: 60ch; */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby="results-caption"] table {
 | 
				
			||||||
 | 
					    font-size: 0.875rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby="results-caption"] thead {
 | 
				
			||||||
 | 
					    font-size: smaller;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby="results-caption"] tr.booted {
 | 
				
			||||||
 | 
					    background-color: rgba(255, 0, 0, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby="results-caption"] tr small {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby="results-caption"] tbody td {
 | 
				
			||||||
 | 
					    min-width: 10ch;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					td.empty {
 | 
				
			||||||
 | 
					    color: GrayText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					article details h3 {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.member img {
 | 
				
			||||||
 | 
					    margin-block-end: 1em;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    max-width: 15rem;
 | 
				
			||||||
 | 
					    height: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* game list */
 | 
				
			||||||
 | 
					.games article {
 | 
				
			||||||
 | 
					    padding: 0.75rem;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    gap: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.games article+article {
 | 
				
			||||||
 | 
					    border-block-start: 0.125em solid currentColor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.games article div:first-child {
 | 
				
			||||||
 | 
					    flex: 1 1 21ch;
 | 
				
			||||||
 | 
					    font-size: smaller;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.games article span {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* forms */
 | 
				
			||||||
 | 
					input[name='keep-manuscript'][value='1']:checked~#manuscript,
 | 
				
			||||||
 | 
					input[name='keep-manuscript'][value='1']:checked~#guidelines {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby$="assignments-caption"] tbody th {
 | 
				
			||||||
 | 
					    width: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					form mark {
 | 
				
			||||||
 | 
					    font-variant: small-caps;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#paypal-errors {
 | 
				
			||||||
 | 
					    font-variant: small-caps;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#paypal-errors:empty {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#paypal-button-container {
 | 
				
			||||||
 | 
					    max-width: 20rem;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    padding: 0.5em;
 | 
				
			||||||
 | 
					    border-radius: 0.5em;
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										907
									
								
								www/assets/css/core.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										907
									
								
								www/assets/css/core.css
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,907 @@
 | 
				
			||||||
 | 
					/* ====== START reset ======
 | 
				
			||||||
 | 
					Based on ["A (more) Modern CSS Reset"](https://piccalil.li/blog/a-more-modern-css-reset/) by Andy Bell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Used under [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*,
 | 
				
			||||||
 | 
					*::before,
 | 
				
			||||||
 | 
					*::after {
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html {
 | 
				
			||||||
 | 
					    -moz-text-size-adjust: none;
 | 
				
			||||||
 | 
					    -webkit-text-size-adjust: none;
 | 
				
			||||||
 | 
					    text-size-adjust: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body,
 | 
				
			||||||
 | 
					h1,
 | 
				
			||||||
 | 
					h2,
 | 
				
			||||||
 | 
					h3,
 | 
				
			||||||
 | 
					h4,
 | 
				
			||||||
 | 
					h5,
 | 
				
			||||||
 | 
					h6,
 | 
				
			||||||
 | 
					p,
 | 
				
			||||||
 | 
					figure,
 | 
				
			||||||
 | 
					blockquote {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Remove list styles, but maintain semantics in VoiceOver */
 | 
				
			||||||
 | 
					ul[role='list'],
 | 
				
			||||||
 | 
					ol[role='list'] {
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    min-height: 100vh;
 | 
				
			||||||
 | 
					    line-height: 1.4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					section,
 | 
				
			||||||
 | 
					main>header {
 | 
				
			||||||
 | 
					    margin: 0 auto;
 | 
				
			||||||
 | 
					    max-width: 60rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Avoid orphans in headings */
 | 
				
			||||||
 | 
					h1,
 | 
				
			||||||
 | 
					h2,
 | 
				
			||||||
 | 
					h3,
 | 
				
			||||||
 | 
					h4 {
 | 
				
			||||||
 | 
					    text-wrap: balance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    text-decoration-skip-ink: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Make images easier to work with */
 | 
				
			||||||
 | 
					img,
 | 
				
			||||||
 | 
					picture {
 | 
				
			||||||
 | 
					    max-width: 100%;
 | 
				
			||||||
 | 
					    height: auto;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Inherit fonts for inputs and buttons */
 | 
				
			||||||
 | 
					input,
 | 
				
			||||||
 | 
					button,
 | 
				
			||||||
 | 
					textarea,
 | 
				
			||||||
 | 
					select {
 | 
				
			||||||
 | 
					    font-family: inherit;
 | 
				
			||||||
 | 
					    font-size: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Anything that has been anchored to should have extra scroll margin */
 | 
				
			||||||
 | 
					:target {
 | 
				
			||||||
 | 
					    scroll-margin-block: 1ex;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ====== START good content ====== */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* @link https://utopia.fyi/type/calculator?c=320,16,1.2,1440,20,1.25,6,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    /* Step -2: 11.1111px → 12.8px */
 | 
				
			||||||
 | 
					    --step--2: clamp(0.6944rem, 0.6643rem + 0.1508vw, 0.8rem);
 | 
				
			||||||
 | 
					    /* Step -1: 13.3333px → 16px */
 | 
				
			||||||
 | 
					    --step--1: clamp(0.8333rem, 0.7857rem + 0.2381vw, 1rem);
 | 
				
			||||||
 | 
					    /* Step 0: 16px → 20px */
 | 
				
			||||||
 | 
					    --step-0: clamp(1rem, 0.9286rem + 0.3571vw, 1.25rem);
 | 
				
			||||||
 | 
					    /* Step 1: 19.2px → 25px */
 | 
				
			||||||
 | 
					    --step-1: clamp(1.2rem, 1.0964rem + 0.5179vw, 1.5625rem);
 | 
				
			||||||
 | 
					    /* Step 2: 23.04px → 31.25px */
 | 
				
			||||||
 | 
					    --step-2: clamp(1.44rem, 1.2934rem + 0.733vw, 1.9531rem);
 | 
				
			||||||
 | 
					    /* Step 3: 27.648px → 39.0625px */
 | 
				
			||||||
 | 
					    --step-3: clamp(1.728rem, 1.5242rem + 1.0192vw, 2.4414rem);
 | 
				
			||||||
 | 
					    /* Step 4: 33.1776px → 48.8281px */
 | 
				
			||||||
 | 
					    --step-4: clamp(2.0736rem, 1.7941rem + 1.3974vw, 3.0518rem);
 | 
				
			||||||
 | 
					    /* Step 5: 39.8131px → 61.0352px */
 | 
				
			||||||
 | 
					    --step-5: clamp(2.4883rem, 2.1094rem + 1.8948vw, 3.8147rem);
 | 
				
			||||||
 | 
					    /* Step 6: 47.7757px → 76.2939px */
 | 
				
			||||||
 | 
					    --step-6: clamp(2.986rem, 2.4767rem + 2.5463vw, 4.7684rem);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.flow>*+* {
 | 
				
			||||||
 | 
					    margin-block-start: var(--flow-space, 1em);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					blockquote {
 | 
				
			||||||
 | 
					    padding-inline-start: 1em;
 | 
				
			||||||
 | 
					    border-inline-start: 0.3em solid;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    font-size: var(--step-1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
					    font-size: var(--step-4);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h2 {
 | 
				
			||||||
 | 
					    font-size: var(--step-3);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3 {
 | 
				
			||||||
 | 
					    font-size: var(--step-2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:is(h1, h2, h3, blockquote) {
 | 
				
			||||||
 | 
					    --flow-space: 1.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:is(h1, h2, h3)+* {
 | 
				
			||||||
 | 
					    --flow-space: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main>* {
 | 
				
			||||||
 | 
					    max-width: 60rem;
 | 
				
			||||||
 | 
					    margin-inline: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    text-underline-offset: 0.3ex;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:hover {
 | 
				
			||||||
 | 
					    text-decoration-thickness: 0.3ex;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:active {
 | 
				
			||||||
 | 
					    text-decoration-thickness: 0.1ex;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ====== START project styles ====== */
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --color-background: linen;
 | 
				
			||||||
 | 
					    --color-foreground: black;
 | 
				
			||||||
 | 
					    --color-accent: darkolivegreen;
 | 
				
			||||||
 | 
					    --color-link: midnightblue;
 | 
				
			||||||
 | 
					    --color-link-visited: rebeccapurple;
 | 
				
			||||||
 | 
					    --color-link-active: darkred;
 | 
				
			||||||
 | 
					    --color-mark: coral;
 | 
				
			||||||
 | 
					    --color-mark: lightsalmon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    color-scheme: light;
 | 
				
			||||||
 | 
					    accent-color: darkolivegreen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    --font-sans-serif: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
 | 
				
			||||||
 | 
					    --font-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
 | 
				
			||||||
 | 
					    --font-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:focus {
 | 
				
			||||||
 | 
					    outline: 0.125rem dashed var(--color-foreground);
 | 
				
			||||||
 | 
					    outline-offset: 0.125em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html {
 | 
				
			||||||
 | 
					    box-sizing: inherit;
 | 
				
			||||||
 | 
					    word-wrap: break-word;
 | 
				
			||||||
 | 
					    -ms-hyphens: auto;
 | 
				
			||||||
 | 
					    hyphens: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    padding: 0 2rem;
 | 
				
			||||||
 | 
					    background-color: var(--color-background);
 | 
				
			||||||
 | 
					    color: var(--color-foreground);
 | 
				
			||||||
 | 
					    font-family: var(--font-serif);
 | 
				
			||||||
 | 
					    font-size: var(--step-0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    color: var(--color-link);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:visited {
 | 
				
			||||||
 | 
					    color: var(--color-link-visited);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:active {
 | 
				
			||||||
 | 
					    color: var(--color-link-active);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-current="page"] {
 | 
				
			||||||
 | 
					    border-block-end-color: var(--color-accent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mark {
 | 
				
			||||||
 | 
					    padding: 0 0.125em;
 | 
				
			||||||
 | 
					    background-color: var(--color-mark);
 | 
				
			||||||
 | 
					    color: black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header {
 | 
				
			||||||
 | 
					    padding: 2rem 0;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    justify-content: space-around;
 | 
				
			||||||
 | 
					    gap: 1.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header:nth-child(2) a:first-child {
 | 
				
			||||||
 | 
					    width: 15rem;
 | 
				
			||||||
 | 
					    height: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header nav {
 | 
				
			||||||
 | 
					    margin: auto 0;
 | 
				
			||||||
 | 
					    font-size: 1.375rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header ul {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header li,
 | 
				
			||||||
 | 
					body>footer li {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header li+li::before {
 | 
				
			||||||
 | 
					    content: "•";
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					    margin-left: 0.1875em;
 | 
				
			||||||
 | 
					    margin-right: 0.1875em;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    top: -0.0625em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header a {
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: currentColor !important;
 | 
				
			||||||
 | 
					    border: 0.25em solid transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>header a:hover {
 | 
				
			||||||
 | 
					    border-block-end-color: var(--color-accent);
 | 
				
			||||||
 | 
					    ;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hr {
 | 
				
			||||||
 | 
					    margin: 1.5em auto;
 | 
				
			||||||
 | 
					    width: 50%;
 | 
				
			||||||
 | 
					    border-color: var(--color-foreground);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dt {
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					aside.alert,
 | 
				
			||||||
 | 
					[data-game-status="0"] [data-status="0"],
 | 
				
			||||||
 | 
					[data-game-status="1"] [data-status="1"],
 | 
				
			||||||
 | 
					[data-game-status="2"] [data-status="2"],
 | 
				
			||||||
 | 
					[data-game-status="3"] [data-status="3"],
 | 
				
			||||||
 | 
					[data-game-status="7"] [data-status="7"],
 | 
				
			||||||
 | 
					[data-game-status="8"] [data-status="8"],
 | 
				
			||||||
 | 
					[data-game-status="9"] [data-status="9"] {
 | 
				
			||||||
 | 
					    padding: 1em 2em;
 | 
				
			||||||
 | 
					    border: 0.125em solid currentColor;
 | 
				
			||||||
 | 
					    border-radius: 0.25em;
 | 
				
			||||||
 | 
					    background-color: var(--color-mark, Mark);
 | 
				
			||||||
 | 
					    color: MarkText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.call-to-action {
 | 
				
			||||||
 | 
					    color: black !important;
 | 
				
			||||||
 | 
					    background: gainsboro;
 | 
				
			||||||
 | 
					    padding: 0.125em 0.375em;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    font-variant: small-caps;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    border: 0.0625em solid currentColor;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.call-to-action:hover,
 | 
				
			||||||
 | 
					a.call-to-action:focus {
 | 
				
			||||||
 | 
					    background: yellowgreen;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.call-to-action::after {
 | 
				
			||||||
 | 
					    content: " →";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.sr-only {
 | 
				
			||||||
 | 
					    border: 0 !important;
 | 
				
			||||||
 | 
					    clip: rect(1px, 1px, 1px, 1px) !important;
 | 
				
			||||||
 | 
					    -webkit-clip-path: inset(50%) !important;
 | 
				
			||||||
 | 
					    clip-path: inset(50%) !important;
 | 
				
			||||||
 | 
					    height: 1px !important;
 | 
				
			||||||
 | 
					    overflow: hidden !important;
 | 
				
			||||||
 | 
					    margin: -1px !important;
 | 
				
			||||||
 | 
					    padding: 0 !important;
 | 
				
			||||||
 | 
					    position: absolute !important;
 | 
				
			||||||
 | 
					    width: 1px !important;
 | 
				
			||||||
 | 
					    white-space: nowrap !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.social-media {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    gap: 0.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.social-media a {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    width: 1.75rem;
 | 
				
			||||||
 | 
					    height: 1.75rem;
 | 
				
			||||||
 | 
					    font-size: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.social-media a {
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.social-media a:hover {
 | 
				
			||||||
 | 
					    color: var(--color-accent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.social-media a svg+span {
 | 
				
			||||||
 | 
					    display: none
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.social-media a:hover {
 | 
				
			||||||
 | 
					    transform: scale(1.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.social-media a:active {
 | 
				
			||||||
 | 
					    transform: scale(0.9);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					svg {
 | 
				
			||||||
 | 
					    fill: currentColor;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav[aria-labelledby="breadcrumb-heading"] ul {
 | 
				
			||||||
 | 
					    margin: 0 0 0.5em 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav[aria-labelledby="breadcrumb-heading"] li {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav[aria-labelledby="breadcrumb-heading"] li::after {
 | 
				
			||||||
 | 
					    --breadcrumb-arrow-size: .4em;
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    margin: 0 0.125em;
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    width: 0;
 | 
				
			||||||
 | 
					    height: 0;
 | 
				
			||||||
 | 
					    border-top: var(--breadcrumb-arrow-size) solid transparent;
 | 
				
			||||||
 | 
					    border-bottom: var(--breadcrumb-arrow-size) solid transparent;
 | 
				
			||||||
 | 
					    border-left: var(--breadcrumb-arrow-size) solid currentColor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav[aria-labelledby="breadcrumb-heading"] li:last-child::after {
 | 
				
			||||||
 | 
					    content: unset;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav[aria-labelledby="breadcrumb-heading"] a {
 | 
				
			||||||
 | 
					    color: currentColor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-label="Skip links"] {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    inset-inline: 0;
 | 
				
			||||||
 | 
					    inset-block-start: -100%;
 | 
				
			||||||
 | 
					    padding: 0.5em;
 | 
				
			||||||
 | 
					    background: Canvas;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0.5em 0 CanvasText;
 | 
				
			||||||
 | 
					    z-index: 10;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-label="Skip links"]:focus-within {
 | 
				
			||||||
 | 
					    inset-block-start: 0%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-label="Skip links"] ul {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-label="Skip links"] li {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-label="Skip links"] li+li {
 | 
				
			||||||
 | 
					    margin-inline-start: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-label="Skip links"] a {
 | 
				
			||||||
 | 
					    color: CanvasText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					footer {
 | 
				
			||||||
 | 
					    margin: 2rem auto 0 auto;
 | 
				
			||||||
 | 
					    padding: 1rem 0;
 | 
				
			||||||
 | 
					    max-width: 75rem;
 | 
				
			||||||
 | 
					    border-block-start: 0.25rem double currentColor;
 | 
				
			||||||
 | 
					    font-size: 0.9rem;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    clear: both;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					footer ul {
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body>footer nav li+li::before {
 | 
				
			||||||
 | 
					    content: "•";
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					    margin-left: 0.375em;
 | 
				
			||||||
 | 
					    margin-right: 0.375em;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    top: 0.0625em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					footer nav a {
 | 
				
			||||||
 | 
					    color: inherit !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					footer>p:last-child a {
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    width: 3rem;
 | 
				
			||||||
 | 
					    font-size: 0;
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    font-variant: small-caps;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type='radio']+label {
 | 
				
			||||||
 | 
					    display: initial;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label>span {
 | 
				
			||||||
 | 
					    margin-block-end: 0.125em;
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="checkbox"]+span,
 | 
				
			||||||
 | 
					input[type="radio"]+span {
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					    font-variant: normal;
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label:has(select) span+span {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label:has(select) span+span::after {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    inset-block-start: 50%;
 | 
				
			||||||
 | 
					    inset-inline-end: 0.75em;
 | 
				
			||||||
 | 
					    transform: translateY(-50%);
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    content: "▼";
 | 
				
			||||||
 | 
					    font-size: 0.8rem;
 | 
				
			||||||
 | 
					    line-height: 1;
 | 
				
			||||||
 | 
					    z-index: 0;
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select {
 | 
				
			||||||
 | 
					    padding: .125em 2em .125em .5em;
 | 
				
			||||||
 | 
					    border: .0625em solid currentColor;
 | 
				
			||||||
 | 
					    color: CanvasText;
 | 
				
			||||||
 | 
					    background-color: Canvas;
 | 
				
			||||||
 | 
					    --webkit-appearance: none;
 | 
				
			||||||
 | 
					    appearance: none;
 | 
				
			||||||
 | 
					    border-radius: 0.25em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type='text'],
 | 
				
			||||||
 | 
					input[type='password'] {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    max-width: 30ch;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input,
 | 
				
			||||||
 | 
					textarea {
 | 
				
			||||||
 | 
					    padding: .25em .5em;
 | 
				
			||||||
 | 
					    border: 0.0625em solid currentColor;
 | 
				
			||||||
 | 
					    color: var(--color-foreground);
 | 
				
			||||||
 | 
					    border-radius: 0.25em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					textarea {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    resize: vertical;
 | 
				
			||||||
 | 
					    min-height: 10ch;
 | 
				
			||||||
 | 
					    border-radius: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button {
 | 
				
			||||||
 | 
					    padding: .375em .625em;
 | 
				
			||||||
 | 
					    background-color: var(--color-accent, ButtonFace);
 | 
				
			||||||
 | 
					    color: var(--color-background, ButtonText);
 | 
				
			||||||
 | 
					    border-radius: 0 0.25em 0 0.25em;
 | 
				
			||||||
 | 
					    border: 0.0625em solid var(--color-foreground, ButtonBorder);
 | 
				
			||||||
 | 
					    font-variant: small-caps;
 | 
				
			||||||
 | 
					    box-shadow: 0.1875em 0.1875em 0 0 var(--color-foreground, ButtonText);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button:focus {
 | 
				
			||||||
 | 
					    box-shadow: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button:active {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    inset-inline-start: 0.1875em;
 | 
				
			||||||
 | 
					    inset-block-start: 0.1875em;
 | 
				
			||||||
 | 
					    box-shadow: inset 0 0 0.375em black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button:disabled {
 | 
				
			||||||
 | 
					    background-color: black;
 | 
				
			||||||
 | 
					    color: GrayText;
 | 
				
			||||||
 | 
					    cursor: not-allowed;
 | 
				
			||||||
 | 
					    text-decoration: line-through;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre.code {
 | 
				
			||||||
 | 
					    background-color: CanvasText;
 | 
				
			||||||
 | 
					    color: Canvas;
 | 
				
			||||||
 | 
					    padding: 0.375em 0.625em;
 | 
				
			||||||
 | 
					    border-radius: 0.375em;
 | 
				
			||||||
 | 
					    border: 0.0625em solid currentColor;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0.375em 0.0625em inset currentColor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					code {
 | 
				
			||||||
 | 
					    background-color: CanvasText;
 | 
				
			||||||
 | 
					    color: Canvas;
 | 
				
			||||||
 | 
					    padding: 0.0625em 0.375em;
 | 
				
			||||||
 | 
					    border-radius: 0.375em;
 | 
				
			||||||
 | 
					    border: 0.0625em solid currentColor;
 | 
				
			||||||
 | 
					    white-space: normal;
 | 
				
			||||||
 | 
					    word-break: break-all;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					code a {
 | 
				
			||||||
 | 
					    color: currentColor !important;
 | 
				
			||||||
 | 
					    text-decoration-color: currentColor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* === Tables === */
 | 
				
			||||||
 | 
					tbody a {
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Standard Tables */
 | 
				
			||||||
 | 
					table {
 | 
				
			||||||
 | 
					    margin: 1em -0.125em;
 | 
				
			||||||
 | 
					    border-collapse: collapse;
 | 
				
			||||||
 | 
					    border: 0.1875em solid ButtonBorder;
 | 
				
			||||||
 | 
					    min-width: 40em;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    color: CanvasText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					caption {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    padding: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th,
 | 
				
			||||||
 | 
					td {
 | 
				
			||||||
 | 
					    padding: 0.25em 0.5em 0.25em 1em;
 | 
				
			||||||
 | 
					    vertical-align: text-top;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    text-indent: -0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tr {
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					    border-block-start: 0.0625em solid GrayText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tbody tr:last-child {
 | 
				
			||||||
 | 
					    border-block-end: 0.0625em solid GrayText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th+th,
 | 
				
			||||||
 | 
					th+td,
 | 
				
			||||||
 | 
					td+td {
 | 
				
			||||||
 | 
					    border-inline-start: 0.0625em solid GrayText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					caption {
 | 
				
			||||||
 | 
					    background-color: Canvas;
 | 
				
			||||||
 | 
					    color: CanvasText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th[scope="col"] {
 | 
				
			||||||
 | 
					    vertical-align: bottom;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tbody tr:nth-child(even) {
 | 
				
			||||||
 | 
					    background-color: rgba(255, 255, 255, 0.2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tfoot tr,
 | 
				
			||||||
 | 
					tfoot th[scope="row"] {
 | 
				
			||||||
 | 
					    background-color: CanvasText;
 | 
				
			||||||
 | 
					    color: Canvas;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tfoot td {
 | 
				
			||||||
 | 
					    border-color: Canvas;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Responsive Tables with Sticky Headers
 | 
				
			||||||
 | 
					Via Adrian Roselli
 | 
				
			||||||
 | 
					https://adrianroselli.com/2020/11/under-engineered-responsive-tables.html
 | 
				
			||||||
 | 
					https://adrianroselli.com/2020/01/fixed-table-headers.html
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					[role="region"][aria-labelledby][tabindex] {
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					    border: 0.1em solid GrayText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[role="region"][aria-labelledby][tabindex]:focus {
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.5);
 | 
				
			||||||
 | 
					    outline: 0.1em solid rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[role="region"][aria-labelledby][tabindex] table {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Scrolling Visual Cue */
 | 
				
			||||||
 | 
					[role="region"][aria-labelledby][tabindex] {
 | 
				
			||||||
 | 
					    background: linear-gradient(to right, Canvas 30%, rgba(255, 255, 255, 0)), linear-gradient(to right, rgba(255, 255, 255, 0), Canvas 70%) 0 100%, radial-gradient(farthest-side at 0% 50%, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0)), radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0)) 0 100%;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-color: Canvas;
 | 
				
			||||||
 | 
					    background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
 | 
				
			||||||
 | 
					    background-position: 0 0, 100%, 0 0, 100%;
 | 
				
			||||||
 | 
					    background-attachment: local, local, scroll, scroll;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Fixed Headers */
 | 
				
			||||||
 | 
					th {
 | 
				
			||||||
 | 
					    position: sticky;
 | 
				
			||||||
 | 
					    inset-block-start: 0;
 | 
				
			||||||
 | 
					    z-index: 2;
 | 
				
			||||||
 | 
					    background-color: Canvas;
 | 
				
			||||||
 | 
					    color: CanvasText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th[scope="row"] {
 | 
				
			||||||
 | 
					    position: sticky;
 | 
				
			||||||
 | 
					    inset-inline-start: 0;
 | 
				
			||||||
 | 
					    z-index: 1;
 | 
				
			||||||
 | 
					    vertical-align: top;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th[scope="row"]::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    inset-inline-end: -0.0625em;
 | 
				
			||||||
 | 
					    inset-block: 0;
 | 
				
			||||||
 | 
					    border-inline-end: 0.0625em solid GrayText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th[scope="col"]:first-child::after {
 | 
				
			||||||
 | 
					    content: "";
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    inset-inline-end: -0.0625em;
 | 
				
			||||||
 | 
					    inset-block: 0;
 | 
				
			||||||
 | 
					    border-inline-end: 0.0625em solid GrayText;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th:not([scope="row"]):first-child {
 | 
				
			||||||
 | 
					    /*   left: 0; */
 | 
				
			||||||
 | 
					    inset-inline-start: 0;
 | 
				
			||||||
 | 
					    z-index: 3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* ====== page-specific styles ====== */
 | 
				
			||||||
 | 
					.highlight {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    gap: 1.5em;
 | 
				
			||||||
 | 
					    max-width: unset;
 | 
				
			||||||
 | 
					    background-color: var(--color-mark);
 | 
				
			||||||
 | 
					    color: black;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    margin-inline-start: -2rem;
 | 
				
			||||||
 | 
					    padding: 3rem;
 | 
				
			||||||
 | 
					    width: calc(100% + 4rem);
 | 
				
			||||||
 | 
					    box-shadow: 0 0 0.5em 0.125em currentColor inset;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.highlight a {
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.highlight>div {
 | 
				
			||||||
 | 
					    flex: 1 1 15rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.highlight>div:first-child p {
 | 
				
			||||||
 | 
					    font-size: var(--step-2);
 | 
				
			||||||
 | 
					    max-width: 40ch;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues {
 | 
				
			||||||
 | 
					    flex-basis: 15rem;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues>*+* {
 | 
				
			||||||
 | 
					    margin-block-start: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues>div:first-of-type {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues>div picture {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues>div picture:first-child {
 | 
				
			||||||
 | 
					    transform: rotate(-6deg) translateX(-50%);
 | 
				
			||||||
 | 
					    z-index: 20;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    inset-inline-start: 42%;
 | 
				
			||||||
 | 
					    inset-block-end: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues>div picture:nth-child(2) {
 | 
				
			||||||
 | 
					    z-index: 10;
 | 
				
			||||||
 | 
					    inset-block-end: 0.25em;
 | 
				
			||||||
 | 
					    transform: rotate(3deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues>div picture:nth-child(3) {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    inset-inline-start: 50%;
 | 
				
			||||||
 | 
					    transform: rotate(9deg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#recent-issues>p:first-of-type {
 | 
				
			||||||
 | 
					    margin-block-start: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[aria-labelledby="swatches-caption"] td {
 | 
				
			||||||
 | 
					    width: 5rem;
 | 
				
			||||||
 | 
					    height: 5rem;
 | 
				
			||||||
 | 
					    background-color: currentColor;
 | 
				
			||||||
 | 
					    font-size: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#masthead ul span {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.patrons {
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					    columns: 3;
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.cover {
 | 
				
			||||||
 | 
					    border: 0.0625em solid GrayText;
 | 
				
			||||||
 | 
					    border-radius: 0.1875em;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 1em -0.5em CanvasText;
 | 
				
			||||||
 | 
					    background-color: var(--color-background);
 | 
				
			||||||
 | 
					    transition: transform 0.375s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a .cover:hover {
 | 
				
			||||||
 | 
					    transform: scale(1.03);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a .cover:active {
 | 
				
			||||||
 | 
					    transform: scale(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#issues ol {
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					    gap: 2em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#issues li {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#issues li>a {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#issues p a {
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    font-weight: 700;
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@supports (display: grid) {
 | 
				
			||||||
 | 
					    #issues ol {
 | 
				
			||||||
 | 
					        display: grid;
 | 
				
			||||||
 | 
					        grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
 | 
				
			||||||
 | 
					        gap: 2em;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#issues li.legacy-issue>a {
 | 
				
			||||||
 | 
					    padding-block-start: 3em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#issues .legacy-issue picture:first-child {
 | 
				
			||||||
 | 
					    width: 60%;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    inset-block-start: -3em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#issues .legacy-issue picture:nth-child(2) {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    width: 60%;
 | 
				
			||||||
 | 
					    inset-block-end: 0;
 | 
				
			||||||
 | 
					    inset-inline-end: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.issue>header~div,
 | 
				
			||||||
 | 
					.issue-legacy>header~div>div {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-wrap: wrap;
 | 
				
			||||||
 | 
					    gap: 1.25em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.issue-legacy>header~div>div {
 | 
				
			||||||
 | 
					    gap: 1em;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.issue>header~div>div h2 {
 | 
				
			||||||
 | 
					    flex-basis: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#toc dd {
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.step-by-step-guide #toc~section {
 | 
				
			||||||
 | 
					    counter-increment: item;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.step-by-step-guide #toc~section h2::before {
 | 
				
			||||||
 | 
					    content: counter(item) ".";
 | 
				
			||||||
 | 
					    margin-inline-end: 0.25em;
 | 
				
			||||||
 | 
					    font-size: 0.625em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								www/assets/css/dark-mode.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								www/assets/css/dark-mode.css
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					    --color-background: #3a3b3f;
 | 
				
			||||||
 | 
					    --color-foreground: seashell;
 | 
				
			||||||
 | 
					    --color-accent: yellowgreen;
 | 
				
			||||||
 | 
					    --color-link: lightskyblue;
 | 
				
			||||||
 | 
					    --color-link-visited: pink;
 | 
				
			||||||
 | 
					    --color-link-active: peachpuff;
 | 
				
			||||||
 | 
					    --color-mark: lightgoldenrodyellow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    color-scheme: dark;
 | 
				
			||||||
 | 
					    accent-color: yellowgreen;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										130
									
								
								www/assets/js/paypal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								www/assets/js/paypal.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,130 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  const paypalErrors = document.querySelector("#paypal-errors");
 | 
				
			||||||
 | 
					  const buttonsContainer = document.getElementById('paypal-button-container');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function resultMessage(msg) {
 | 
				
			||||||
 | 
					    paypalErrors.innerHTML = `${msg}`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const GAME_NAME = document.querySelector('input[id="game-name"]').value;
 | 
				
			||||||
 | 
					  // const CSRF_TOKEN = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  window.paypal
 | 
				
			||||||
 | 
					    .Buttons({
 | 
				
			||||||
 | 
					      style: {
 | 
				
			||||||
 | 
					        shape: "rect",
 | 
				
			||||||
 | 
					        layout: "vertical",
 | 
				
			||||||
 | 
					        color: "gold",
 | 
				
			||||||
 | 
					        label: "paypal",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      onInit: function (data, actions) {
 | 
				
			||||||
 | 
					        buttonsContainer.style.display = 'block';
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      async createOrder() {
 | 
				
			||||||
 | 
					        paypalErrors.textContent = '';
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await fetch("/api/orders", {
 | 
				
			||||||
 | 
					            method: "POST",
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					              "Content-Type": "application/json",
 | 
				
			||||||
 | 
					              // 'X-CSRF-TOKEN': CSRF_TOKEN,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            // use the "body" param to optionally pass additional order information
 | 
				
			||||||
 | 
					            // like product ids and quantities
 | 
				
			||||||
 | 
					            body: JSON.stringify({
 | 
				
			||||||
 | 
					              payee: {
 | 
				
			||||||
 | 
					                merchant_id: "YUD9N2F66QX7A",
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              cart: [{
 | 
				
			||||||
 | 
					                description: GAME_NAME,
 | 
				
			||||||
 | 
					                quantity: "1",
 | 
				
			||||||
 | 
					                category: "DIGITAL_GOODS",
 | 
				
			||||||
 | 
					                unit_amount: {
 | 
				
			||||||
 | 
					                  currency_code: "USD",
 | 
				
			||||||
 | 
					                  value: "5.00"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              },],
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const orderData = await response.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (orderData.id) {
 | 
				
			||||||
 | 
					            return orderData.id;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          const errorDetail = orderData?.details?.[0];
 | 
				
			||||||
 | 
					          const errorMessage = errorDetail ?
 | 
				
			||||||
 | 
					            `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})` :
 | 
				
			||||||
 | 
					            JSON.stringify(orderData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          throw new Error(errorMessage);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          console.error(error);
 | 
				
			||||||
 | 
					          resultMessage(`Could not initiate PayPal Checkout.`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      async onApprove(data, actions) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response = await fetch(`/api/orders/${data.orderID}/capture`, {
 | 
				
			||||||
 | 
					            method: "POST",
 | 
				
			||||||
 | 
					            headers: {
 | 
				
			||||||
 | 
					              // 'X-CSRF-TOKEN': CSRF_TOKEN,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const orderData = await response.json();
 | 
				
			||||||
 | 
					          // Three cases to handle:
 | 
				
			||||||
 | 
					          //   (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
 | 
				
			||||||
 | 
					          //   (2) Other non-recoverable errors -> Show a failure message
 | 
				
			||||||
 | 
					          //   (3) Successful transaction -> Show confirmation or thank you message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const errorDetail = orderData?.details?.[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
 | 
				
			||||||
 | 
					            // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
 | 
				
			||||||
 | 
					            // recoverable state, per
 | 
				
			||||||
 | 
					            // https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
 | 
				
			||||||
 | 
					            return actions.restart();
 | 
				
			||||||
 | 
					          } else if (errorDetail) {
 | 
				
			||||||
 | 
					            // (2) Other non-recoverable errors -> Show a failure message
 | 
				
			||||||
 | 
					            throw new Error(`${errorDetail.description} (${orderData.debug_id})`);
 | 
				
			||||||
 | 
					          } else if (!orderData.purchase_units) {
 | 
				
			||||||
 | 
					            throw new Error(JSON.stringify(orderData));
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            // (3) Successful transaction -> Show confirmation or thank you message
 | 
				
			||||||
 | 
					            // Or go to another URL:  actions.redirect('thank_you.html');
 | 
				
			||||||
 | 
					            const transaction =
 | 
				
			||||||
 | 
					              orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
 | 
				
			||||||
 | 
					              orderData?.purchase_units?.[0]?.payments?.authorizations?.[0];
 | 
				
			||||||
 | 
					            resultMessage(
 | 
				
			||||||
 | 
					              `<b>Payment successful!</b> (Transaction ID: ${transaction.id})`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            document.querySelector("input[name='tx-id']").removeAttribute('disabled');
 | 
				
			||||||
 | 
					            document.querySelector("input[name='tx-id']").value = transaction.id;
 | 
				
			||||||
 | 
					            buttonsContainer.style.display = "none";
 | 
				
			||||||
 | 
					            // console.log(
 | 
				
			||||||
 | 
					            //   "Capture result",
 | 
				
			||||||
 | 
					            //   orderData,
 | 
				
			||||||
 | 
					            //   JSON.stringify(orderData, null, 2)
 | 
				
			||||||
 | 
					            // );
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          console.error(error);
 | 
				
			||||||
 | 
					          resultMessage(
 | 
				
			||||||
 | 
					            `Your transaction (Transaction ID: ${transaction.id}) could not be processed.`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      onError: (err) => {
 | 
				
			||||||
 | 
					        if (err === "Error: Detected popup close") { return; };
 | 
				
			||||||
 | 
					        console.error(err);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      onCancel: (data) => {
 | 
				
			||||||
 | 
					        // Show a cancel page or return to cart
 | 
				
			||||||
 | 
					        resultMessage("Payment cancelled.");
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .render("#paypal-button-container");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								www/assets/lockup.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								www/assets/lockup.svg
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 984.5 200"><title>Sixfold</title><path d="M100 0 0 57v86l100 57 100-57V57zm0 18 82 47v70l-82 47-82-47V65Z"/><path d="M100 27 27 69v62l73 42 73-42V69Zm0 6 67 38v58l-67 38-67-38V71Z"/><path d="M129 88c0-5-3-8-7-8-7 0-14 10-16 10l-1-1c0-1 3-7 3-13 0-5-2-8-8-8-4 0-7 2-7 8l1 12-1 2c-4 0-8-10-15-10-3 0-7 2-7 6 0 10 17 9 17 13 0 3-17 5-17 13 0 2 1 7 6 7 7 0 13-8 16-8l1 2-1 11c0 7 4 8 7 8 1 0 8-1 8-7l-2-13 1-2c2 0 8 10 15 10 3 0 7-3 7-7 0-11-17-9-17-13 0-3 17-4 17-12z"/><path d="M314 8q16 0 28 4t17 8l8 5-11 18-8-5q-5-4-13-7t-18-3q-17 0-26 7t-10 19q0 8 5 14t14 12l21 10 19 9q9 5 17 12t12 16q5 10 5 22 0 11-5 21-5 9-13 16-9 7-20 10t-25 4q-17 0-30-5-14-4-22-8l-10-6 12-19 7 5 11 5 14 5 18 2q20 0 30-9t10-22q0-11-7-18-6-8-17-14l-22-11q-13-6-23-13-11-7-17-16-7-10-7-23t8-23q7-10 20-16t28-6zm88 188V81h22v115zm11-156q-6 0-11-4-4-5-4-11t4-10q5-5 11-5l8 2q3 2 5 6 2 3 2 7 0 6-4 11t-11 4zm109 96 50 60h-25l-38-47-38 47h-25l50-59-45-56h26l33 42 33-42h24zm56-55h25V48q0-17 5-28 6-10 16-15 9-5 18-5 12 0 18 4l8 4-9 17-5-3q-3-2-10-2-5 0-9 2-5 2-8 9t-3 20v30h34v19h-34v96h-21v-96h-25zm150 119q-17 0-31-8t-21-22q-8-14-8-31t8-31q7-14 21-22t31-8q18 0 31 8 14 8 22 22 7 14 7 31t-7 31q-8 14-22 22-13 8-31 8zm0-19q12 0 21-6 8-5 13-15 5-9 5-21t-5-21q-5-10-13-15-9-6-21-6-11 0-20 6-9 5-14 15-5 9-5 21t5 21q5 10 14 15 9 6 20 6zm109 15h-23V4h23zm148 0V4h-23v100q-1-5-6-11-6-6-16-10-10-5-23-5-16 0-28 8-13 7-20 21-7 13-7 32 0 18 7 32t20 21q12 8 28 8 12 0 22-5t16-11q6-6 8-11v23zm-101-57q0-13 5-22 5-10 14-15t19-5q11 0 20 5t15 15q5 9 5 22t-5 22q-6 10-15 15t-20 5q-10 0-19-5t-14-15q-5-9-5-22z" aria-label="Sixfold" font-size="280" letter-spacing="1" style="line-height:1.25"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.7 KiB  | 
							
								
								
									
										8
									
								
								www/assets/sixfold.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								www/assets/sixfold.svg
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
 | 
				
			||||||
 | 
					    <title>The Sixfold logo.</title>
 | 
				
			||||||
 | 
						<desc>The logo portrays the outline of three concentric hexagons that decrease in width, alternating between black and white. A six-point asterisk with rounded edges sits at the center.</desc>
 | 
				
			||||||
 | 
						<path d="M100.004 0 0 57.465v85.08L100.004 200 200 142.531v-85.05L100.004 0zm0 18L182 65.135v69.74L100.004 182 18 134.887V65.12L100.004 18z"/>
 | 
				
			||||||
 | 
						<path d="M100.002 27.02 27.02 68.957v62.092l72.982 41.931 72.978-41.94V68.968l-72.978-41.95zm0 5.84L167.14 71.45v57.106l-67.139 38.584-67.143-38.577V71.441l67.143-38.582z"/>
 | 
				
			||||||
 | 
						<path d="M129.472 87.616c0-4.224-3.84-7.488-7.488-7.488-6.72 0-13.632 9.408-16.128 9.408a.413.413 0 0 1-.384-.384c0-1.344 2.304-7.296 2.304-12.672 0-4.992-2.112-8.832-7.488-8.832-3.84 0-7.68 1.92-7.68 8.64 0 4.608 1.728 9.792 1.728 12.096 0 1.152-.384 1.344-.96 1.344-4.608 0-8.64-10.176-14.976-10.176-3.264 0-7.296 2.688-7.296 6.72C71.104 96.448 88 94.72 88 98.56c0 3.456-17.472 5.184-17.472 13.824 0 1.344 1.344 6.72 6.144 6.72 7.104 0 13.056-8.448 16.128-8.448.96 0 1.152.96 1.152 2.112 0 2.688-1.344 7.488-1.344 11.52 0 6.336 4.608 8.064 6.912 8.064 1.92 0 8.064-1.536 8.064-7.488 0-2.688-1.728-8.448-1.728-13.248 0-1.152.192-1.728 1.344-1.728 2.112 0 7.872 10.176 14.592 10.176 3.648 0 7.296-3.072 7.296-7.488 0-10.944-17.472-9.024-17.472-12.48 0-2.88 17.856-4.416 17.856-12.48z"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										69
									
								
								www/docs/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								www/docs/index.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$title = "Documents";
 | 
				
			||||||
 | 
					$description = "Read a document.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (isset($_GET["hash"])):
 | 
				
			||||||
 | 
					    $sql = "SELECT
 | 
				
			||||||
 | 
					id,
 | 
				
			||||||
 | 
					member_id AS owner,
 | 
				
			||||||
 | 
					game_id,
 | 
				
			||||||
 | 
					basename,
 | 
				
			||||||
 | 
					title,
 | 
				
			||||||
 | 
					doc_is_public AS is_public
 | 
				
			||||||
 | 
					FROM submissions
 | 
				
			||||||
 | 
					WHERE hash = :hash
 | 
				
			||||||
 | 
					";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "hash" => $_GET["hash"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $doc = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $sql = "SELECT
 | 
				
			||||||
 | 
					member_id
 | 
				
			||||||
 | 
					FROM assignments
 | 
				
			||||||
 | 
					WHERE submission_id = :submission_id
 | 
				
			||||||
 | 
					";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "submission_id" => $doc->id,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $doc->readers = $stmt->fetchAll(PDO::FETCH_COLUMN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $IS_OWNER = LOGGED_IN ? $_SESSION["account"]->id === $doc->owner : false;
 | 
				
			||||||
 | 
					    $IS_READER = LOGGED_IN ? in_array($_SESSION["account"]->id, $doc->readers, true) : false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ($IS_OWNER || $IS_READER || IS_ADMIN || $doc->is_public) :
 | 
				
			||||||
 | 
					    header('Content-Type: application/pdf');
 | 
				
			||||||
 | 
					    header('Content-Disposition: inline; filename="' . slugify($doc->title) . '.pdf"');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    echo file_get_contents(sprintf('%s/assets/docs/%s/%s', ABS_PATH, $doc->game_id, $doc->basename));
 | 
				
			||||||
 | 
					    die;
 | 
				
			||||||
 | 
					    endif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    include "partials/head.php"; ?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <?php if (!LOGGED_IN && isset($_GET['hash']) && !$doc->is_public): ?>
 | 
				
			||||||
 | 
					        <p>You must log in to access this page.</p>
 | 
				
			||||||
 | 
					        <?php
 | 
				
			||||||
 | 
					        http_response_code(401);
 | 
				
			||||||
 | 
					        include "partials/login-form.php";
 | 
				
			||||||
 | 
					        ?>
 | 
				
			||||||
 | 
					        <?php elseif (!isset($_GET["hash"])): ?>
 | 
				
			||||||
 | 
					        <p><a href='/docs/random' class='call-to-action'>Read a random document</a></p>
 | 
				
			||||||
 | 
					        <?php endif; ?>
 | 
				
			||||||
 | 
					    </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
 | 
					    <?php
 | 
				
			||||||
 | 
					endif; ?>
 | 
				
			||||||
							
								
								
									
										12
									
								
								www/docs/random.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								www/docs/random.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$sql = "SELECT
 | 
				
			||||||
 | 
					hash
 | 
				
			||||||
 | 
					FROM submissions
 | 
				
			||||||
 | 
					WHERE doc_is_public = 1 ORDER BY RANDOM() LIMIT 1";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$hash = $db["data"]->query($sql)->fetch(PDO::FETCH_COLUMN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					http_response_code(303);
 | 
				
			||||||
 | 
					header('Location: /docs/' . $hash);
 | 
				
			||||||
 | 
					die;
 | 
				
			||||||
							
								
								
									
										16
									
								
								www/errors/404.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								www/errors/404.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$description = "The page you requested could not be found.";
 | 
				
			||||||
 | 
					$title = "Page Not Found";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					        <main id="main" class="flow">
 | 
				
			||||||
 | 
					            <header>
 | 
				
			||||||
 | 
					                <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					            </header>
 | 
				
			||||||
 | 
					            <p>The page you requested could not be found.</p>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					        <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										18
									
								
								www/errors/503.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								www/errors/503.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$description = "We're doing some maintenance right now; we'll be back shortly.";
 | 
				
			||||||
 | 
					$title = "Under Maintenance";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$NAVIGATION->header = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					        <main id="main" class="flow">
 | 
				
			||||||
 | 
					            <header>
 | 
				
			||||||
 | 
					                <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					            </header>
 | 
				
			||||||
 | 
					            <p>We're doing some maintenance right now; we'll be back shortly. In the meantime, feel free to <a href="https://www.sixfold.org/issues">read our previous issues</a>.</p>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					        <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										182
									
								
								www/forgot-password.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								www/forgot-password.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,182 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (LOGGED_IN) {
 | 
				
			||||||
 | 
					    // http_response_code(303);
 | 
				
			||||||
 | 
					    // header("Location: /");
 | 
				
			||||||
 | 
					    // die();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$title = "Reset Password";
 | 
				
			||||||
 | 
					$description = "Request a password reset email.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (isset($_GET["hash"])) {
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					        "SELECT member_id FROM password_resets WHERE hash = :hash"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "hash" => $_GET["hash"] ?? null,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $is_valid_hash = $stmt->fetch(PDO::FETCH_COLUMN) ? true : false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!$is_valid_hash) {
 | 
				
			||||||
 | 
					        $_SESSION["alert_message"] =
 | 
				
			||||||
 | 
					            "This password reset link is expired or invalid.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] === "POST"):
 | 
				
			||||||
 | 
					    define(
 | 
				
			||||||
 | 
					        "IS_RESET_REQUEST",
 | 
				
			||||||
 | 
					        $_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["email"])
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    define(
 | 
				
			||||||
 | 
					        "IS_NEW_PASSWORD",
 | 
				
			||||||
 | 
					        $_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["new-password"])
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $errors = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (IS_NEW_PASSWORD) {
 | 
				
			||||||
 | 
					        if (mb_strlen(trim($_POST["new-password"])) === 0) {
 | 
				
			||||||
 | 
					            $errors["new-password"] = "You can't have an empty password.";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($_POST["new-password"] !== $_POST["new-password-confirm"]) {
 | 
				
			||||||
 | 
					            $errors["new-password"] =
 | 
				
			||||||
 | 
					                "The newly-entered passwords do not match.";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (count($errors) === 0) {
 | 
				
			||||||
 | 
					            // get id from has lookup
 | 
				
			||||||
 | 
					            $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                "SELECT member_id FROM password_resets WHERE hash = :hash"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            $stmt->execute([
 | 
				
			||||||
 | 
					                "hash" => $_POST["hash"],
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $member_id = $stmt->fetch(PDO::FETCH_COLUMN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // update password
 | 
				
			||||||
 | 
					            $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                "UPDATE members SET password = :password WHERE id = :id;"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $stmt->execute([
 | 
				
			||||||
 | 
					                "id" => $member_id,
 | 
				
			||||||
 | 
					                "password" => password_hash(
 | 
				
			||||||
 | 
					                    $_POST["new-password"],
 | 
				
			||||||
 | 
					                    PASSWORD_ARGON2ID
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                "DELETE FROM password_resets WHERE hash = :hash;"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $stmt->execute([
 | 
				
			||||||
 | 
					                "hash" => $_POST["hash"],
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $_SESSION["alert_message"] =
 | 
				
			||||||
 | 
					                "Your password has been successfully reset.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            http_response_code(303);
 | 
				
			||||||
 | 
					            header("Location: /login");
 | 
				
			||||||
 | 
					            die();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (IS_RESET_REQUEST) {
 | 
				
			||||||
 | 
					        if (mb_strlen(trim($_POST["email"])) === 0) {
 | 
				
			||||||
 | 
					            $errors["email"] = "Please enter an email address.";
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                "SELECT id FROM members WHERE email = :email"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            $stmt->execute([
 | 
				
			||||||
 | 
					                "email" => $_POST["email"],
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					            $member_id = $stmt->fetch(PDO::FETCH_COLUMN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($member_id) {
 | 
				
			||||||
 | 
					                $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                    "INSERT OR REPLACE INTO password_resets (hash, member_id) VALUES (:hash, :member_id)"
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                $stmt->execute([
 | 
				
			||||||
 | 
					                    "hash" => bin2hex(random_bytes(32)),
 | 
				
			||||||
 | 
					                    "member_id" => $member_id,
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // send password reset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // $_SESSION["alert_message"] =
 | 
				
			||||||
 | 
					                //     "A password reset email will be sent if an account with the provided email exists.";
 | 
				
			||||||
 | 
					                http_response_code(303);
 | 
				
			||||||
 | 
					                header("Location: /forgot-password");
 | 
				
			||||||
 | 
					                die();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					endif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					        <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <?php if (isset($_SESSION["alert_message"])) { ?>
 | 
				
			||||||
 | 
					            <aside class="alert">
 | 
				
			||||||
 | 
					                <p><?= $_SESSION["alert_message"] ?></p>
 | 
				
			||||||
 | 
					                <?php unset($_SESSION["alert_message"]); ?>
 | 
				
			||||||
 | 
					            </aside>
 | 
				
			||||||
 | 
					            <?php } ?>
 | 
				
			||||||
 | 
					                <form action="<?= $_SERVER[
 | 
				
			||||||
 | 
					                    "REQUEST_URI"
 | 
				
			||||||
 | 
					                ] ?>" method="post" class="flow">
 | 
				
			||||||
 | 
					                    <?php if (isset($is_valid_hash) && $is_valid_hash): ?>
 | 
				
			||||||
 | 
					                    <?php if (
 | 
				
			||||||
 | 
					                        isset($errors) &&
 | 
				
			||||||
 | 
					                        isset($errors["new-password"])
 | 
				
			||||||
 | 
					                    ) { ?><p><mark><?= $errors[
 | 
				
			||||||
 | 
					    "new-password"
 | 
				
			||||||
 | 
					] ?></mark></p><?php } ?>
 | 
				
			||||||
 | 
					                    <label>
 | 
				
			||||||
 | 
					                        <span>New password</span>
 | 
				
			||||||
 | 
					                        <input type="password" name="new-password" value='<?= $_POST[
 | 
				
			||||||
 | 
					                            "new-password"
 | 
				
			||||||
 | 
					                        ] ?? "" ?>'/>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <label>
 | 
				
			||||||
 | 
					                        <span>Confirm new password</span>
 | 
				
			||||||
 | 
					                        <input type="password" name="new-password-confirm" value='<?= $_POST[
 | 
				
			||||||
 | 
					                            "new-password-confirm"
 | 
				
			||||||
 | 
					                        ] ?? "" ?>'/>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <input type='hidden' name='hash' value='<?= $_GET[
 | 
				
			||||||
 | 
					                        "hash"
 | 
				
			||||||
 | 
					                    ] ?>'/>
 | 
				
			||||||
 | 
					                    <button type="submit">Reset password</button>
 | 
				
			||||||
 | 
					                    <?php else: ?>
 | 
				
			||||||
 | 
					            <aside class="alert">
 | 
				
			||||||
 | 
					                <p><b>This form doesn't work yet.</b> Please mail <code>sixfold [at] sixfold.org</code> to request a reset link.</p>
 | 
				
			||||||
 | 
					            </aside>
 | 
				
			||||||
 | 
					                    <label>
 | 
				
			||||||
 | 
					                        <span>Email address <small>(email@example.com)</small></span>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["email"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "email"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <input type="email" name="email"/>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <button type="submit">Send reset email</button>
 | 
				
			||||||
 | 
					                    <?php endif; ?>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										208
									
								
								www/games/game.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								www/games/game.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,208 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$title = "Games";
 | 
				
			||||||
 | 
					$description = "View previous games.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!isset($_SESSION["account"])) {
 | 
				
			||||||
 | 
					    $title = "Log In";
 | 
				
			||||||
 | 
					    $description = "Login to view details about this vote.";
 | 
				
			||||||
 | 
					    http_response_code(401);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (LOGGED_IN && isset($_GET["game"])) {
 | 
				
			||||||
 | 
					    $sql = "SELECT games.id, games.name, games.status_id,
 | 
				
			||||||
 | 
					    games.submitstart, games.submitend,
 | 
				
			||||||
 | 
					    games.onestart, games.twostart, games.threestart, games.gameend
 | 
				
			||||||
 | 
					    FROM games
 | 
				
			||||||
 | 
					    JOIN game_status ON games.status_id = game_status.id
 | 
				
			||||||
 | 
					    WHERE games.id = :id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "id" => $_GET["game"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $game = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					    unset($stmt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $title = "Game: " . $game->name;
 | 
				
			||||||
 | 
					    $description = "View details about the " . $game->name . " vote.";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					        <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					            <?php if (!LOGGED_IN) {
 | 
				
			||||||
 | 
					                include "partials/login-form.php";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                    "SELECT * FROM assignments WHERE assignments.game_id = :game_id"
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                $stmt->execute([
 | 
				
			||||||
 | 
					                    "game_id" => $_GET["game"],
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					                $assignments = $stmt->execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                    "SELECT * FROM submissions WHERE game_id = :game_id AND member_id = :member_id"
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                $stmt->execute([
 | 
				
			||||||
 | 
					                    "game_id" => $_GET["game"],
 | 
				
			||||||
 | 
					                    "member_id" => $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					                $submission = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					                unset($stmt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ($game->status_id === STATUS_ENROLLING && $submission) {
 | 
				
			||||||
 | 
					                    $participant_state = 'GAME_OPEN_WITH_SUBMISSION';
 | 
				
			||||||
 | 
					                } elseif ($game->status_id === STATUS_ENROLLING && !$submission) {
 | 
				
			||||||
 | 
					                    $participant_state = 'GAME_OPEN_WITHOUT_SUBMISSION';
 | 
				
			||||||
 | 
					                } elseif ($submission && in_array($game->status_id, [
 | 
				
			||||||
 | 
					                    STATUS_ROUND_ONE,
 | 
				
			||||||
 | 
					                    STATUS_ROUND_TWO,
 | 
				
			||||||
 | 
					                    STATUS_ROUND_THREE,
 | 
				
			||||||
 | 
					                    STATUS_DONE,
 | 
				
			||||||
 | 
					                ])) {
 | 
				
			||||||
 | 
					                    $participant_state = 'GAME_CLOSED_WITH_SUBMISSION';
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    $participant_state = 'GAME_CLOSED_WITHOUT_SUBMISSION';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $dates = [
 | 
				
			||||||
 | 
					                    "submitstart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                        "U",
 | 
				
			||||||
 | 
					                        $game->submitstart
 | 
				
			||||||
 | 
					                    )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                    "submitend" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                        "U",
 | 
				
			||||||
 | 
					                        $game->submitend
 | 
				
			||||||
 | 
					                    )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                    "onestart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                        "U",
 | 
				
			||||||
 | 
					                        $game->onestart
 | 
				
			||||||
 | 
					                    )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                    "twostart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                        "U",
 | 
				
			||||||
 | 
					                        $game->twostart
 | 
				
			||||||
 | 
					                    )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                    "threestart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                        "U",
 | 
				
			||||||
 | 
					                        $game->threestart
 | 
				
			||||||
 | 
					                    )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                    "gameend" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                        "U",
 | 
				
			||||||
 | 
					                        $game->gameend
 | 
				
			||||||
 | 
					                    )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					                ?>
 | 
				
			||||||
 | 
					            <article>
 | 
				
			||||||
 | 
					            <div class="flow">
 | 
				
			||||||
 | 
					                <h2>Your Submission</h2>
 | 
				
			||||||
 | 
					                <?php
 | 
				
			||||||
 | 
					                switch ($participant_state) {
 | 
				
			||||||
 | 
					                    case 'GAME_OPEN_WITH_SUBMISSION': ?>
 | 
				
			||||||
 | 
					                    <p><a href='/docs/<?= $submission->hash ?>'><?= $submission->title ?></a></p>
 | 
				
			||||||
 | 
					                    <p><a href="/games/<?= $game->id ?>/update" class='call-to-action'>Update submission</a></p>
 | 
				
			||||||
 | 
					                    <?php break;
 | 
				
			||||||
 | 
					                    case 'GAME_OPEN_WITHOUT_SUBMISSION': ?>
 | 
				
			||||||
 | 
					                    <p>You haven't submitted work to this contest.</p>
 | 
				
			||||||
 | 
					                    <p><a href="/games/<?= $game->id ?>/submit" class='call-to-action'>Submit to <?= $game->name ?></a></p>
 | 
				
			||||||
 | 
					                    <?php break;
 | 
				
			||||||
 | 
					                    case 'GAME_CLOSED_WITH_SUBMISSION': ?>
 | 
				
			||||||
 | 
					                    <?php include "partials/submission-info.php"; ?>
 | 
				
			||||||
 | 
					                    <p><a href='/games/<?= $game->id ?>/update' class='call-to-action'>Update submission visibility</a></p>
 | 
				
			||||||
 | 
					                    <?php include "partials/feedback.php"; ?>
 | 
				
			||||||
 | 
					                    <?php include "partials/assignments.php"; ?>
 | 
				
			||||||
 | 
					                    <?php break;
 | 
				
			||||||
 | 
					                    case 'GAME_CLOSED_WITHOUT_SUBMISSION': ?>
 | 
				
			||||||
 | 
					                    <p>You didn't submit a work to this contest.</p>
 | 
				
			||||||
 | 
					                    <?php break;
 | 
				
			||||||
 | 
					                    default: ?>
 | 
				
			||||||
 | 
					                    <?php break;
 | 
				
			||||||
 | 
					                } ?>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					                <hr/>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <h2>Schedule</h2>
 | 
				
			||||||
 | 
					                <dl class="flow" data-game-status="<?= $game->status_id ?>">
 | 
				
			||||||
 | 
					                    <div data-status="<?= STATUS_ENROLLING ?>">
 | 
				
			||||||
 | 
					                    <dt>Submissions</dt>
 | 
				
			||||||
 | 
					                    <dd><span>Open: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "submitstart"
 | 
				
			||||||
 | 
					                    ]->format("c") ?>"><?= $dates["submitstart"]->format(
 | 
				
			||||||
 | 
					    'l, j F, o \a\t h:i A T'
 | 
				
			||||||
 | 
					) ?></time></dd>
 | 
				
			||||||
 | 
					                    <dd><span>Close: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "submitend"
 | 
				
			||||||
 | 
					                    ]->format("c") ?>"><?= $dates["submitend"]->format(
 | 
				
			||||||
 | 
					    'l, j F, o \a\t h:i A T'
 | 
				
			||||||
 | 
					) ?></time></dd>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div data-status="<?= STATUS_REVIEW ?>">
 | 
				
			||||||
 | 
					                    <dt>Document Review</dt>
 | 
				
			||||||
 | 
					                    <dd><span>Start: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "submitend"
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                        ->add($one_second)
 | 
				
			||||||
 | 
					                        ->format("c") ?>"><?= $dates["submitend"]
 | 
				
			||||||
 | 
					    ->add($one_second)
 | 
				
			||||||
 | 
					    ->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                    <dd><span>End: </span><time datetime="<?= $dates["onestart"]
 | 
				
			||||||
 | 
					                        ->sub($one_second)
 | 
				
			||||||
 | 
					                        ->format("c") ?>"><?= $dates["onestart"]
 | 
				
			||||||
 | 
					    ->sub($one_second)
 | 
				
			||||||
 | 
					    ->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div data-status="<?= STATUS_ROUND_ONE ?>">
 | 
				
			||||||
 | 
					                    <dt>Round One</dt>
 | 
				
			||||||
 | 
					                    <dd><span>Start: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "onestart"
 | 
				
			||||||
 | 
					                    ]->format("c") ?>"><?= $dates["onestart"]->format(
 | 
				
			||||||
 | 
					    'l, j F, o \a\t h:i A T'
 | 
				
			||||||
 | 
					) ?></time></dd>
 | 
				
			||||||
 | 
					                    <dd><span>End: </span><time datetime="<?= $dates["twostart"]
 | 
				
			||||||
 | 
					                        ->sub($one_second)
 | 
				
			||||||
 | 
					                        ->format("c") ?>"><?= $dates["twostart"]
 | 
				
			||||||
 | 
					    ->sub($one_second)
 | 
				
			||||||
 | 
					    ->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div data-status="<?= STATUS_ROUND_TWO ?>">
 | 
				
			||||||
 | 
					                    <dt>Round Two</dt>
 | 
				
			||||||
 | 
					                    <dd><span>Start: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "twostart"
 | 
				
			||||||
 | 
					                    ]->format("c") ?>"><?= $dates["twostart"]->format(
 | 
				
			||||||
 | 
					    'l, j F, o \a\t h:i A T'
 | 
				
			||||||
 | 
					) ?></time></dd>
 | 
				
			||||||
 | 
					                    <dd><span>End: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "threestart"
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                        ->sub($one_second)
 | 
				
			||||||
 | 
					                        ->format("c") ?>"><?= $dates["threestart"]
 | 
				
			||||||
 | 
					    ->sub($one_second)
 | 
				
			||||||
 | 
					    ->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div data-status="<?= STATUS_ROUND_THREE ?>">
 | 
				
			||||||
 | 
					                    <dt>Round Three</dt>
 | 
				
			||||||
 | 
					                    <dd><span>Start: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "threestart"
 | 
				
			||||||
 | 
					                    ]->format("c") ?>"><?= $dates["threestart"]->format(
 | 
				
			||||||
 | 
					    'l, j F, o \a\t h:i A T'
 | 
				
			||||||
 | 
					) ?></time></dd>
 | 
				
			||||||
 | 
					                    <dd><span>End: </span><time datetime="<?= $dates[
 | 
				
			||||||
 | 
					                        "gameend"
 | 
				
			||||||
 | 
					                    ]->format("c") ?>"><?= $dates["gameend"]->format(
 | 
				
			||||||
 | 
					    'l, j F, o \a\t h:i A T'
 | 
				
			||||||
 | 
					) ?></time></dd>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </dl>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            </article>
 | 
				
			||||||
 | 
					            <?php
 | 
				
			||||||
 | 
					            } ?>
 | 
				
			||||||
 | 
					            </main>
 | 
				
			||||||
 | 
					        <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										118
									
								
								www/games/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								www/games/index.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,118 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$title = "Games";
 | 
				
			||||||
 | 
					$description = "View previous games.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!isset($_SESSION["account"])) {
 | 
				
			||||||
 | 
					    $title = "Log In";
 | 
				
			||||||
 | 
					    $description = "Login to view details about this vote.";
 | 
				
			||||||
 | 
					    http_response_code(401);
 | 
				
			||||||
 | 
					} elseif (isset($_GET["game"])) {
 | 
				
			||||||
 | 
					    $sql = "SELECT games.id, games.name, games.submitstart, games.status_id, games.submitend, games.onestart, games.twostart, games.threestart, games.gameend FROM games
 | 
				
			||||||
 | 
					    JOIN game_status ON games.status_id = game_status.id
 | 
				
			||||||
 | 
					    WHERE games.id = :id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "id" => $_GET["game"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    $game = $stmt->fetch();
 | 
				
			||||||
 | 
					    unset($stmt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $title = "Game: " . $game["name"];
 | 
				
			||||||
 | 
					    $description = "View details about the " . $game["name"] . " vote.";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					        <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <div class="games">
 | 
				
			||||||
 | 
					            <?php if (!LOGGED_IN) {
 | 
				
			||||||
 | 
					                include "partials/login-form.php";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                $sql = "SELECT games.id, games.name, games.submitstart, games.status_id, games.submitend, games.onestart, games.twostart, games.threestart, games.gameend
 | 
				
			||||||
 | 
					                    FROM games
 | 
				
			||||||
 | 
					                    JOIN game_status ON games.status_id = game_status.id
 | 
				
			||||||
 | 
					                    WHERE games.status_id != 8
 | 
				
			||||||
 | 
					                    ORDER BY games.id DESC";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					                $stmt->execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                while ($game = $stmt->fetch()) {
 | 
				
			||||||
 | 
					                    static $one_second = new DateInterval("PT1S");
 | 
				
			||||||
 | 
					                    static $time_zone = new DateTimeZone("America/New_York");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $stmt2 = $db["data"]->prepare('SELECT rank_round_1 AS one, rank_round_2 AS two, rank_round_3 AS three, rank_final AS final FROM submissions
 | 
				
			||||||
 | 
					                    WHERE game_id = :game_id AND member_id = :member_id');
 | 
				
			||||||
 | 
					                    $stmt2->execute([
 | 
				
			||||||
 | 
					                        'game_id' => $game['id'],
 | 
				
			||||||
 | 
					                        'member_id' => $_SESSION['account']->id,
 | 
				
			||||||
 | 
					                    ]);
 | 
				
			||||||
 | 
					                    $ranks = $stmt2->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $dates = [
 | 
				
			||||||
 | 
					                        "submitstart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                            "U",
 | 
				
			||||||
 | 
					                            $game["submitstart"]
 | 
				
			||||||
 | 
					                        )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                        "submitend" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                            "U",
 | 
				
			||||||
 | 
					                            $game["submitend"]
 | 
				
			||||||
 | 
					                        )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                        "onestart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                            "U",
 | 
				
			||||||
 | 
					                            $game["onestart"]
 | 
				
			||||||
 | 
					                        )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                        "twostart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                            "U",
 | 
				
			||||||
 | 
					                            $game["twostart"]
 | 
				
			||||||
 | 
					                        )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                        "threestart" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                            "U",
 | 
				
			||||||
 | 
					                            $game["threestart"]
 | 
				
			||||||
 | 
					                        )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                        "gameend" => DateTimeImmutable::createFromFormat(
 | 
				
			||||||
 | 
					                            "U",
 | 
				
			||||||
 | 
					                            $game["gameend"]
 | 
				
			||||||
 | 
					                        )->setTimezone($time_zone),
 | 
				
			||||||
 | 
					                    ];
 | 
				
			||||||
 | 
					                    ?>
 | 
				
			||||||
 | 
					                <article>
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
 | 
					                        <h3><?= $game["name"] ?></h3>
 | 
				
			||||||
 | 
					                        <p><span>Status: </span><?= get_status_message(
 | 
				
			||||||
 | 
					                            $game["status_id"]
 | 
				
			||||||
 | 
					                        ) ?></p>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            $game["status_id"] === STATUS_DONE
 | 
				
			||||||
 | 
					                        ) { ?><p><span>Your ranking: </span><?= $ranks->one ?? $ranks->two ?? $ranks->three ?? $ranks->final ?? 'N/A' ?></p><?php } ?>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="flow">
 | 
				
			||||||
 | 
					                        <?php if ($game["status_id"] !== STATUS_DELAYED) { ?>
 | 
				
			||||||
 | 
					                            <p><a href="/games/<?= $game[
 | 
				
			||||||
 | 
					                                "id"
 | 
				
			||||||
 | 
					                            ] ?>" class="call-to-action"><?= $game[
 | 
				
			||||||
 | 
					    "name"
 | 
				
			||||||
 | 
					] ?> details</a></p>
 | 
				
			||||||
 | 
					                        <?php } ?>
 | 
				
			||||||
 | 
					                        <?php if ($game["status_id"] === STATUS_DONE) { ?>
 | 
				
			||||||
 | 
					                            <p><a href="/games/<?= $game[
 | 
				
			||||||
 | 
					                                "id"
 | 
				
			||||||
 | 
					                            ] ?>/results" class="call-to-action"><?= $game[
 | 
				
			||||||
 | 
					    "name"
 | 
				
			||||||
 | 
					] ?> results</a></p>
 | 
				
			||||||
 | 
					                        <?php } ?>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                </article>
 | 
				
			||||||
 | 
					            <?php
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } ?>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					        <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										208
									
								
								www/games/process-order.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								www/games/process-order.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,208 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generate an OAuth 2.0 access token for authenticating with PayPal REST APIs.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see https://developer.paypal.com/api/rest/authentication/
 | 
				
			||||||
 | 
					 * @return string|null
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function generateAccessToken()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (!PAYPAL_CLIENT_ID || !PAYPAL_CLIENT_SECRET) {
 | 
				
			||||||
 | 
					        throw new \Exception("MISSING_API_CREDENTIALS");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $url = PAYPAL_BASE_URL . "/v1/oauth2/token";
 | 
				
			||||||
 | 
					    $payload = [
 | 
				
			||||||
 | 
					        "grant_type" => "client_credentials",
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    $ch = curl_init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_URL, $url);
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_POST, 1);
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_HTTPHEADER, [
 | 
				
			||||||
 | 
					        "Content-Type: application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
					        "Authorization: Basic " .
 | 
				
			||||||
 | 
					        base64_encode(PAYPAL_CLIENT_ID . ":" . PAYPAL_CLIENT_SECRET),
 | 
				
			||||||
 | 
					        // Uncomment one of these to force an error for negative testing (in sandbox mode only).
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
				
			||||||
 | 
					    $response = curl_exec($ch);
 | 
				
			||||||
 | 
					    $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
				
			||||||
 | 
					    curl_close($ch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Further processing ...
 | 
				
			||||||
 | 
					    if ($response_code === 200) {
 | 
				
			||||||
 | 
					        return json_decode($response)->access_token;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        header("Content-Type: application/json");
 | 
				
			||||||
 | 
					        http_response_code($response_code);
 | 
				
			||||||
 | 
					        echo $response;
 | 
				
			||||||
 | 
					        die();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Create an order to start the transaction.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see https://developer.paypal.com/docs/api/orders/v2/#orders_create
 | 
				
			||||||
 | 
					 * @param array $cart
 | 
				
			||||||
 | 
					 * @return array
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function createOrder($cart)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        $access_token = generateAccessToken();
 | 
				
			||||||
 | 
					        if (!$access_token) {
 | 
				
			||||||
 | 
					            http_response_code(500);
 | 
				
			||||||
 | 
					            header("Content-Type: application/json");
 | 
				
			||||||
 | 
					            echo json_encode(["error" => "Failed to obtain access token."]);
 | 
				
			||||||
 | 
					            die();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $url = PAYPAL_BASE_URL . "/v2/checkout/orders";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $payload = [
 | 
				
			||||||
 | 
					            "intent" => "CAPTURE",
 | 
				
			||||||
 | 
					            "purchase_units" => [
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    "amount" => [
 | 
				
			||||||
 | 
					                        "currency_code" => $cart[0]->unit_amount->currency_code,
 | 
				
			||||||
 | 
					                        "value" => $cart[0]->unit_amount->value,
 | 
				
			||||||
 | 
					                        "breakdown" => [
 | 
				
			||||||
 | 
					                            "item_total" => [
 | 
				
			||||||
 | 
					                                "currency_code" =>
 | 
				
			||||||
 | 
					                                    $cart[0]->unit_amount->currency_code,
 | 
				
			||||||
 | 
					                                "value" => $cart[0]->unit_amount->value,
 | 
				
			||||||
 | 
					                            ],
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    "items" => [
 | 
				
			||||||
 | 
					                        [
 | 
				
			||||||
 | 
					                            "name" => $cart[0]->description,
 | 
				
			||||||
 | 
					                            "description" => $cart[0]->description,
 | 
				
			||||||
 | 
					                            "unit_amount" => [
 | 
				
			||||||
 | 
					                                "currency_code" =>
 | 
				
			||||||
 | 
					                                    $cart[0]->unit_amount->currency_code,
 | 
				
			||||||
 | 
					                                "value" => $cart[0]->unit_amount->value,
 | 
				
			||||||
 | 
					                            ],
 | 
				
			||||||
 | 
					                            "quantity" => 1,
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $ch = curl_init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        curl_setopt($ch, CURLOPT_URL, $url);
 | 
				
			||||||
 | 
					        curl_setopt($ch, CURLOPT_POST, 1);
 | 
				
			||||||
 | 
					        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
 | 
				
			||||||
 | 
					        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
 | 
				
			||||||
 | 
					        curl_setopt($ch, CURLOPT_HTTPHEADER, [
 | 
				
			||||||
 | 
					            "Content-Type: application/json",
 | 
				
			||||||
 | 
					            "Authorization: Bearer $access_token",
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        // Uncomment one of these to force an error for negative testing (in sandbox mode only).
 | 
				
			||||||
 | 
					        // Documentation: https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/
 | 
				
			||||||
 | 
					        // 'PayPal-Mock-Response' => '{"mock_application_codes": "MISSING_
 | 
				
			||||||
 | 
					        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
				
			||||||
 | 
					        $response = curl_exec($ch);
 | 
				
			||||||
 | 
					        $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
				
			||||||
 | 
					        curl_close($ch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Further processing ...
 | 
				
			||||||
 | 
					        if ($response_code === 200) {
 | 
				
			||||||
 | 
					            header("Content-Type: application/json");
 | 
				
			||||||
 | 
					            http_response_code($response_code);
 | 
				
			||||||
 | 
					            echo $response;
 | 
				
			||||||
 | 
					            die();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            header("Content-Type: application/json");
 | 
				
			||||||
 | 
					            http_response_code($response_code);
 | 
				
			||||||
 | 
					            echo $response;
 | 
				
			||||||
 | 
					            die();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } catch (\Exception $error) {
 | 
				
			||||||
 | 
					        header("Content-Type: application/json");
 | 
				
			||||||
 | 
					        http_response_code(500);
 | 
				
			||||||
 | 
					        echo json_encode(["error" => "Failed to create order."]);
 | 
				
			||||||
 | 
					        die();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Capture payment for the given order
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture
 | 
				
			||||||
 | 
					 * @param array $cart
 | 
				
			||||||
 | 
					 * @return array
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function captureOrder($order_id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    $url = PAYPAL_BASE_URL . "/v2/checkout/orders/{$order_id}/capture";
 | 
				
			||||||
 | 
					    // Http::fake(function ($request) {
 | 
				
			||||||
 | 
					    //     // Capture and log request headers
 | 
				
			||||||
 | 
					    //     $headers = $request->headers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //     // Log headers for inspection
 | 
				
			||||||
 | 
					    //     \Log::info('Captured Request Headers', $headers);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //     return Http::response('', 200, [
 | 
				
			||||||
 | 
					    //         'X-Custom-Response-Header' => 'HeaderValue'
 | 
				
			||||||
 | 
					    //     ]);
 | 
				
			||||||
 | 
					    // });
 | 
				
			||||||
 | 
					    $auth = base64_encode(PAYPAL_CLIENT_ID . ":" . PAYPAL_CLIENT_SECRET);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $ch = curl_init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_URL, $url);
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_POST, 1);
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_HTTPHEADER, [
 | 
				
			||||||
 | 
					        "Content-Type: application/json",
 | 
				
			||||||
 | 
					        "Authorization: Basic $auth",
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    // Uncomment one of these to force an error for negative testing (in sandbox mode only).
 | 
				
			||||||
 | 
					    // Documentation: https://developer.paypal.com/tools/sandbox/negative-testing/request-headers/
 | 
				
			||||||
 | 
					    // 'PayPal-Mock-Response' => '{"mock_application_codes": "MISSING_
 | 
				
			||||||
 | 
					    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 | 
				
			||||||
 | 
					    $response = curl_exec($ch);
 | 
				
			||||||
 | 
					    $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
				
			||||||
 | 
					    curl_close($ch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Further processing ...
 | 
				
			||||||
 | 
					    if ($response_code === 200) {
 | 
				
			||||||
 | 
					        header("Content-Type: application/json");
 | 
				
			||||||
 | 
					        http_response_code($response_code);
 | 
				
			||||||
 | 
					        echo $response;
 | 
				
			||||||
 | 
					        die();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        header("Content-Type: application/json");
 | 
				
			||||||
 | 
					        http_response_code($response_code);
 | 
				
			||||||
 | 
					        echo $response;
 | 
				
			||||||
 | 
					        die();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (
 | 
				
			||||||
 | 
					    $_SERVER["REQUEST_METHOD"] === "POST" &&
 | 
				
			||||||
 | 
					    $_SERVER["REQUEST_URI"] === "/api/orders"
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    $body = file_get_contents("php://input");
 | 
				
			||||||
 | 
					    $cart = json_decode($body)->cart;
 | 
				
			||||||
 | 
					    createOrder($cart);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] === "POST") {
 | 
				
			||||||
 | 
					    $order_id = substr($_SERVER["REQUEST_URI"], 12, 17);
 | 
				
			||||||
 | 
					    captureOrder($order_id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] !== "POST") {
 | 
				
			||||||
 | 
					    http_response_code(404);
 | 
				
			||||||
 | 
					    die();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										140
									
								
								www/games/results.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								www/games/results.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,140 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$stmt = $db["data"]->prepare("SELECT name FROM games WHERE id = :id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$stmt->execute([
 | 
				
			||||||
 | 
					    "id" => $_GET["game"],
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$title = "Results: " . $stmt->fetch(PDO::FETCH_COLUMN);
 | 
				
			||||||
 | 
					$description = "View the results of particular issue's voting.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$stmt = $db["data"]
 | 
				
			||||||
 | 
					    ->prepare('SELECT submissions.id AS id, submissions.hash, members.name, members.handle,
 | 
				
			||||||
 | 
					    CASE WHEN name_is_public = 1 AND name IS NOT NULL
 | 
				
			||||||
 | 
					        THEN name
 | 
				
			||||||
 | 
					        ELSE CONCAT("Member ", submissions.member_id)
 | 
				
			||||||
 | 
					    END AS author,
 | 
				
			||||||
 | 
					    CASE WHEN doc_is_public = 1
 | 
				
			||||||
 | 
					        THEN title
 | 
				
			||||||
 | 
					        ELSE CONCAT("Entry ", submissions.id)
 | 
				
			||||||
 | 
					    END AS title
 | 
				
			||||||
 | 
					    , doc_is_public, name_is_public, score_round_1, rank_round_1, score_round_2, rank_round_2, score_round_3, rank_round_3, rank_final, votes_round_1, votes_round_2, votes_round_3, num_assignments_round_1, num_assignments_round_2, num_assignments_round_3
 | 
				
			||||||
 | 
					    FROM submissions
 | 
				
			||||||
 | 
					    JOIN members ON submissions.member_id = members.id
 | 
				
			||||||
 | 
					    WHERE submissions.game_id = :id AND rank_final IS NOT NULL AND submissions.transaction_id IS NOT NULL ORDER BY rank_final ASC');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$results = $stmt->execute([
 | 
				
			||||||
 | 
					    "id" => $_GET["game"],
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					$row = $stmt->fetch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!$row) {
 | 
				
			||||||
 | 
					    $title = "No Results Found";
 | 
				
			||||||
 | 
					    $description = "We couldn't find the game results you requested.";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					        <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <?php if ($row): ?>
 | 
				
			||||||
 | 
					    <!-- <p><a href="/resulthowto.html">How to Read the Results</a></p> -->
 | 
				
			||||||
 | 
					            <div role="region" aria-labelledby="results-caption" tabindex="0">
 | 
				
			||||||
 | 
					                <table>
 | 
				
			||||||
 | 
					                    <caption id="results-caption">Results for Game <?= $_GET[
 | 
				
			||||||
 | 
					                        "game"
 | 
				
			||||||
 | 
					                    ] ?></caption>
 | 
				
			||||||
 | 
					                <thead>
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <th scope="col" rowspan="3">Title</th>
 | 
				
			||||||
 | 
					                        <th scope="col" rowspan="3">Author</th>
 | 
				
			||||||
 | 
					                        <th scope="col" colspan="3">Score</th>
 | 
				
			||||||
 | 
					                        <th scope="col" colspan="3">Rank</th>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                        <th scope="col">Round One</th>
 | 
				
			||||||
 | 
					                        <th scope="col">Round Two</th>
 | 
				
			||||||
 | 
					                        <th scope="col">Round Three</th>
 | 
				
			||||||
 | 
					                        <th scope="col">Round One</th>
 | 
				
			||||||
 | 
					                        <th scope="col">Round Two</th>
 | 
				
			||||||
 | 
					                        <th scope="col">Round Three</th>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                </thead>
 | 
				
			||||||
 | 
					                <tbody>
 | 
				
			||||||
 | 
					                    <?php do {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $is_booted =
 | 
				
			||||||
 | 
					                            ($row["rank_round_1"] &&
 | 
				
			||||||
 | 
					                                $row["num_assignments_round_1"] &&
 | 
				
			||||||
 | 
					                                !$row["votes_round_1"]) ||
 | 
				
			||||||
 | 
					                            ($row["rank_round_2"] &&
 | 
				
			||||||
 | 
					                                $row["num_assignments_round_2"] &&
 | 
				
			||||||
 | 
					                                !$row["votes_round_2"]) ||
 | 
				
			||||||
 | 
					                            ($row["rank_round_3"] &&
 | 
				
			||||||
 | 
					                                $row["num_assignments_round_3"] &&
 | 
				
			||||||
 | 
					                                !$row["votes_round_3"])
 | 
				
			||||||
 | 
					                                ? true
 | 
				
			||||||
 | 
					                                : false;
 | 
				
			||||||
 | 
					                        $is_autoadvance =
 | 
				
			||||||
 | 
					                            ($row["votes_round_1"] &&
 | 
				
			||||||
 | 
					                                $row["votes_round_1"] < 4) ||
 | 
				
			||||||
 | 
					                            ($row["votes_round_2"] && $row["votes_round_2"] < 4)
 | 
				
			||||||
 | 
					                                ? true
 | 
				
			||||||
 | 
					                                : false;
 | 
				
			||||||
 | 
					                        $has_visible_doc = $row["doc_is_public"] || IS_ADMIN;
 | 
				
			||||||
 | 
					                        $doc_title = htmlspecialchars(
 | 
				
			||||||
 | 
					                            $row["title"],
 | 
				
			||||||
 | 
					                            ENT_QUOTES
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        $doc_url = sprintf("/docs/%s", $row["hash"]);
 | 
				
			||||||
 | 
					                        $author = htmlspecialchars($row["author"], ENT_QUOTES);
 | 
				
			||||||
 | 
					                        $profile_url = "/members/" . $row["handle"];
 | 
				
			||||||
 | 
					                        ?>
 | 
				
			||||||
 | 
					                        <tr <?= $is_booted ? 'class="booted"' : "" ?>>
 | 
				
			||||||
 | 
					                            <th scope="row">
 | 
				
			||||||
 | 
					                                <?= $has_visible_doc
 | 
				
			||||||
 | 
					                                    ? "<a href='$doc_url'>$doc_title</a>"
 | 
				
			||||||
 | 
					                                    : $doc_title ?>
 | 
				
			||||||
 | 
					                                <?php if (
 | 
				
			||||||
 | 
					                                    $is_booted
 | 
				
			||||||
 | 
					                                ) { ?><small>(disqualified)</small><?php } ?>
 | 
				
			||||||
 | 
					                            </th>
 | 
				
			||||||
 | 
					                            <td><?= $row["name_is_public"]
 | 
				
			||||||
 | 
					                                ? "<a href='$profile_url'>$author</a>"
 | 
				
			||||||
 | 
					                                : $author ?></td>
 | 
				
			||||||
 | 
					                            <td <?= is_null($row["score_round_1"])
 | 
				
			||||||
 | 
					                                ? 'class="empty"'
 | 
				
			||||||
 | 
					                                : "" ?>><?= is_null($row["score_round_1"])
 | 
				
			||||||
 | 
					    ? 0
 | 
				
			||||||
 | 
					    : $row["score_round_1"] ?></td>
 | 
				
			||||||
 | 
					                            <td <?= is_null($row["score_round_2"])
 | 
				
			||||||
 | 
					                                ? 'class="empty"'
 | 
				
			||||||
 | 
					                                : "" ?>><?= is_null($row["score_round_2"])
 | 
				
			||||||
 | 
					    ? 0
 | 
				
			||||||
 | 
					    : $row["score_round_2"] ?></td>
 | 
				
			||||||
 | 
					                            <td <?= is_null($row["score_round_3"])
 | 
				
			||||||
 | 
					                                ? 'class="empty"'
 | 
				
			||||||
 | 
					                                : "" ?>><?= is_null($row["score_round_3"])
 | 
				
			||||||
 | 
					    ? 0
 | 
				
			||||||
 | 
					    : $row["score_round_3"] ?></td>
 | 
				
			||||||
 | 
					                            <td><?= $row["rank_round_1"] ?></td>
 | 
				
			||||||
 | 
					                            <td><?= $row["rank_round_2"] ?></td>
 | 
				
			||||||
 | 
					                            <td><?= $row["rank_round_3"] ?></td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    <?php
 | 
				
			||||||
 | 
					                    } while ($row = $stmt->fetch()); ?>
 | 
				
			||||||
 | 
					                </tbody>
 | 
				
			||||||
 | 
					                <tfoot>
 | 
				
			||||||
 | 
					                </tfoot>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <?php else: ?>
 | 
				
			||||||
 | 
					        <p><?= $description ?></p>
 | 
				
			||||||
 | 
					        <?php endif; ?>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										301
									
								
								www/games/submit.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								www/games/submit.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,301 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!LOGGED_IN) {
 | 
				
			||||||
 | 
					    $title = "Log In";
 | 
				
			||||||
 | 
					    $description = "Log in to enter a work into this vote.";
 | 
				
			||||||
 | 
					    http_response_code(401);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (LOGGED_IN && isset($_GET["game"])) {
 | 
				
			||||||
 | 
					    $sql = "SELECT id FROM submissions
 | 
				
			||||||
 | 
					    WHERE game_id = :game_id AND member_id = :member_id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "game_id" => $_GET["game"],
 | 
				
			||||||
 | 
					        "member_id" => $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $HAS_SUBMISSION = $stmt->fetch(PDO::FETCH_COLUMN) !== false;
 | 
				
			||||||
 | 
					    if ($HAS_SUBMISSION) {
 | 
				
			||||||
 | 
					        http_response_code(303);
 | 
				
			||||||
 | 
					        header("Location: /games/" . $_GET["game"] . "/update");
 | 
				
			||||||
 | 
					        die();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $sql = "SELECT id, name, status_id FROM games
 | 
				
			||||||
 | 
					    WHERE id = :id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "id" => $_GET["game"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    $game = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $title = "Submit: {$game->name}";
 | 
				
			||||||
 | 
					    $description = "Enter a work into the " . $game->name . " vote.";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] === "POST"):
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare("SELECT id FROM games WHERE id = :id");
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "id" => $_GET["game"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $game_id = $stmt->fetch(PDO::FETCH_COLUMN) ?? false;
 | 
				
			||||||
 | 
					    $errors = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $RULES_FOLLOWED =
 | 
				
			||||||
 | 
					        isset($_POST["agree-toc"]) &&
 | 
				
			||||||
 | 
					        $_POST["agree-toc"] === "1" &&
 | 
				
			||||||
 | 
					        (isset($_POST["agree-guidelines"]) &&
 | 
				
			||||||
 | 
					            $_POST["agree-guidelines"] === "1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!$RULES_FOLLOWED) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					        $errors["agreements"] =
 | 
				
			||||||
 | 
					            "Please accept the Terms & Conditions and the Submission Guidelines.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ($_FILES["manuscript"]["size"] === 0) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					        $errors["filesize"] = "A file upload is required.";
 | 
				
			||||||
 | 
					    } elseif ($_FILES["manuscript"]["size"] > UPLOAD_MAX_FILESIZE) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					        $errors["filesize"] = "Your document is too large.";
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        $finfo = finfo_open(FILEINFO_MIME_TYPE);
 | 
				
			||||||
 | 
					        $mime_type = finfo_file($finfo, $_FILES["manuscript"]["tmp_name"]);
 | 
				
			||||||
 | 
					        finfo_close($finfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $PROPER_MIMETYPE = $mime_type === "application/pdf";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isset($PROPER_MIMETYPE) && !$PROPER_MIMETYPE) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					        $errors["mimetype"] = "Only PDF submissions are allowed.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isset($_POST["title"]) || !trim($_POST["title"])) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					        $errors["title"] = "Please enter a title.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isset($_SESSION["account"]) && !isset($_SESSION["account"]->id)) {
 | 
				
			||||||
 | 
					        http_response_code(500);
 | 
				
			||||||
 | 
					        $errors["account"] =
 | 
				
			||||||
 | 
					            "We can't upload a document without knowing which account it belongs to.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!$game_id) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					        $errors["game"] = "The chosen game doesn't exist.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isset($_POST["tx-id"])) {
 | 
				
			||||||
 | 
					        $errors["payment"] = "No transaction ID was provided.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (count($errors) > 0) {
 | 
				
			||||||
 | 
					        if (!isset($errors["filesize"]) && !isset($errors["mimetype"])) {
 | 
				
			||||||
 | 
					            $errors["upload"] =
 | 
				
			||||||
 | 
					                "Fix all other errors and choose your file again.";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        $new_basename =
 | 
				
			||||||
 | 
					            md5(microtime() . $game_id . $_SESSION["account"]->id) . ".pdf";
 | 
				
			||||||
 | 
					        $lookup_hash = md5(
 | 
				
			||||||
 | 
					            $_SESSION["account"]->id . $game_id . microtime() . "salt"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $file_destination = sprintf(
 | 
				
			||||||
 | 
					            "%s/%s/%s",
 | 
				
			||||||
 | 
					            DIRECTORY_DOCS,
 | 
				
			||||||
 | 
					            $game_id,
 | 
				
			||||||
 | 
					            $new_basename
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $file_moved = move_uploaded_file(
 | 
				
			||||||
 | 
					                $_FILES["manuscript"]["tmp_name"],
 | 
				
			||||||
 | 
					                $file_destination
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($file_moved) {
 | 
				
			||||||
 | 
					                $show_doc = isset($_POST["public-doc"]) ? 1 : 0;
 | 
				
			||||||
 | 
					                $show_name = isset($_POST["public-name"]) ? 1 : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $stmt = $db["data"]
 | 
				
			||||||
 | 
					                    ->prepare("INSERT INTO submissions (member_id, game_id, title, basename, hash, doc_is_public, name_is_public, transaction_id, status, is_freeroll, created_at)
 | 
				
			||||||
 | 
					                    VALUES (:member_id, :game_id, :title, :basename, :hash, :doc_is_public, :name_is_public, :transaction_id, :status, :is_freeroll, :created_at)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $stmt->execute([
 | 
				
			||||||
 | 
					                    "member_id" => $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					                    "game_id" => $_GET["game"],
 | 
				
			||||||
 | 
					                    "title" => $_POST["title"],
 | 
				
			||||||
 | 
					                    "basename" => "$new_basename",
 | 
				
			||||||
 | 
					                    "hash" => $lookup_hash,
 | 
				
			||||||
 | 
					                    "doc_is_public" => $show_doc,
 | 
				
			||||||
 | 
					                    "name_is_public" => $show_name,
 | 
				
			||||||
 | 
					                    "transaction_id" => $_POST["tx-id"],
 | 
				
			||||||
 | 
					                    "status" => 1,
 | 
				
			||||||
 | 
					                    "is_freeroll" => 0,
 | 
				
			||||||
 | 
					                    "created_at" => date("Y-m-d\TH:i:s\Z"),
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                http_response_code(303);
 | 
				
			||||||
 | 
					                header("Location: /games/" . $_GET["game"]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (Exception $e) {
 | 
				
			||||||
 | 
					            http_response_code(500);
 | 
				
			||||||
 | 
					            unlink($file_destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $errors["upload"] =
 | 
				
			||||||
 | 
					                "There was an error adding your submission to our database. Please try again.";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					endif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					        <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					            <?php if (!LOGGED_IN) {
 | 
				
			||||||
 | 
					                include "partials/login-form.php";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (
 | 
				
			||||||
 | 
					                    http_response_code() === 400 ||
 | 
				
			||||||
 | 
					                    http_response_code() === 500
 | 
				
			||||||
 | 
					                ) { ?>
 | 
				
			||||||
 | 
					                     <aside class='alert'>
 | 
				
			||||||
 | 
					                         <p>We found <?= count(
 | 
				
			||||||
 | 
					                             $errors
 | 
				
			||||||
 | 
					                         ) ?> error(s) with your submission. Please correct the errors provided by each field.</p>
 | 
				
			||||||
 | 
					                     </aside>
 | 
				
			||||||
 | 
					                 <?php } ?>
 | 
				
			||||||
 | 
					                <form action="<?= $_SERVER[
 | 
				
			||||||
 | 
					                    "REQUEST_URI"
 | 
				
			||||||
 | 
					                ] ?>" method="post" enctype="multipart/form-data" class="flow">
 | 
				
			||||||
 | 
					                    <input type="hidden" id="game-name" value="<?= $game->name ?>" />
 | 
				
			||||||
 | 
					                    <?php if (
 | 
				
			||||||
 | 
					                        isset($errors) &&
 | 
				
			||||||
 | 
					                        isset($errors["account"])
 | 
				
			||||||
 | 
					                    ) { ?><p><mark><?= $errors[
 | 
				
			||||||
 | 
					    "account"
 | 
				
			||||||
 | 
					] ?></mark></p><?php } ?>
 | 
				
			||||||
 | 
					                    <?php if (
 | 
				
			||||||
 | 
					                        isset($errors) &&
 | 
				
			||||||
 | 
					                        isset($errors["game"])
 | 
				
			||||||
 | 
					                    ) { ?><p><mark><?= $errors[
 | 
				
			||||||
 | 
					    "game"
 | 
				
			||||||
 | 
					] ?></mark></p><?php } ?>
 | 
				
			||||||
 | 
					                    <fieldset class="flow">
 | 
				
			||||||
 | 
					                        <legend>Manuscript details</legend>
 | 
				
			||||||
 | 
					                    <label>
 | 
				
			||||||
 | 
					                        <span>Title of Work</span>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["title"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "title"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <input type="text" name="title" value='<?= $_POST[
 | 
				
			||||||
 | 
					                            "title"
 | 
				
			||||||
 | 
					                        ] ?? "" ?>'/>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <label>
 | 
				
			||||||
 | 
					                        <span>Manuscript PDF <small>(6MB limit)</small></span>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["upload"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "upload"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["filesize"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "filesize"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["mimetype"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "mimetype"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <input type="file" name="manuscript" accept="application/pdf"/>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <div>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["agreements"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "agreements"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <label>
 | 
				
			||||||
 | 
					                            <input type="checkbox" name="agree-toc" value="1" <?= isset(
 | 
				
			||||||
 | 
					                                $_POST["agree-toc"]
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>I agree to <i>Sixfold</i>'s <a href="https://sixfold.org/terms-and-conditions" target="_blank">terms and conditions</a></span>
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                        <label>
 | 
				
			||||||
 | 
					                            <input type="checkbox" name="agree-guidelines" value="1" <?= isset(
 | 
				
			||||||
 | 
					                                $_POST["agree-guidelines"]
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>I confirm that the submitted manuscript meets the <strong></strong><a href="https://sixfold.org/how-it-works/submission-guidelines" target="_blank">submission guidelines</a></span>
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    </fieldset>
 | 
				
			||||||
 | 
					                    <fieldset>
 | 
				
			||||||
 | 
					                        <legend>Privacy settings</legend>
 | 
				
			||||||
 | 
					                        <label>
 | 
				
			||||||
 | 
					                            <input type="checkbox" name="public-name" value="1" <?= isset(
 | 
				
			||||||
 | 
					                                $_POST["public-name"]
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>Display my name in the public results</span>
 | 
				
			||||||
 | 
					                            </label>
 | 
				
			||||||
 | 
					                            <label>
 | 
				
			||||||
 | 
					                            <input type="checkbox" name="public-doc" value="1" <?= isset(
 | 
				
			||||||
 | 
					                                $_POST["public-doc"]
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>Display my document in the public results</span>
 | 
				
			||||||
 | 
					                            </label>
 | 
				
			||||||
 | 
					                    </fieldset>
 | 
				
			||||||
 | 
					                    <fieldset>
 | 
				
			||||||
 | 
					                        <legend>Payment</legend>
 | 
				
			||||||
 | 
					                    <div class="flow">
 | 
				
			||||||
 | 
					                        <p><b>Payment is required before a submission will be processed.</b></p>
 | 
				
			||||||
 | 
					                        <p><mark id='paypal-errors' aria-live='polite'><?php if (
 | 
				
			||||||
 | 
					                            isset($_POST["tx-id"])
 | 
				
			||||||
 | 
					                        ) { ?><b>Payment successful!</b> (Transaction ID: <?= $_POST[
 | 
				
			||||||
 | 
					    "tx-id"
 | 
				
			||||||
 | 
					] ?>)<?php } ?></mark></p>
 | 
				
			||||||
 | 
					                        <?php if (!isset($_POST["tx-id"])) { ?>
 | 
				
			||||||
 | 
					                            <input type="hidden" name="tx-id"/>
 | 
				
			||||||
 | 
					                    <div id="paypal-button-container"></div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <!-- Initialize the JS-SDK -->
 | 
				
			||||||
 | 
					                    <script
 | 
				
			||||||
 | 
					                        src="https://www.paypal.com/sdk/js?merchant-id=LF2R8M5TKKHRL&client-id=<?= PAYPAL_CLIENT_ID ?>¤cy=USD&components=buttons&enable-funding=card,venmo&disable-funding=paylater"
 | 
				
			||||||
 | 
					                        data-sdk-integration-source="developer-studio"></script>
 | 
				
			||||||
 | 
					                    <script src="/assets/js/paypal.js"></script>
 | 
				
			||||||
 | 
					                    <?php } ?>
 | 
				
			||||||
 | 
					                    </fieldset>
 | 
				
			||||||
 | 
					                    <button type="submit">Submit work</button>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            <?php
 | 
				
			||||||
 | 
					            } ?>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										370
									
								
								www/games/update.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								www/games/update.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,370 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!LOGGED_IN) {
 | 
				
			||||||
 | 
					    $title = "Log In";
 | 
				
			||||||
 | 
					    $description = "Log in to update your existing submission for this vote.";
 | 
				
			||||||
 | 
					    http_response_code(401);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (LOGGED_IN && isset($_GET["game"])) {
 | 
				
			||||||
 | 
					    $sql = "SELECT * FROM submissions
 | 
				
			||||||
 | 
					    WHERE game_id = :game_id AND member_id = :member_id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "game_id" => $_GET["game"],
 | 
				
			||||||
 | 
					        "member_id" => $_SESSION["account"]->id,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $submission = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					    if (!$submission) {
 | 
				
			||||||
 | 
					        http_response_code(303);
 | 
				
			||||||
 | 
					        header("Location: /games/" . $_GET["game"]);
 | 
				
			||||||
 | 
					        die();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $sql = "SELECT id, name, status_id FROM games
 | 
				
			||||||
 | 
					    WHERE id = :id";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare($sql);
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "id" => $_GET["game"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    $game = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    define("GAME_IS_OPEN", $game->status_id === STATUS_ENROLLING);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $title = "Update: {$game->name}";
 | 
				
			||||||
 | 
					    $description = "Update your submission for the " . $game->name . " vote.";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] === "POST"):
 | 
				
			||||||
 | 
					    define("NEW_MANUSCRIPT", $_POST["keep-manuscript"] === "0");
 | 
				
			||||||
 | 
					    define("EXISTING_MANUSCRIPT", $_POST["keep-manuscript"] === "1");
 | 
				
			||||||
 | 
					    define(
 | 
				
			||||||
 | 
					        "RULES_WERE_FOLLOWED",
 | 
				
			||||||
 | 
					        isset($_POST["agree-toc"]) &&
 | 
				
			||||||
 | 
					            $_POST["agree-toc"] === "1" &&
 | 
				
			||||||
 | 
					            (isset($_POST["agree-guidelines"]) &&
 | 
				
			||||||
 | 
					                $_POST["agree-guidelines"] === "1")
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    define("FILE_EMPTY", $_FILES["manuscript"]["size"] === 0);
 | 
				
			||||||
 | 
					    define("FILE_TOO_BIG", $_FILES["manuscript"]["size"] > UPLOAD_MAX_FILESIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare("SELECT id FROM games WHERE id = :id");
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "id" => $_GET["game"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $errors = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!$stmt->fetch(PDO::FETCH_COLUMN)) {
 | 
				
			||||||
 | 
					        $errors["game"] = "The chosen game doesn't exist.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (GAME_IS_OPEN && NEW_MANUSCRIPT && !RULES_WERE_FOLLOWED) {
 | 
				
			||||||
 | 
					        $errors["agreements"] =
 | 
				
			||||||
 | 
					            "Please accept the Terms & Conditions and the Submission Guidelines.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (GAME_IS_OPEN && NEW_MANUSCRIPT && FILE_EMPTY) {
 | 
				
			||||||
 | 
					        $errors["filesize"] = "A file upload is required.";
 | 
				
			||||||
 | 
					    } elseif (NEW_MANUSCRIPT && FILE_TOO_BIG) {
 | 
				
			||||||
 | 
					        $errors["filesize"] = "Your document is too large.";
 | 
				
			||||||
 | 
					    } elseif (GAME_IS_OPEN && NEW_MANUSCRIPT) {
 | 
				
			||||||
 | 
					        $finfo = finfo_open(FILEINFO_MIME_TYPE);
 | 
				
			||||||
 | 
					        $mime_type = finfo_file($finfo, $_FILES["manuscript"]["tmp_name"]);
 | 
				
			||||||
 | 
					        finfo_close($finfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $PROPER_MIMETYPE = $mime_type === "application/pdf";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isset($PROPER_MIMETYPE) && !$PROPER_MIMETYPE) {
 | 
				
			||||||
 | 
					        $errors["mimetype"] = "Only PDF submissions are allowed.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (GAME_IS_OPEN && (!isset($_POST["title"]) || !trim($_POST["title"]))) {
 | 
				
			||||||
 | 
					        $errors["title"] = "Please enter a title.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (GAME_IS_OPEN && !isset($_SESSION["account"])) {
 | 
				
			||||||
 | 
					        $errors["account"] =
 | 
				
			||||||
 | 
					            "We can't upload a document without knowing which account it belongs to.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (GAME_IS_OPEN && !isset($_POST["tx-id"])) {
 | 
				
			||||||
 | 
					        $errors["payment"] = "You must submit a payment.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (count($errors) > 0) {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					        define(
 | 
				
			||||||
 | 
					            "HAS_FILE_ERRORS",
 | 
				
			||||||
 | 
					            isset($errors["filesize"]) || isset($errors["mimetype"])
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (NEW_MANUSCRIPT && !HAS_FILE_ERRORS) {
 | 
				
			||||||
 | 
					            $errors["upload"] =
 | 
				
			||||||
 | 
					                "Fix all other errors and choose your file again.";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        $params = [
 | 
				
			||||||
 | 
					            "submission_id" =>  $submission->id,
 | 
				
			||||||
 | 
					            "title" => $_POST["title"],
 | 
				
			||||||
 | 
					            "doc_is_public" => isset($_POST["public-doc"]) ? 1 : 0,
 | 
				
			||||||
 | 
					            "name_is_public" => isset($_POST["public-name"]) ? 1 : 0,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (GAME_IS_OPEN && NEW_MANUSCRIPT) {
 | 
				
			||||||
 | 
					            $basename =
 | 
				
			||||||
 | 
					                md5(microtime() . $game->id . $_SESSION["account"]->id) .
 | 
				
			||||||
 | 
					                ".pdf";
 | 
				
			||||||
 | 
					            $hash = md5(
 | 
				
			||||||
 | 
					                $_SESSION["account"]->id . $game->id . microtime() . "salt"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $file_destination = sprintf(
 | 
				
			||||||
 | 
					                "%s/%s/%s",
 | 
				
			||||||
 | 
					                DIRECTORY_DOCS,
 | 
				
			||||||
 | 
					                $game->id,
 | 
				
			||||||
 | 
					                $basename
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                $file_moved = move_uploaded_file(
 | 
				
			||||||
 | 
					                    $_FILES["manuscript"]["tmp_name"],
 | 
				
			||||||
 | 
					                    $file_destination
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if ($file_moved) {
 | 
				
			||||||
 | 
					                    $stmt = $db["data"]->prepare(
 | 
				
			||||||
 | 
					                        "SELECT basename FROM submissions WHERE id = :submission_id"
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    $stmt->execute([
 | 
				
			||||||
 | 
					                        "submission_id" => $submission->id,
 | 
				
			||||||
 | 
					                    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $old_manuscript = sprintf(
 | 
				
			||||||
 | 
					                        "%s/%s/%s",
 | 
				
			||||||
 | 
					                        DIRECTORY_DOCS,
 | 
				
			||||||
 | 
					                        $game->id,
 | 
				
			||||||
 | 
					                        $stmt->fetch(PDO::FETCH_COLUMN)
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    unlink($old_manuscript);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $stmt = $db["data"]
 | 
				
			||||||
 | 
					                        ->prepare("UPDATE submissions SET (title, basename, hash, doc_is_public, name_is_public, created_at)
 | 
				
			||||||
 | 
					                        = (:title, :basename, :hash, :doc_is_public, :name_is_public, :created_at) WHERE id = :submission_id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $params["basename"] = $basename;
 | 
				
			||||||
 | 
					                    $params["hash"] = $hash;
 | 
				
			||||||
 | 
					                    $params["created_at"] = date("Y-m-d\TH:i:s\Z");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    $stmt->execute($params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    http_response_code(303);
 | 
				
			||||||
 | 
					                    header("Location: /games/" . $_GET["game"]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (Exception $e) {
 | 
				
			||||||
 | 
					                var_dump($e);
 | 
				
			||||||
 | 
					                http_response_code(500);
 | 
				
			||||||
 | 
					                unlink($file_destination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                $errors["upload"] =
 | 
				
			||||||
 | 
					                    "There was an error adding your submission to our database. Please try again.";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (GAME_IS_OPEN && EXISTING_MANUSCRIPT) {
 | 
				
			||||||
 | 
					            $stmt = $db["data"]
 | 
				
			||||||
 | 
					                ->prepare("UPDATE submissions SET (title, doc_is_public, name_is_public)
 | 
				
			||||||
 | 
					                = (:title, :doc_is_public, :name_is_public) WHERE id = :submission_id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $stmt->execute($params);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            http_response_code(303);
 | 
				
			||||||
 | 
					            header("Location: /games/" . $_GET["game"]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!GAME_IS_OPEN) {
 | 
				
			||||||
 | 
					            $stmt = $db["data"]
 | 
				
			||||||
 | 
					                ->prepare("UPDATE submissions SET (doc_is_public, name_is_public)
 | 
				
			||||||
 | 
					                = (:doc_is_public, :name_is_public) WHERE id = :submission_id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $stmt->execute([
 | 
				
			||||||
 | 
					                "submission_id" => $submission->id,
 | 
				
			||||||
 | 
					                "doc_is_public" => isset($_POST["public-doc"]) ? 1 : 0,
 | 
				
			||||||
 | 
					                "name_is_public" => isset($_POST["public-name"]) ? 1 : 0,
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            http_response_code(303);
 | 
				
			||||||
 | 
					            header("Location: /games/" . $_GET["game"]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					endif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					        <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					            <?php if (!LOGGED_IN) {
 | 
				
			||||||
 | 
					                include "partials/login-form.php";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (GAME_IS_OPEN) { ?>
 | 
				
			||||||
 | 
					                    <p><b>If you would like to withdraw your submission, please email us at <a href="mailto:sixfold@sixfold.org?subject=Withdraw Submission: <?= $game->name ?>">sixfold@sixfold.org</a>.</b></p>
 | 
				
			||||||
 | 
					                <?php } else { ?>
 | 
				
			||||||
 | 
					                    <p><b>This game's submissions are now locked, and you may only edit your work's public visibility.</b></p>
 | 
				
			||||||
 | 
					                <?php } ?>
 | 
				
			||||||
 | 
					                <div role="region" aria-labelledby="table-caption" tabindex="0">
 | 
				
			||||||
 | 
					                <table>
 | 
				
			||||||
 | 
					                    <caption id='table-caption'>Submission Details</caption>
 | 
				
			||||||
 | 
					                    <thead>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <th scope='col'>Submission ID</th>
 | 
				
			||||||
 | 
					                            <th scope='col'>Transaction ID</th>
 | 
				
			||||||
 | 
					                            <th scope='col'>Account ID</th>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    </thead>
 | 
				
			||||||
 | 
					                    <tbody>
 | 
				
			||||||
 | 
					                        <tr>
 | 
				
			||||||
 | 
					                            <td><?= $submission->id ?></td>
 | 
				
			||||||
 | 
					                            <td><?= $submission->transaction_id ?></td>
 | 
				
			||||||
 | 
					                            <td><?= $_SESSION["account"]->id ?></td>
 | 
				
			||||||
 | 
					                        </tr>
 | 
				
			||||||
 | 
					                    </tbody>
 | 
				
			||||||
 | 
					                </table>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <?php if (
 | 
				
			||||||
 | 
					                    http_response_code() === 400 ||
 | 
				
			||||||
 | 
					                    http_response_code() === 500
 | 
				
			||||||
 | 
					                ) { ?>
 | 
				
			||||||
 | 
					                     <aside class='alert'>
 | 
				
			||||||
 | 
					                         <p>We found <?= count(
 | 
				
			||||||
 | 
					                             $errors
 | 
				
			||||||
 | 
					                         ) ?> error(s) with your submission. Please correct the errors provided by each field.</p>
 | 
				
			||||||
 | 
					                     </aside>
 | 
				
			||||||
 | 
					                 <?php } ?>
 | 
				
			||||||
 | 
					                <?php
 | 
				
			||||||
 | 
					                $doc_is_public = isset($_POST["public-doc"])
 | 
				
			||||||
 | 
					                    ? (bool) $_POST["public-doc"]
 | 
				
			||||||
 | 
					                    : $submission->doc_is_public;
 | 
				
			||||||
 | 
					                $name_is_public = isset($_POST["public-name"])
 | 
				
			||||||
 | 
					                    ? (bool) $_POST["public-name"]
 | 
				
			||||||
 | 
					                    : $submission->name_is_public;
 | 
				
			||||||
 | 
					                ?>
 | 
				
			||||||
 | 
					                <form action="<?= $_SERVER[
 | 
				
			||||||
 | 
					                    "REQUEST_URI"
 | 
				
			||||||
 | 
					                ] ?>" method="post" enctype="multipart/form-data" class="flow">
 | 
				
			||||||
 | 
					                    <?php if (
 | 
				
			||||||
 | 
					                        isset($errors) &&
 | 
				
			||||||
 | 
					                        isset($errors["account"])
 | 
				
			||||||
 | 
					                    ) { ?><p><mark><?= $errors[
 | 
				
			||||||
 | 
					    "account"
 | 
				
			||||||
 | 
					] ?></mark></p><?php } ?>
 | 
				
			||||||
 | 
					                <?php if (GAME_IS_OPEN) { ?>
 | 
				
			||||||
 | 
					                    <fieldset class="flow">
 | 
				
			||||||
 | 
					                        <legend>Manuscript details</legend>
 | 
				
			||||||
 | 
					                    <label>
 | 
				
			||||||
 | 
					                        <span>Title of Work</span>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["title"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "title"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <input type="text" name="title" value='<?= $_POST[
 | 
				
			||||||
 | 
					                            "title"
 | 
				
			||||||
 | 
					                        ] ?? $submission->title ?>'/>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <input type='radio' name='keep-manuscript' id='keep-existing' value='1' <?= !isset(
 | 
				
			||||||
 | 
					                        $_POST["keep-manuscript"]
 | 
				
			||||||
 | 
					                    ) ||
 | 
				
			||||||
 | 
					                    (isset($_POST["keep-manuscript"]) &&
 | 
				
			||||||
 | 
					                        $_POST["keep-manuscript"] === "1")
 | 
				
			||||||
 | 
					                        ? "checked"
 | 
				
			||||||
 | 
					                        : "" ?>/><label for='keep-existing'>Keep existing manuscript</label><br/>
 | 
				
			||||||
 | 
					                    <input type='radio' name='keep-manuscript' id='upload-new' value='0' <?= isset(
 | 
				
			||||||
 | 
					                        $_POST["keep-manuscript"]
 | 
				
			||||||
 | 
					                    ) && NEW_MANUSCRIPT
 | 
				
			||||||
 | 
					                        ? "checked"
 | 
				
			||||||
 | 
					                        : "" ?>/><label for='upload-new'>Upload new manuscript</label>
 | 
				
			||||||
 | 
					                    <label id='manuscript'>
 | 
				
			||||||
 | 
					                        <span>Manuscript PDF <small>(6MB limit)</small></span>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["upload"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "upload"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["filesize"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "filesize"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["mimetype"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "mimetype"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <input type="file" name="manuscript" accept="application/pdf"/>
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                    <div id='guidelines'>
 | 
				
			||||||
 | 
					                        <?php if (
 | 
				
			||||||
 | 
					                            isset($errors) &&
 | 
				
			||||||
 | 
					                            isset($errors["agreements"])
 | 
				
			||||||
 | 
					                        ) { ?><span><mark><?= $errors[
 | 
				
			||||||
 | 
					    "agreements"
 | 
				
			||||||
 | 
					] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					                        <label>
 | 
				
			||||||
 | 
					                            <input type="checkbox" name="agree-toc" value="1" <?= isset(
 | 
				
			||||||
 | 
					                                $_POST["agree-toc"]
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>I agree to <i>Sixfold</i>'s <a href="/terms-and-conditions" target="_blank">terms and conditions</a></span>
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                        <label>
 | 
				
			||||||
 | 
					                            <input type="checkbox" name="agree-guidelines" value="1" <?= isset(
 | 
				
			||||||
 | 
					                                $_POST["agree-guidelines"]
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>I confirm that the submitted manuscript meets the <strong></strong><a href="/how-it-works/submission-guidelines" target="_blank">submission guidelines</a></span>
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    </fieldset>
 | 
				
			||||||
 | 
					                    <?php } ?>
 | 
				
			||||||
 | 
					                    <fieldset>
 | 
				
			||||||
 | 
					                        <legend>Privacy settings</legend>
 | 
				
			||||||
 | 
					                        <label>
 | 
				
			||||||
 | 
					                            <input type="checkbox" name="public-name" value="1" <?= $name_is_public
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>Display my name in the public results</span>
 | 
				
			||||||
 | 
					                            </label>
 | 
				
			||||||
 | 
					                            <label>
 | 
				
			||||||
 | 
					                            <input type='checkbox' name='public-doc' value='1' <?= $doc_is_public
 | 
				
			||||||
 | 
					                                ? "checked"
 | 
				
			||||||
 | 
					                                : "" ?>/>
 | 
				
			||||||
 | 
					                            <span>Display my document in the public results</span>
 | 
				
			||||||
 | 
					                            </label>
 | 
				
			||||||
 | 
					                    </fieldset>
 | 
				
			||||||
 | 
					                     <?php if (GAME_IS_OPEN) { ?>
 | 
				
			||||||
 | 
					                    <fieldset>
 | 
				
			||||||
 | 
					                        <legend>Payment</legend>
 | 
				
			||||||
 | 
					                    <div class="flow">
 | 
				
			||||||
 | 
					                        <p><b>You have already paid for this submission.</b> (Transaction ID: <?= $submission->transaction_id ?>)</p>
 | 
				
			||||||
 | 
					                        <input type="hidden" name="tx-id" value="<?= $submission->transaction_id ?>"/>
 | 
				
			||||||
 | 
					                    </fieldset>
 | 
				
			||||||
 | 
					                     <?php } ?>
 | 
				
			||||||
 | 
					                    <button type="submit">Update submission</button>
 | 
				
			||||||
 | 
					                </form>
 | 
				
			||||||
 | 
					            <?php
 | 
				
			||||||
 | 
					            } ?>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										68
									
								
								www/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								www/index.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,68 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$description = "View the most recent games.";
 | 
				
			||||||
 | 
					$title = 'Dashboard';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php"; ?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php" ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					        <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <?php if (!LOGGED_IN) : ?>
 | 
				
			||||||
 | 
					    <p>You must log in to view this page.</p>
 | 
				
			||||||
 | 
					    <?php
 | 
				
			||||||
 | 
					    http_response_code(401);
 | 
				
			||||||
 | 
					    include "partials/login-form.php";
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					    $sql = "SELECT games.id, games.name, games.submitstart, games.status_id, games.submitend, games.onestart, games.twostart, games.threestart, games.gameend FROM games JOIN game_status ON games.status_id = game_status.id ORDER BY games.id DESC LIMIT 2";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db['data']->query($sql);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while ($game = $stmt->fetch()) {
 | 
				
			||||||
 | 
					        $dates = [
 | 
				
			||||||
 | 
					        'submitstart' => DateTimeImmutable::createFromFormat('U', $game['submitstart'])->setTimezone($time_zone),
 | 
				
			||||||
 | 
					        'submitend' => DateTimeImmutable::createFromFormat('U', $game['submitend'])->setTimezone($time_zone),
 | 
				
			||||||
 | 
					        'onestart' => DateTimeImmutable::createFromFormat('U', $game['onestart'])->setTimezone($time_zone),
 | 
				
			||||||
 | 
					        'twostart' => DateTimeImmutable::createFromFormat('U', $game['twostart'])->setTimezone($time_zone),
 | 
				
			||||||
 | 
					        'threestart' => DateTimeImmutable::createFromFormat('U', $game['threestart'])->setTimezone($time_zone),
 | 
				
			||||||
 | 
					        'gameend' => DateTimeImmutable::createFromFormat('U', $game['gameend'])->setTimezone($time_zone),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        ?>
 | 
				
			||||||
 | 
					        <article>
 | 
				
			||||||
 | 
					            <h3><?= $game['name'] ?></h3>
 | 
				
			||||||
 | 
					            <p><span>Status: </span><?= get_status_message($game['status_id']) ?></p>
 | 
				
			||||||
 | 
					            <dl>
 | 
				
			||||||
 | 
					                <?php if ($game['status_id'] === STATUS_ENROLLING) : ?>
 | 
				
			||||||
 | 
					                <dt>Submissions</dt>
 | 
				
			||||||
 | 
					                <dd><span>Open: </span><time datetime="<?= $dates['submitstart']->format('c') ?>"><?= $dates['submitstart']->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <dd><span>Close: </span><time datetime="<?= $dates['submitend']->format('c') ?>"><?= $dates['submitend']->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <?php endif; ?>
 | 
				
			||||||
 | 
					                <?php if ($game['status_id'] === STATUS_ROUND_ONE) : ?>
 | 
				
			||||||
 | 
					                <dt>Round One</dt>
 | 
				
			||||||
 | 
					                <dd><span>Open: </span><time datetime="<?= $dates['onestart']->format('c') ?>"><?= $dates['onestart']->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <dd><span>Close: </span><time datetime="<?= $dates['twostart']->sub($one_second)->format('c') ?>"><?= $dates['twostart']->sub($one_second)->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <?php endif; ?>
 | 
				
			||||||
 | 
					                <?php if ($game['status_id'] === STATUS_ROUND_TWO) : ?>
 | 
				
			||||||
 | 
					                <dt>Round Two</dt>
 | 
				
			||||||
 | 
					                <dd><span>Open: </span><time datetime="<?= $dates['twostart']->format('c') ?>"><?= $dates['twostart']->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <dd><span>Close: </span><time datetime="<?= $dates['threestart']->sub($one_second)->format('c') ?>"><?= $dates['threestart']->sub($one_second)->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <?php endif; ?>
 | 
				
			||||||
 | 
					                <?php if ($game['status_id'] === STATUS_ROUND_THREE) : ?>
 | 
				
			||||||
 | 
					                <dt>Round Three</dt>
 | 
				
			||||||
 | 
					                <dd><span>Open: </span><time datetime="<?= $dates['threestart']->format('c') ?>"><?= $dates['threestart']->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <dd><span>Close: </span><time datetime="<?= $dates['gameend']->format('c') ?>"><?= $dates['gameend']->format('l, j F, o \a\t h:i A T') ?></time></dd>
 | 
				
			||||||
 | 
					                <?php endif; ?>
 | 
				
			||||||
 | 
					                <?php if ($game['status_id'] === STATUS_REVIEW) : ?>
 | 
				
			||||||
 | 
					                <p>Submissions are currently under review.</p>
 | 
				
			||||||
 | 
					                <p>Round One begins on <time datetime="<?= $dates['onestart']->format('c') ?>"><?= $dates['onestart']->format('l, j F, o \a\t h:i A T') ?></time></p>
 | 
				
			||||||
 | 
					                <?php endif; ?>
 | 
				
			||||||
 | 
					            </dl>
 | 
				
			||||||
 | 
					            <p><a href="/games/<?= $game['id'] ?>" class="call-to-action"><?= $game['name'] ?> details</a></p>
 | 
				
			||||||
 | 
					        </article>
 | 
				
			||||||
 | 
					    <?php } ?>
 | 
				
			||||||
 | 
					        <p><a href="/games">View previously completed games</a></p>
 | 
				
			||||||
 | 
					    <?php endif; ?>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										58
									
								
								www/login.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								www/login.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					if (isset($_SESSION["account"]->id)) {
 | 
				
			||||||
 | 
					    http_response_code(303);
 | 
				
			||||||
 | 
					    header("Location: /");
 | 
				
			||||||
 | 
					    die();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$description = "Log in to access your account.";
 | 
				
			||||||
 | 
					$title = "Login";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] === "POST"): ?>
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					$stmt = $db["data"]->prepare("SELECT * FROM members WHERE email = :email");
 | 
				
			||||||
 | 
					$results = $stmt->execute([
 | 
				
			||||||
 | 
					    "email" => $_POST["email"],
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					$account = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					if (!$account) {
 | 
				
			||||||
 | 
					    http_response_code(401);
 | 
				
			||||||
 | 
					} elseif (password_check($account)) {
 | 
				
			||||||
 | 
					    $_SESSION["account"] = $account;
 | 
				
			||||||
 | 
					    http_response_code(303);
 | 
				
			||||||
 | 
					    header("Location: " . $_POST["redirect"] ?? "/");
 | 
				
			||||||
 | 
					    die();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					endif;
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					        <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <?php if (isset($_SESSION["alert_message"])) { ?>
 | 
				
			||||||
 | 
					        <aside class="alert">
 | 
				
			||||||
 | 
					            <p><?= $_SESSION["alert_message"] ?></p>
 | 
				
			||||||
 | 
					            <?php unset($_SESSION["alert_message"]); ?>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					        <?php } ?>
 | 
				
			||||||
 | 
					    <?php if (!COOKIES_ENABLED && $_SERVER["REQUEST_METHOD"] === "POST") { ?>
 | 
				
			||||||
 | 
					        <aside class='alert'>
 | 
				
			||||||
 | 
					            <p>Your browser is not set to accept cookies. You cannot login or use certain portions of the site unless your browser accepts cookies. Please change your browser settings to accept cookies and try again. Thanks.</p>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					        <?php } ?>
 | 
				
			||||||
 | 
					    <?php if (http_response_code() === 401) { ?>
 | 
				
			||||||
 | 
					        <aside class='alert'>
 | 
				
			||||||
 | 
					            <p>Your email or password is incorrect.</p>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					        <?php } ?>
 | 
				
			||||||
 | 
					    <?php if (http_response_code() === 500) { ?>
 | 
				
			||||||
 | 
					        <aside class='alert'>
 | 
				
			||||||
 | 
					            <p>We encounterred an issue when processing your request; please try again.</p>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					        <?php } ?>
 | 
				
			||||||
 | 
					    <?php include "partials/login-form.php"; ?>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										105
									
								
								www/members/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								www/members/index.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,105 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$title = 'Members';
 | 
				
			||||||
 | 
					$description = "View a list of our members.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$member_count = $db['data']->query('SELECT COUNT(*) FROM members', PDO::FETCH_COLUMN, 0)->fetch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$page = intval($_GET['page'] ?? 1);
 | 
				
			||||||
 | 
					$per_page = intval($_GET['per_page'] ?? 50);
 | 
				
			||||||
 | 
					$num_pages = ceil($member_count / $per_page) + ($member_count % $per_page > 0 ? 1 : 0);
 | 
				
			||||||
 | 
					$has_next_page = $page < $num_pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$sql = "SELECT
 | 
				
			||||||
 | 
					id,
 | 
				
			||||||
 | 
					name,
 | 
				
			||||||
 | 
					email,
 | 
				
			||||||
 | 
					handle,
 | 
				
			||||||
 | 
					links,
 | 
				
			||||||
 | 
					created_at
 | 
				
			||||||
 | 
					FROM members
 | 
				
			||||||
 | 
					ORDER BY created_at DESC
 | 
				
			||||||
 | 
					LIMIT :per_page
 | 
				
			||||||
 | 
					OFFSET :offset";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$stmt = $db['data']->prepare($sql);
 | 
				
			||||||
 | 
					$stmt->execute([
 | 
				
			||||||
 | 
					    'per_page' => ($per_page > 500) ? 500 : $per_page,
 | 
				
			||||||
 | 
					    'offset' => ($page - 1) * $per_page,
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php"; ?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php" ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <?php if ($per_page > 500) { ?><p><em>Results are limited to 500 per page.</em></p><?php } ?>
 | 
				
			||||||
 | 
					        <div role="region" aria-labelledby="members-caption" tabindex="0">
 | 
				
			||||||
 | 
					            <table>
 | 
				
			||||||
 | 
					                <caption id="members-caption">Sixfold Member List (<?= $member_count ?> members)</caption>
 | 
				
			||||||
 | 
					            <thead>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <th scope="col">Handle</th>
 | 
				
			||||||
 | 
					                    <th scope="col">Name</th>
 | 
				
			||||||
 | 
					                    <th scope="col">Date Joined</th>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            </thead>
 | 
				
			||||||
 | 
					            <tbody>
 | 
				
			||||||
 | 
					        <?php foreach ($stmt->fetchAll() as $member) {
 | 
				
			||||||
 | 
					            // set empty names
 | 
				
			||||||
 | 
					            // $stmt2 = $db['data']->prepare('UPDATE members SET name = :name WHERE name = " "');
 | 
				
			||||||
 | 
					            // $stmt2->execute([
 | 
				
			||||||
 | 
					            //     'name' => NULL
 | 
				
			||||||
 | 
					            // ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fix broken encodings
 | 
				
			||||||
 | 
					            // $stmt2 = $db['data']->prepare('UPDATE members SET name = :name WHERE id = :id');
 | 
				
			||||||
 | 
					            // $stmt2->execute([
 | 
				
			||||||
 | 
					            //     'id' => $member['id'],
 | 
				
			||||||
 | 
					            //     'name' => mb_convert_encoding($member['name'], 'Windows-1252','utf-8'),
 | 
				
			||||||
 | 
					            // ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fix null handles
 | 
				
			||||||
 | 
					            // $stmt2 = $db['data']->prepare('UPDATE members SET handle = :handle WHERE id = :id');
 | 
				
			||||||
 | 
					            // $stmt2->execute([
 | 
				
			||||||
 | 
					            //     'id' => $member['id'],
 | 
				
			||||||
 | 
					            //     'handle' => 'member-' . $member['id']
 | 
				
			||||||
 | 
					            // ]);
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // set empty bio
 | 
				
			||||||
 | 
					            $stmt2 = $db['data']->prepare('UPDATE members SET biography = :bio WHERE biography = ""');
 | 
				
			||||||
 | 
					            $stmt2->execute([
 | 
				
			||||||
 | 
					                'bio' => NULL
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fix links
 | 
				
			||||||
 | 
					            $stmt2 = $db['data']->prepare('UPDATE members SET links = :links WHERE id = :id');
 | 
				
			||||||
 | 
					            $stmt2->execute([
 | 
				
			||||||
 | 
					                'links' => str_replace('", url:', '", "url":', $member['links']),
 | 
				
			||||||
 | 
					                'id' => $member['id']
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					            ?>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th scope="row"><a href="/members/<?= $member['handle'] ?>"><?= $member['handle'] ?></a></th>
 | 
				
			||||||
 | 
					                <td><?= $member['name'] ?? 'Anonymous' ?></td>
 | 
				
			||||||
 | 
					                <?php $created_at = new DateTime($member['created_at']); ?>
 | 
				
			||||||
 | 
					                <td><?= $created_at->format("j F Y") ?></td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					        <?php } ?>
 | 
				
			||||||
 | 
					                        </tbody>
 | 
				
			||||||
 | 
					                    </table>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <ul>
 | 
				
			||||||
 | 
					                <li>
 | 
				
			||||||
 | 
					                    <?php if ($page > 1) { ?>
 | 
				
			||||||
 | 
					                        <a href="/members?page=<?= $page - 1 ?>&per_page=<?= $per_page ?>">Previous page</a>
 | 
				
			||||||
 | 
					                    <?php } else { ?>
 | 
				
			||||||
 | 
					                    <a disabled>Previous page</a>
 | 
				
			||||||
 | 
					                    <?php } ?>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					                <?php if ($has_next_page) { ?><li><a href="/members?page=<?= $page + 1 ?>&per_page=<?= $per_page ?>">Next page</a></li><?php } ?>
 | 
				
			||||||
 | 
					                </ul>
 | 
				
			||||||
 | 
					    </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										69
									
								
								www/members/member.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								www/members/member.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$title = 'Member Not Found';
 | 
				
			||||||
 | 
					$description = "We couldn't find anyone with the handle" . $_GET['handle'] . ".";
 | 
				
			||||||
 | 
					$sql = "SELECT
 | 
				
			||||||
 | 
					id,
 | 
				
			||||||
 | 
					name,
 | 
				
			||||||
 | 
					handle,
 | 
				
			||||||
 | 
					avatar,
 | 
				
			||||||
 | 
					biography,
 | 
				
			||||||
 | 
					links,
 | 
				
			||||||
 | 
					created_at,
 | 
				
			||||||
 | 
					last_access
 | 
				
			||||||
 | 
					FROM members WHERE UPPER(handle) = UPPER(:handle)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$stmt = $db['data']->prepare($sql);
 | 
				
			||||||
 | 
					$stmt->execute([
 | 
				
			||||||
 | 
					    'handle' => $_GET['handle'],
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					$member = $stmt->fetch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($member) {
 | 
				
			||||||
 | 
					    $name =  $member['name'] ?? 'Member ' . $member['id'];
 | 
				
			||||||
 | 
					    $title = 'Member: ' . $name;
 | 
				
			||||||
 | 
					    $description = $member['biography'] ?? "No biography provided.";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php"; ?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php" ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					        <header>
 | 
				
			||||||
 | 
					            <h1><?= $name ?? "Member Not Found" ?> <small>(<?= $member['handle'] ?? $_GET['handle'] ?>)</small></h1>
 | 
				
			||||||
 | 
					        </header>
 | 
				
			||||||
 | 
					        <?php
 | 
				
			||||||
 | 
					        if (isset($member) && $member) {
 | 
				
			||||||
 | 
					            $links = json_decode($member['links']);
 | 
				
			||||||
 | 
					            // fix broken encodings
 | 
				
			||||||
 | 
					            // $stmt2 = $db['data']->prepare('UPDATE members SET name = :name WHERE id = :id');
 | 
				
			||||||
 | 
					            // $stmt2->execute([
 | 
				
			||||||
 | 
					            //     'id' => $member['id'],
 | 
				
			||||||
 | 
					            //     'name' => mb_convert_encoding($member['name'], 'Windows-1252','utf-8'),
 | 
				
			||||||
 | 
					            // ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fix null handles
 | 
				
			||||||
 | 
					            // $stmt2 = $db['data']->prepare('UPDATE members SET handle = :handle WHERE id = :id');
 | 
				
			||||||
 | 
					            // $stmt2->execute([
 | 
				
			||||||
 | 
					            //     'id' => $member['id'],
 | 
				
			||||||
 | 
					            //     'handle' => 'member-' . $member['id']
 | 
				
			||||||
 | 
					            // ]);
 | 
				
			||||||
 | 
					            ?>
 | 
				
			||||||
 | 
					            <article class="member flow">
 | 
				
			||||||
 | 
					                <?php if ($member['avatar']) { ?><img src="/assets/avatars<?= $member['avatar'] ?>" alt=""/><?php } ?>
 | 
				
			||||||
 | 
					                <div><?= $member['biography'] ?? "<p>No biography provided.</p>"?></div>
 | 
				
			||||||
 | 
					                <?php if ($links) : ?>
 | 
				
			||||||
 | 
					                <ul>
 | 
				
			||||||
 | 
					                    <?php foreach ($links as $link) : ?>
 | 
				
			||||||
 | 
					                    <li><a href="<?= $link->url ?>"><?= $link->name ?></a></li>
 | 
				
			||||||
 | 
					                    <?php endforeach; ?>
 | 
				
			||||||
 | 
					                </ul>
 | 
				
			||||||
 | 
					                <?php endif; ?>
 | 
				
			||||||
 | 
					            </article>
 | 
				
			||||||
 | 
					        <?php } else {
 | 
				
			||||||
 | 
					            http_response_code(404);
 | 
				
			||||||
 | 
					            ?>
 | 
				
			||||||
 | 
					        <p>We couldn't find anyone with the handle <b><?= $_GET['handle'] ?></b>.</p>
 | 
				
			||||||
 | 
					        <?php } ?>
 | 
				
			||||||
 | 
					    </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
							
								
								
									
										125
									
								
								www/signup.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								www/signup.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,125 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function validate_fields($data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    global $db;
 | 
				
			||||||
 | 
					    $errors = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare("SELECT COUNT(*) FROM members WHERE email = :email");
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "email" => $data["email"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    if ($stmt->fetch(PDO::FETCH_COLUMN) > 0) {
 | 
				
			||||||
 | 
					        $errors["email"] = "That email address is already in use.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $stmt = $db["data"]->prepare("SELECT COUNT(*) FROM members WHERE UPPER(handle) = UPPER(:handle)");
 | 
				
			||||||
 | 
					    $stmt->execute([
 | 
				
			||||||
 | 
					        "handle" => $data["handle"],
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    if ($stmt->fetch(PDO::FETCH_COLUMN) > 0) {
 | 
				
			||||||
 | 
					        $errors["handle"] = "That handle is taken.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isset($data['terms-privacy'])) {
 | 
				
			||||||
 | 
					        $errors["terms-privacy"] = "Please agree to the terms.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (preg_match('/[^A-Za-z0-9-_]/', $_POST['handle'])) {
 | 
				
			||||||
 | 
					        $errors["handle"] = "Please only use allowed characters.";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return $errors;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$description = "Sign up to submit your work to Sixfold.";
 | 
				
			||||||
 | 
					$title = "Sign Up";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if ($_SERVER["REQUEST_METHOD"] === "POST"):
 | 
				
			||||||
 | 
					    $errors = validate_fields($_POST);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (count($errors) === 0) {
 | 
				
			||||||
 | 
					        $stmt = $db["data"]
 | 
				
			||||||
 | 
					            ->prepare('INSERT INTO members (name, handle, email, password, account_type, created_at, last_access)
 | 
				
			||||||
 | 
					    VALUES (:name, :handle, :email, :password, :account_type, :created_at, :last_access)
 | 
				
			||||||
 | 
					    RETURNING id');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $stmt->execute([
 | 
				
			||||||
 | 
					            "name" => $_POST["name"],
 | 
				
			||||||
 | 
					            "handle" => $_POST["handle"],
 | 
				
			||||||
 | 
					            "email" => $_POST["email"],
 | 
				
			||||||
 | 
					            "password" => password_hash($_POST['password'], PASSWORD_ARGON2ID),
 | 
				
			||||||
 | 
					            "account_type" => 1,
 | 
				
			||||||
 | 
					            "created_at" => date("Y-m-dTH:i:s"),
 | 
				
			||||||
 | 
					            "last_access" => date("Y-m-dTH:i:s"),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $stmt = $db['data']->query('SELECT * FROM members WHERE email = :email');
 | 
				
			||||||
 | 
					        $stmt->execute([
 | 
				
			||||||
 | 
					        'email' => $_POST['email'],
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $_SESSION['account'] = $stmt->fetch(PDO::FETCH_OBJ);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        http_response_code(400);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					endif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (isset($_SESSION["account"])) {
 | 
				
			||||||
 | 
					    http_response_code(303);
 | 
				
			||||||
 | 
					    header("Location: /");
 | 
				
			||||||
 | 
					    die();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include "partials/head.php";
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					    <body>
 | 
				
			||||||
 | 
					        <?php include "partials/header.php"; ?>
 | 
				
			||||||
 | 
					    <main id="main" class="flow">
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					        <h1><?= $title ?></h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <?php if (!COOKIES_ENABLED && $_SERVER["REQUEST_METHOD"] === "POST") { ?>
 | 
				
			||||||
 | 
					        <p>Your browser is not set to accept cookies. You cannot login or use certain portions of the site unless your browser accepts cookies. Please change your browser settings to accept cookies and try again. Thanks.</p>
 | 
				
			||||||
 | 
					        <?php } ?>
 | 
				
			||||||
 | 
					    <?php if (http_response_code() === 400) { ?>
 | 
				
			||||||
 | 
					        <aside class='alert'>
 | 
				
			||||||
 | 
					            <p>We found <?= count($errors) ?> error(s) with your submission. Please correct the errors provided by each field.</p>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					    <?php } ?>
 | 
				
			||||||
 | 
					    <?php if (http_response_code() === 500) { ?><p>We encounterred an issue when processing your request; please try again.</p><?php } ?>
 | 
				
			||||||
 | 
					    <form action="<?= $_SERVER["REQUEST_URI"] ?>" method="post" class="flow">
 | 
				
			||||||
 | 
					        <p><b>All fields are required.</b> Pseudonyms are welcome.</p>
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <span>Name <small>(A. M. Barnard)</small></span>
 | 
				
			||||||
 | 
					            <input type="text" name="name" value="<?= $_POST["name"] ??
 | 
				
			||||||
 | 
					                "" ?>" required/>
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <span>Handle <small>(lm_alcott)</small><br/><span>A to Z, 0 to 9, dashes, underscores</span></span>
 | 
				
			||||||
 | 
					            <?php if (isset($errors) && isset($errors['handle'])) { ?><span><mark><?= $errors['handle'] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					            <input type="text" name="handle" value="<?= $_POST["handle"] ??
 | 
				
			||||||
 | 
					                "" ?>" required/>
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <span>Email address <small>(email@example.com)</small></span>
 | 
				
			||||||
 | 
					            <?php if (isset($errors) && isset($errors['email'])) { ?><span><mark><?= $errors['email'] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					            <input type="text" name="email" value="<?= $_POST["email"] ??
 | 
				
			||||||
 | 
					                "" ?>" required/>
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <span>Password</span>
 | 
				
			||||||
 | 
					            <?php if (isset($errors) && isset($errors['password'])) { ?><span><mark><?= $errors['password'] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					            <input type="password" name="password" value="<?= $_POST["email"] ??
 | 
				
			||||||
 | 
					                "" ?>" required/>
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					        <label>
 | 
				
			||||||
 | 
					            <input type="checkbox" name="terms-privacy" value="1" required <?= isset($_POST["terms-privacy"]) ? 'checked' : '' ?>/>
 | 
				
			||||||
 | 
					            <?php if (isset($errors) && isset($errors['terms-privacy'])) { ?><span><mark><?= $errors['terms-privacy'] ?></mark></span><?php } ?>
 | 
				
			||||||
 | 
					            <span>I agree to the <a href='/terms-and-conditions'>Terms & Conditions</a> and <a href='/privacy'>Privacy Policy</a></span>
 | 
				
			||||||
 | 
					        </label>
 | 
				
			||||||
 | 
					        <button type="submit">Sign Up</button>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					    <p><a href="/login">Log in to an existing account</a></p>
 | 
				
			||||||
 | 
					        </main>
 | 
				
			||||||
 | 
					    <?php include "partials/footer.php"; ?>
 | 
				
			||||||
		Loading…
	
		Reference in a new issue