I created a gaming community website about 13 years ago. It uses PHP, SQL, HTML, and CSS, and does not use a framework or template engine. It includes features such as a login system for members, different levels of access depending on your rank, dropping members that are inactive from the member list, and other features useful to a gaming community. At its height, the gaming community that this website supported had 300 active members, so this code has been in production for a while. This website's PHP code has about 97 versions.
I am behind on PHP best practices. I am thinking of upgrading the site, as practice to get me back up to speed, and from there I can use that knowledge and skill to create new websites. However, I would like some advice on what direction to take this website and my PHP programming in general.
Questions
Do I need to learn a framework like Laravel and convert everything to that? The learning curve for frameworks looks steep. Is it worth it? What kinds of features do they provide?
Do I need to start using classes and OOP? What are the benefits? Where would I start? As you can see, all of my code from this sample page is non-OOP.
I am already aware of a couple of things that I should upgrade. For example, I need to convert from MYSQL to PDO for security, a template engine like Smarty is better for code readability, I should sanitize my incoming $_POST data, I should use an ENUM for access levels instead of numbers, etc. There's a lot that needs to be upgraded.
Sample code
Here is a page in the member area that allows high ranking members to add, edit, and delete games, and pick which games that the community supports. Whatever games are supported are added to the join form, member profiles, etc. for members to choose whether or not they play them.
Screenshot
games_manage.php
<?php
if (authenticate ("9", "0", "1") == "verified")
{
// *** ADD FORM PROCESSOR ****
if ( $_GET['action'] == "add" && $_POST['submit'] == "Add Game" )
{
$game_name = $_POST['game_name'];
$game_image_url = $_POST['game_image_url'];
$game_active = $_POST['game_active'];
$query = mysql_query("INSERT INTO game_list SET game_name = '$game_name', game_image_url = '$game_image_url', game_active = '$game_active';");
addtolog("$username_l added $game_name to the list of games played");
feedback ("Add Game", "Successful", "Manage Games", "console.php?id=games_manage");
}
// *** ADD ****
else if ( $_GET['action'] == "add" )
{
print("
<form action=\"console.php?id=games_manage&action=add\" method=\"post\">
<h2><center>
Add Game
</center></h2>
<p align=\"center\">
<b>Game Name:</b><br />
<input name=\"game_name\" size=\"20\" />
</p>
<p align=\"center\">
<b>Image URL:</b><br />
<input name=\"game_image_url\" size=\"50\" />
</p>
<p align=\"center\">
<b>Visible?</b><br />
Does our community play this game? Do you<br />
want this game to show up on the website?<br />
<input type=\"radio\" name=\"game_active\" value=\"1\" checked /> Yes<br>
<input type=\"radio\" name=\"game_active\" value=\"0\" /> No<br>
</p>
<p align=\"center\">
<input type=\"submit\" value=\"Add Game\" name=\"submit\" size=\"10\">
<input type=\"reset\" value=\"Reset\" name=\"reset\" size=\"10\">
</p>
</form>
");
}
// *** DELETE FORM PROCESSOR ****
else if ( $_GET['action'] == "delete" )
{
$game_id = $_GET['game'];
$query = mysql_query("SELECT game_name FROM game_list WHERE game_id = '$game_id';");
$myrow = mysql_fetch_array($query);
extract($myrow);
$query = mysql_query("DELETE FROM game_list WHERE game_id = '$game_id';");
$query = mysql_query("DELETE FROM game_players WHERE game_id = '$game_id';");
addtolog("$username_l deleted $game_name from the list of games played");
feedback ("Delete Game", "Successful", "Manage Games", "console.php?id=games_manage");
}
// *** EDIT FORM PROCESSOR ****
else if ( $_GET['action'] == "edit" && $_POST['submit'] == "Edit Game" )
{
$game_id = $_GET['game'];
$game_name = $_POST['game_name'];
$game_image_url = $_POST['game_image_url'];
$game_active = $_POST['game_active'];
$query = mysql_query("UPDATE game_list SET game_name = '$game_name', game_image_url = '$game_image_url', game_active = '$game_active' WHERE game_id = '$game_id';");
addtolog("$username_l edited the details of the game $game_name");
feedback ("Edit Game", "Successful", "Manage Games", "console.php?id=games_manage");
}
// *** EDIT ****
else if ( $_GET['action'] == "edit" )
{
$game_id = $_GET['game'];
$query = mysql_query("SELECT * FROM game_list WHERE game_id = '$game_id';");
$myrow = mysql_fetch_array($query);
extract($myrow);
if ($game_active == "0")
{$checked0 = "checked";}
elseif ($game_active == "1")
{$checked1 = "checked";}
print("
<form action=\"console.php?id=games_manage&action=edit&game=$game_id\" method=\"post\">
<h2><center>
Edit Game
</center></h2>
<p align=\"center\">
<b>Game Name:</b><br />
<input name=\"game_name\" size=\"20\" value=\"$game_name\" />
</p>
<p align=\"center\">
<b>Image URL:</b><br />
<input name=\"game_image_url\" size=\"50\" value=\"$game_image_url\" />
</p>
<p align=\"center\">
<b>Image:</b><br />
<img src=\"$game_image_url\" width=\"$game_icon_width_in_px" . "px\" alt=\"Error!\" title=\"$game_name\" />
</p>
<p align=\"center\">
<b>Active?</b><br />
Does our community play this game? Do you<br />
want this game to show up on the website?<br />
<input type=\"radio\" name=\"game_active\" value=\"1\" $checked1 /> Yes<br>
<input type=\"radio\" name=\"game_active\" value=\"0\" $checked0 /> No<br>
</p>
<p align=\"center\">
<input type=\"submit\" value=\"Edit Game\" name=\"submit\" size=\"10\">
<input type=\"reset\" value=\"Reset\" name=\"reset\" size=\"10\">
</p>
</form>
");
}
// *** LIST ****
else
{
print("
<h2><center>
Manage Games
</center></h2>
<p>
Use this page to manage the games that your users can mark that they play. Useful for communities that support multiple games. You can even add games not listed here.
</p>
<p align=\"center\">
<input type=\"submit\" value=\"Add Game\" onclick=\"window.location='console.php?id=games_manage&action=add';\" />
</p>
<center>
<table width=\"500px\" class=\"valign_middle\">
<tr class=\"valign_middle\">
<th width=\"110px\" class=\"valign_middle\">Options</th>
<th class=\"valign_middle\">Visible?</th>
<th class=\"valign_middle\">Game</th>
<th class=\"valign_middle\" align=\"center\">Image</th>
<!-- <th class=\"valign_middle\">Image URL</th> -->
</tr>
");
$query = mysql_query("SELECT * FROM game_list ORDER BY game_active DESC, game_name ASC;");
while ( $myrow = mysql_fetch_array($query) )
{
extract($myrow);
if ( $game_active == 1)
{
$game_active_text = "<b>Yes</b>";
}
else
{
$game_active_text = "No";
}
print("
<tr class=\"valign_middle\">
<td class=\"valign_middle\">
<input type=\"submit\" value=\"Edit\" onclick=\"window.location='console.php?id=games_manage&action=edit&game=$game_id';\" />
<input type=\"submit\" value=\"Delete\" onclick=\"window.location='console.php?id=games_manage&action=delete&game=$game_id';\" />
</td>
<td class=\"valign_middle\">
$game_active_text
</td>
<td class=\"valign_middle\">
$game_name
</td>
<td class=\"valign_middle\" align=\"center\">
<img src=\"$game_image_url\" width=\"$game_icon_width_in_px" . "px\" alt=\"Error!\" title=\"$game_name\" />
</td>
<!--
<td class=\"valign_middle\">
$game_image_url
</td>
-->
</tr>
");
}
print("
</table>
</center>
");
}
}
?>
Security
My website's lack of security has been mentioned in several posts. I do have some security, since this is production code. I just didn't post the gigantic function and navigation files that get loaded before the code for this specific page.
SQL injection defense code
This code runs mysql_real_escape_string on all incoming GET, POST, and COOKIE data.
if ( get_magic_quotes_gpc() != 1 )
{
foreach ($_POST as $key=>$val)
{
$_POST[$key]=mysql_real_escape_string($val);
}
foreach ($_GET as $key=>$val)
{
$_GET[$key]=mysql_real_escape_string($val);
}
foreach ($_COOKIE as $key=>$val)
{
$_COOKIE[$key]=mysql_real_escape_string($val);
}
}
XSS defense code
This code runs strip_tags on all incoming GET, POST, and COOKIE data if the user is not a high rank in the clan. High ranking users are assumed to be trusted to include HTML tags in their incoming form data, so that they can do things like add HTML to their internal news posts.
Yes, this is vulnerable. Adding this, at the time, was easier than sanitizing all incoming data and all outgoing data on a case-by-case basis.
if (authenticate ("6", "0", "0") != "verified")
{
if (is_array ($_GET))
{
while (list($k, $v) = each($_GET))
{
if (is_array ($_GET[$k]))
{
while (list ($k2, $v2) = each ($_GET[$k]))
{$_GET[$k][$k2] = strip_tags ($v2);}
@reset($_GET[$k]);
}
else
{$_GET[$k] = strip_tags($v);}
}
@reset($_GET);
}
if (is_array ($_POST))
{
while (list($k, $v) = each($_POST))
{
if (is_array ($_POST[$k]))
{
while (list ($k2, $v2) = each ($_POST[$k]))
{$_POST[$k][$k2] = strip_tags ($v2);}
@reset($_POST[$k]);
}
else
{$_POST[$k] = strip_tags($v);}
}
@reset($_POST);
}
if (is_array ($_COOKIE))
{
while (list($k, $v) = each($_COOKIE))
{
if (is_array ($_COOKIE[$k]))
{
while (list ($k2, $v2) = each ($_COOKIE[$k]))
{$_COOKIE[$k][$k2] = strip_tags ($v2);}
@reset($_COOKIE[$k]);
}
else
{$_COOKIE[$k] = strip_tags($v);}
}
@reset($_COOKIE);
}
}
CSRF
I do not have a defense against CSRF, since I only read about that the other day, shortly before making this code review.
Expiring cookies
Cookies (and therefore user logins) expire after 30 days.
setcookie ($cookie_prefix . "_name", $username, time () + 2592000, "/", $domain_name);
setcookie ($cookie_prefix . "_pass", $password, time () + 2592000, "/", $domain_name);
setcookie ($cookie_prefix . "_name", $username, time () + 2592000, "/", 'www.' . $domain_name);
setcookie ($cookie_prefix . "_pass", $password, time () + 2592000, "/", 'www.' . $domain_name);
Sessions
I chose to not use sessions. The cookies handle everything related to user logins.
IP ban for too many login attempts
More than 10 failed login attempts results in a 1 day IP ban from the entire site. The IP ban looks at the first 3 digits of the IP address, which helps to block some types of dynamic IP where the 4th digit will change but the rest of the digits will stay the same. This is a less effective technique now, with the prevalence of static IP's at home, less AOL users (often dynamic IP following the aforementioned pattern), and mobile phones with constantly changing 3G/4G IP's, but it was a decent idea back in the day.
I have not added support for IPv6. If somebody connects to my website using an IPv6, it may throw some kind of error when my code tries to add the overly large IPv6 IP to the IP log. I haven't tested it yet.
// unsuccessful login
else
{
$query = mysql_query ("SELECT * FROM logins WHERE ip1 = '$ip1' AND ip2 = '$ip2' AND ip3 = '$ip3';");
$myrow = mysql_fetch_array ($query);
if ($myrow == NULL)
{
$query = mysql_query ("INSERT INTO logins SET ip1 = '$ip1', ip2 = '$ip2', ip3 = '$ip3', logins = '1', lastseen = '$datetime';");
}
else
{
extract ($myrow);
$period = date ("Y-m-d H:i:s", mktime (date ("H"), date ("i"), date ("s"), date ("m"), date ("d") - 1, date ("Y")));
if ($lastseen >= $period)
{
$logins = $logins + 1;
$query = mysql_query ("UPDATE logins SET logins = '$logins', lastseen = '$datetime' WHERE ip1 = '$ip1' AND ip2 = '$ip2' AND ip3 = '$ip3';");
}
else
{
$query = mysql_query ("UPDATE logins SET logins = '1', lastseen = '$datetime' WHERE ip1 = '$ip1' AND ip2 = '$ip2' AND ip3 = '$ip3';");
}
if ($logins == "10")
{
$bantime = date ("Y-m-d H:i:s", mktime (date ("H"), date ("i"), date ("s"), date ("m"), date ("d") + 1, date ("Y")));
$query = mysql_query ("INSERT INTO ipbans SET ip1 = '$ip1', ip2 = '$ip2', ip3 = '$ip3', expiration = '$bantime', description = '10 Failed Logins (Auto Ban)';");
}
}
print ("<html><head><meta http-equiv=\"refresh\" content=\"3;url=console.php\"></head><body><h2>Login Incorrect!</h2><p><a href=\"javascript:history.go(-1)\">Go Back</a></p></body></html>");
}