From 0bee588af09a31b60e2d1fd7f8dff2b908a96e58 Mon Sep 17 00:00:00 2001 From: Kroc Camen Date: Thu, 1 Dec 2011 13:59:08 +0000 Subject: [PATCH] Access control (WIP) Work in progress of the access control feature: * You can lock forums and limit posting / access to certain users: (Members can be specified in a 'members.txt' file) - 'threads': Only moderators / members can start threads, but anybody can reply - 'posts': Only moderators / members can start threads or reply - 'private': Only moderators / members can access and participate in the forum (hidden to the public) * Moderators can sign-in to do moderator actions * Moderators can now reply to and append/delete in locked threads * Moderators can now fully remove previously deleted (blanked-out) comments --- .gitignore | 2 + HISTORY.txt | 11 +++ action.php | 59 ++++++++----- config.example.php | 3 + index.php | 6 +- shared.php | 150 ++++++++++++++++++++++---------- themes/greyscale/append.inc.php | 30 ++++--- themes/greyscale/delete.inc.php | 30 ++++--- themes/greyscale/index.inc.php | 78 ++++++++++++----- themes/greyscale/lock.inc.php | 30 ++++--- themes/greyscale/theme.css | 13 ++- themes/greyscale/thread.inc.php | 103 +++++++++++++++------- thread.php | 42 ++++++--- 13 files changed, 382 insertions(+), 175 deletions(-) diff --git a/.gitignore b/.gitignore index ba14f03..80c5603 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.rss *.xml sticky.txt +locked.txt mods.txt +members.txt config.php diff --git a/HISTORY.txt b/HISTORY.txt index ef68a91..ed5b8ba 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,3 +1,14 @@ +v8 +* Access control: Major new feature! You can lock forums and limit posting / access to certain users: + (Members can be specified in a 'members.txt' file) + - 'threads': Only moderators / members can start threads, but anybody can reply + - 'posts': Only moderators / members can start threads or reply + - 'private': Only moderators / members can access and participate in the forum (hidden to the public) +* Moderators can sign-in to do moderator actions +* Moderators can now reply to and append/delete in locked threads +* Moderators can now fully remove previously deleted (blanked-out) comments +* Config option to disable new user registrations site-wide (`FORUM_NEWBIES`) + v7 05.NOV.11 * NNF can now be run from a folder, with thanks to Richard van Velzen (this requires theme changes: URLs must be prepended with `FORUM_PATH`) diff --git a/action.php b/action.php index d29fadb..6aaab50 100644 --- a/action.php +++ b/action.php @@ -32,15 +32,22 @@ /* has the un/pw been submitted to authenticate the append? -------------------------------------------------------------------------------------------------------------- */ - if ( - NAME && PASS && AUTH - //only a moderator, or the post originator can append to a post - && (isMod (NAME) || NAME == (string) $post->author) - //cannot append to a deleted post - && !(bool) $post->xpath ("category[text()='deleted']") - //cannot append to a locked thread - && !$xml->channel->xpath ("category[text()='locked']") - ) { + if (AUTH && TEXT && FORUM_ENABLED && ( + //- if the thread is unlocked and the forum is either unlocked or thread-locked (anybody can reply) + (!(bool) $xml->channel->xpath ("category[text()='locked']") && (!FORUM_LOCK || FORUM_LOCK == 'threads')) || + //- if the thread is locked, but you are a moderator (signed in) + ((bool) $xml->channel->xpath ("category[text()='locked']") && CAN_MOD) || + //- if the forum is post-locked, but you are a moderator (signed in) or member + (FORUM_LOCK == 'posts' && (CAN_MOD || isMember (NAME))) + ) && ( + //a moderator can always append + isMod (NAME) || + //the owner of a post can append + (strtolower (NAME) == strtolower ($post->author) && ( + //if the forum is post-locked, they must be a member to append to their own posts + (!FORUM_LOCK || FORUM_LOCK == 'threads') || isMember (NAME) + )) + )) { $now = time (); $post->description .= "\n".template_tags (TEMPLATE_APPEND, array ( 'AUTHOR' => safeHTML (NAME), @@ -129,17 +136,29 @@ /* has the un/pw been submitted to authenticate the delete? -------------------------------------------------------------------------------------------------------------- */ - if ( - NAME && PASS && AUTH - //only a moderator, or the post originator can delete a post/thread - && (isMod (NAME) || NAME == (string) $post->author) - //cannot delete a locked thread - && !$xml->channel->xpath ("category[text()='locked']") - + if (AUTH && FORUM_ENABLED && ( + //- if the thread is unlocked and the forum is either unlocked or thread-locked (anybody can reply) + (!(bool) $xml->channel->xpath ("category[text()='locked']") && (!FORUM_LOCK || FORUM_LOCK == 'threads')) || + //- if the thread is locked, but you are a moderator (signed in) + ((bool) $xml->channel->xpath ("category[text()='locked']") && CAN_MOD) || + //- if the forum is post-locked, but you are a moderator (signed in) or member + (FORUM_LOCK == 'posts' && (CAN_MOD || isMember (NAME))) + ) && ( + //a moderator can always delete + isMod (NAME) || + //the owner of a post can delete + (strtolower (NAME) == strtolower ($post->author) && ( + //if the forum is post-locked, they must be a member to delete their own posts + (!FORUM_LOCK || FORUM_LOCK == 'threads') || isMember (NAME) + )) //deleting a post? - ) if ($ID) { + )) if ($ID) { //full delete? (option ticked, is moderator, and post is on the last page) - if (isset ($_POST['remove']) && isMod (NAME) && $i <= (count ($xml->channel->item)-2) % FORUM_POSTS) { + if ( + (isMod (NAME) && $i <= (count ($xml->channel->item)-2) % FORUM_POSTS) && + //if the post has already been blanked, delete it fully + (isset ($_POST['remove']) || $post->xpath ("category[text()='deleted']")) + ) { //remove the post from the thread entirely unset ($xml->channel->item[$i]); @@ -225,7 +244,7 @@ $FILE = (preg_match ('/^[^.\/]+$/', @$_GET['file']) ? $_GET['file'] : '') or die ('Malformed request'); //get a write lock on the file so that between now and saving, no other posts could slip in - $f = fopen ("$FILE.rss", 'c'); flock ($f, LOCK_EX); + $f = fopen ("$FILE.rss", 'c'); flock ($f, LOCK_EX); $xml = simplexml_load_file ("$FILE.rss") or die ('Invalid file'); //what’s the current status? @@ -233,7 +252,7 @@ /* has the un/pw been submitted to authenticate the un/lock? (only a moderator can un/lock a thread) -------------------------------------------------------------------------------------------------------------- */ - if (NAME && PASS && AUTH && isMod (NAME)) { + if (CAN_MOD && AUTH) { if ($LOCKED) { //if there’s a "locked" category, remove it //note: for simplicity this removes *all* channel categories as NNF only uses one atm, diff --git a/config.example.php b/config.example.php index f9615ba..eec9e51 100644 --- a/config.example.php +++ b/config.example.php @@ -8,6 +8,9 @@ /* --- copy this file as 'config.php' and customise to your liking --- */ +//uncomment this if you want to show PHP errors in the browser +#error_reporting (-1); + //forum’s title. used in theme, and in RSS feeds //WARNING: changing this won’t update the index RSS feed containing this name; delete 'index.xml' and then post/delete // a thread to regenerate the 'index.xml' file so as to see the change diff --git a/index.php b/index.php index 6141fb4..fd4e11d 100644 --- a/index.php +++ b/index.php @@ -18,7 +18,7 @@ /* ====================================================================================================================== */ //has the user submitted a new thread? (and is the info valid?) -if (FORUM_ENABLED && NAME && PASS && AUTH && TITLE && TEXT && @$_POST['email'] == 'example@abc.com') { +if (CAN_POST && AUTH && TITLE && TEXT && @$_POST['email'] == 'example@abc.com') { //the file on disk is a simplified version of the title: $translit = preg_replace ( //replace non alphanumerics with underscores and don’t use more than 2 in a row @@ -79,7 +79,7 @@ /* ====================================================================================================================== */ /* sub-forums ---------------------------------------------------------------------------------------------------------------------- */ -//don’t all sub-sub-forums +//don’t allow sub-sub-forums (yet) if (!PATH) foreach (array_filter ( //get a list of folders: //include only directories, but ignore directories starting with ‘.’ and the users / themes folders @@ -161,7 +161,7 @@ /* new thread form ---------------------------------------------------------------------------------------------------------------------- */ //(exclude if posting has been disabled) -if (FORUM_ENABLED) $FORM = array ( +if (CAN_POST) $FORM = array ( 'NAME' => safeString (NAME), 'PASS' => safeString (PASS), 'TITLE' => safeString (TITLE), diff --git a/shared.php b/shared.php index 279c1c8..4953e34 100644 --- a/shared.php +++ b/shared.php @@ -5,22 +5,22 @@ you may do whatever you want to this code as long as you give credit to Kroc Camen, */ -//let me know when I’m being stupid -error_reporting (-1); - //default UTF-8 throughout mb_internal_encoding ('UTF-8'); mb_regex_encoding ('UTF-8'); /* constants: some stuff we don’t expect to change ---------------------------------------------------------------------------------------------------------------------- */ -define ('START', microtime (true)); //record how long the page takes to generate define ('FORUM_ROOT', dirname (__FILE__)); //full server-path for absolute references define ('FORUM_PATH', //relative from webroot--if running in a folder str_replace ('//', '/', dirname ($_SERVER['SCRIPT_NAME']).'/') //(always starts with a slash and ends in one) ); define ('FORUM_URL', 'http://'.$_SERVER['HTTP_HOST']); //todo: https support +//for HTTP authentication (private forums) +define ('HTTP_AUTH_UN', @$_SERVER['PHP_AUTH_USER']); //username if using HTTP authentication +define ('HTTP_AUTH_PW', @$_SERVER['PHP_AUTH_PW']); //password if using HTTP authentication + //these are just some enums for templates to react to define ('ERROR_NONE', 0); define ('ERROR_NAME', 1); //name entered is invalid / blank @@ -59,54 +59,108 @@ date_default_timezone_set (FORUM_TIMEZONE); -/* get input +/* common input ====================================================================================================================== */ -//all pages can accept a name / password when committing actions (new thread / post &c.) -define ('NAME', safeGet (@$_POST['username'], SIZE_NAME)); -define ('PASS', safeGet (@$_POST['password'], SIZE_PASS, false)); +//all our pages use path (often optional) so this is done here +define ('PATH', preg_match ('/[^.\/&]+/', @$_GET['path']) ? $_GET['path'] : ''); +//these two get used an awful lot +define ('PATH_URL', !PATH ? FORUM_PATH : safeURL (FORUM_PATH.PATH.'/', false)); //when outputting as part of a URL +define ('PATH_DIR', !PATH ? '/' : '/'.PATH.'/'); //serverside, like `chdir` / `unlink` + +//we have to change directory for `is_dir` to work, see +//being in the right directory is also assumed for reading 'mods.txt' and when generating the RSS (`indexRSS`) +//(oddly with `chdir` the path must end in a slash) +@chdir (FORUM_ROOT.PATH_DIR) or die ("Invalid path"); + -//if name & password are provided, validate them -if ( +/* access control + ====================================================================================================================== */ +/* name / password authorisation: + ---------------------------------------------------------------------------------------------------------------------- */ +//all pages can accept a name / password when committing actions (new thread / post &c.) +//in the case of HTTP authentication (sign in / private forums), these are provided in the request header +define ('NAME', HTTP_AUTH_UN ? HTTP_AUTH_UN : safeGet (@$_POST['username'], SIZE_NAME)); +define ('PASS', HTTP_AUTH_PW ? HTTP_AUTH_PW : safeGet (@$_POST['password'], SIZE_PASS, false)); + +if (( + //if any HTTP authentication is given, we don’t need to validate form fields + HTTP_AUTH_UN && HTTP_AUTH_PW +) || ( + //if an input form was submitted: NAME && PASS && //the email check is a fake hidden field in the form to try and fool spam bots isset ($_POST['email']) && @$_POST['email'] == 'example@abc.com' && //I wonder what this does...? ((isset ($_POST['x']) && isset ($_POST['y'])) || (isset ($_POST['submit_x']) && isset ($_POST['submit_y']))) -) { +)) { //users are stored as text files based on the hash of the given name $name = hash ('sha512', strtolower (NAME)); $user = FORUM_ROOT."/users/$name.txt"; - //create the user, if new (if registrations are allowed) - if (FORUM_NEWBIES && !file_exists ($user)) file_put_contents ($user, hash ('sha512', $name.PASS)); + + //create the user, if new: + //- if registrations are allowed (`FORUM_NEWBIES`) + //- you can’t create new users with the HTTP_AUTH sign in + if (FORUM_NEWBIES && !HTTP_AUTH_UN && !file_exists ($user)) file_put_contents ($user, hash ('sha512', $name.PASS)); + //does password match? define ('AUTH', @file_get_contents ($user) == hash ('sha512', $name.PASS)); + + //if signed in with HTTP_AUTH, confirm that it’s okay to use + //(e.g. the user could still have given the wrong password with HTTP_AUTH) + define ('HTTP_AUTH', HTTP_AUTH_UN ? AUTH : false); } else { - define ('AUTH', false); + define ('AUTH', false); + define ('HTTP_AUTH', false); } -//all our pages use path (often optional) so this is done here -define ('PATH', preg_match ('/[^.\/&]+/', @$_GET['path']) ? $_GET['path'] : ''); -//these two get used an awful lot -define ('PATH_URL', !PATH ? FORUM_PATH : safeURL (FORUM_PATH.PATH.'/', false)); //when outputting as part of a URL -define ('PATH_DIR', !PATH ? '/' : '/'.PATH.'/'); //serverside, like `chdir` / `unlink` +//get the lock status of the current forum we’re in: +//"threads" - only users in "mods.txt" / "members.txt" can start threads, but anybody can reply +//"posts" - only users in "mods.txt" / "members.txt" can start threads or reply +//"private" - only users in "mods.txt" / "members.txt" can enter and use the forum, it is hidden from everybody else +define ('FORUM_LOCK', trim (@file_get_contents ('locked.txt'))); -//we have to change directory for `is_dir` to work, see -//being in the right directory is also assumed for reading 'mods.txt' and when generating the RSS (`indexRSS`) -//(oddly with `chdir` the path must end in a slash) -@chdir (FORUM_ROOT.PATH_DIR) or die ("Invalid path"); +//if the sign-in link was clicked, invoke a HTTP_AUTH request in the browser +if (!HTTP_AUTH && isset ($_GET['login'])) { + header ('WWW-Authenticate: Basic'); + header ('HTTP/1.0 401 Unauthorized'); +} +/* access rights + ---------------------------------------------------------------------------------------------------------------------- */ //get the list of moderators: $MODS = array ( - //mods.txt on root for mods on all sub-forums + //'mods.txt' on root for mods on all sub-forums 'GLOBAL'=> file_exists (FORUM_ROOT.'/mods.txt') ? file (FORUM_ROOT.'/mods.txt', FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES) : array (), - //if in a sub-forum, the local mods.txt + //if in a sub-forum, the local 'mods.txt' 'LOCAL' => PATH && file_exists ('mods.txt') ? file ('mods.txt', FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES) : array () ); +//get the list (if any) of users allowed to access this current forum +$MEMBERS = file_exists ('members.txt') ? file ('members.txt', FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES) : array (); + +//can the current user moderate in this forum? +define ('CAN_MOD', HTTP_AUTH ? isMod (NAME) : false); + +//can the current user post new threads in the current forum? +//(posting replies is dependent on the the thread -- if locked -- so tested in 'thread.php') +define ('CAN_POST', FORUM_ENABLED && ( + //- if the user is a moderator or member of the current forum, they can post + CAN_MOD || isMember (NAME) || + //- if the forum is unlocked (mods will have to log in to see the form) + !FORUM_LOCK +)); + +//if the forum is private, check the current user and issue an auth request if not signed in or allowed +if (FORUM_LOCK == 'private' && !(CAN_MOD || isMember (NAME))) { + header ('WWW-Authenticate: Basic'); + header ('HTTP/1.0 401 Unauthorized'); + //todo: a proper error page, if I make a splash/login screen for a private root-forum + die ("Authorisation required."); +} /* ---------------------------------------------------------------------------------------------------------------------- */ @@ -114,22 +168,6 @@ header ('Cache-Control: no-cache', true); header ('Expires: 0', true); - -/* ====================================================================================================================== */ - -// -//we could of course do all the XML manipulation in DOM proper to save doing this… -class allow_prepend extends SimpleXMLElement { - public function prependChild ($name, $value=null) { - $dom = dom_import_simplexml ($this); - $new = $dom->insertBefore ( - $dom->ownerDocument->createElement ($name, $value), - $dom->firstChild - ); - return simplexml_import_dom ($new, get_class ($this)); - } -} - /* ====================================================================================================================== */ //sanitise input: @@ -165,7 +203,7 @@ function template_tags ($template, $values) { return $template; } -//produces a truncated list of pages around the current page +//produces a truncated list of page numbers around the current page function pageList ($current, $total) { //always include the first page $PAGES[] = 1; @@ -273,12 +311,17 @@ function formatText ($text) { /* ====================================================================================================================== */ -//check to see if a name is a known moderator in mods.txt +//check to see if a name is a known moderator in 'mods.txt' function isMod ($name) { global $MODS; return in_array (strtolower ($name), array_map ('strtolower', $MODS['GLOBAL'] + $MODS['LOCAL'])); } +function isMember ($name) { + global $MEMBERS; + return in_array (strtolower ($name), array_map ('strtolower', $MEMBERS)); +} + /* ====================================================================================================================== */ //regenerate a folder's RSS file (all changes happening in a folder) @@ -330,11 +373,11 @@ function indexRSS () { /* sitemap -------------------------------------------------------------------------------------------------------------- */ + //we’re going to use the RSS files as sitemaps chdir (FORUM_ROOT); - //we’re going to use the RSS files as sitemaps - $folders = array (''); //get list of sub-forums + $folders = array (''); foreach (array_filter ( //include only directories, but ignore directories starting with ‘.’ and the users / themes folders preg_grep ('/^(\.|users$|themes$)/', scandir (FORUM_ROOT.'/'), PREG_GREP_INVERT), 'is_dir' @@ -370,4 +413,19 @@ function indexRSS () { clearstatcache (); } -?> +/* ====================================================================================================================== */ + +// +//we could of course do all the XML manipulation in DOM proper to save doing this… +class allow_prepend extends SimpleXMLElement { + public function prependChild ($name, $value=null) { + $dom = dom_import_simplexml ($this); + $new = $dom->insertBefore ( + $dom->ownerDocument->createElement ($name, $value), + $dom->firstChild + ); + return simplexml_import_dom ($new, get_class ($this)); + } +} + +?> \ No newline at end of file diff --git a/themes/greyscale/append.inc.php b/themes/greyscale/append.inc.php index fa1d365..d46df5c 100644 --- a/themes/greyscale/append.inc.php +++ b/themes/greyscale/append.inc.php @@ -117,23 +117,28 @@
-

- Moderators for this sub-forum: - , ', array_map ('safeHTML', $MODS['LOCAL']))?> -

- - -

- Your friendly neighbourhood moderators: - , ', array_map ('safeHTML', $MODS['GLOBAL']))?> -

+

+ Moderators for this sub-forum: + , ', array_map ('safeHTML', $MODS['LOCAL']))?> +

+ +

+ Your friendly neighbourhood moderators: + , ', array_map ('safeHTML', $MODS['GLOBAL']))?> +

+ +

+ Members of this forum: + , ', array_map ('safeHTML', $MEMBERS))?> +

-
- - + \ No newline at end of file diff --git a/themes/greyscale/delete.inc.php b/themes/greyscale/delete.inc.php index 7b2e179..4bb5604 100644 --- a/themes/greyscale/delete.inc.php +++ b/themes/greyscale/delete.inc.php @@ -119,23 +119,28 @@
-

- Moderators for this sub-forum: - , ', array_map ('safeHTML', $MODS['LOCAL']))?> -

- - -

- Your friendly neighbourhood moderators: - , ', array_map ('safeHTML', $MODS['GLOBAL']))?> -

+

+ Moderators for this sub-forum: + , ', array_map ('safeHTML', $MODS['LOCAL']))?> +

+ +

+ Your friendly neighbourhood moderators: + , ', array_map ('safeHTML', $MODS['GLOBAL']))?> +

+ +

+ Members of this forum: + , ', array_map ('safeHTML', $MEMBERS))?> +

-
- - + \ No newline at end of file diff --git a/themes/greyscale/index.inc.php b/themes/greyscale/index.inc.php index c0d57d1..7eac732 100644 --- a/themes/greyscale/index.inc.php +++ b/themes/greyscale/index.inc.php @@ -44,10 +44,13 @@ --> @@ -66,6 +69,20 @@ + +

+ Only moderators or members can start new threads here, but anybody can reply to existing threads. +

+ +

+ Only moderators or members can participate here. +

+ +

+ Only moderators or members can access and participate here. +

+ +

Threads

@@ -87,11 +104,11 @@
+

Add Thread

-

- + + +

+ +

+

(Leave this as-is, it’s a trap!)

+ -

There is no need to “register”, just enter the same name + password of your choice every time.

- + //if signed in, there's no password field + if (HTTP_AUTH): ?> +

(Quit your browser or clear the browser cache to sign out.)

+

Only registered users can post.
No new registrations are allowed.

+ +

There is no need to “register”, just enter the same name + password of your choice every time.

@@ -160,31 +188,40 @@

- -

Sorry, posting is currently disabled.

-
+
-

- Moderators for this sub-forum: - , ', array_map ('safeHTML', $MODS['LOCAL']))?> -

- - -

- Your friendly neighbourhood moderators: - , ', array_map ('safeHTML', $MODS['GLOBAL']))?> -

+

+ Moderators for this sub-forum: + , ', array_map ('safeHTML', $MODS['LOCAL']))?> +

+ +

+ Your friendly neighbourhood moderators: + , ', array_map ('safeHTML', $MODS['GLOBAL']))?> +

+ +

+ Members of this forum: + , ', array_map ('safeHTML', $MEMBERS))?> +

-
- - + \ No newline at end of file diff --git a/themes/greyscale/lock.inc.php b/themes/greyscale/lock.inc.php index cd9e9bc..60dd5e8 100644 --- a/themes/greyscale/lock.inc.php +++ b/themes/greyscale/lock.inc.php @@ -86,23 +86,28 @@
-

- Moderators for this sub-forum: - , ', array_map ('safeHTML', $MODS['LOCAL']))?> -

- - -

- Your friendly neighbourhood moderators: - , ', array_map ('safeHTML', $MODS['GLOBAL']))?> -

+

+ Moderators for this sub-forum: + , ', array_map ('safeHTML', $MODS['LOCAL']))?> +

+ +

+ Your friendly neighbourhood moderators: + , ', array_map ('safeHTML', $MODS['GLOBAL']))?> +

+ +

+ Members of this forum: + , ', array_map ('safeHTML', $MEMBERS))?> +

-
- - + \ No newline at end of file diff --git a/themes/greyscale/theme.css b/themes/greyscale/theme.css index 8618472..8963ce6 100644 --- a/themes/greyscale/theme.css +++ b/themes/greyscale/theme.css @@ -26,7 +26,8 @@ html {font: 16px/20px Corbel, "URW Gothic L", "Liberation Sans", "Trebuchet M /* page centering / width */ -#mast nav, section, #mods {width: 80%; max-width: 1024px;} +#mast nav, section, #mods, + #rights {width: 80%; max-width: 1024px;} h1 {margin: 0; padding: 20px 0 20px 60px; font-weight: bold; font-size: 22px; line-height: 20px; color: #f7f6f7; @@ -107,6 +108,9 @@ input[type=search]:focus {outline: none;} #go {position: relative; margin-left: -20px; top: -20px;} +#rights {margin: 0 auto; text-align: center; color: #888;} +#rights a {color: #666;} + /* index page ====================================================================================================================== */ @@ -224,9 +228,9 @@ input, textarea {font: 16px/20px Corbel, "Liberation Sans", "Trebuchet MS", sa label {display: block; height: 40px; padding: 0 20px; line-height: 40px; color: #222;} form p {padding: 0; color: #888;} -#ok, #error, #markup {padding: 20px !important;} +#ok, #error, #markup {padding: 20px 20px 0 !important;} #error {color: #d11;} -#markup {padding-top: 0 !important;} + input[type=text], input[type=password], textarea {-webkit-box-shadow: inset 0 5px 5px -6px black; @@ -312,6 +316,9 @@ footer {position: absolute; height: 60px; margin: 20px 0 0; padding: 0 0 20px footer p {margin: 0 auto 0 10%; padding-left: 20px; font-size: .9em; color: #f7f6f7;} footer p a {color: #888;} +p#login {margin: -40px 10% 0 auto; padding-right: 20px; text-align: right;} +p#login a {text-transform: uppercase;} + /* ====================================================================================================================== mobile / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / diff --git a/themes/greyscale/thread.inc.php b/themes/greyscale/thread.inc.php index 9742793..1f5e989 100644 --- a/themes/greyscale/thread.inc.php +++ b/themes/greyscale/thread.inc.php @@ -44,7 +44,9 @@ -->