@nobackseat: my comments were for the sake of anyone unaware of the issues at play, including tradervic and future readers. For instance, you obviously know about SQL injection, but tradervic doesn't (in all likelihood). Without such warnings, sample code winds up in production code. The key statement in the post is "often example scripts do things that shouldn't be done in production code"; the rest was basically expanding on that.
As for nitpicking, that's what it takes to be a developer. The devil's in the details, after all.
Did I mention I don't know jack squat about all this stuff?
In that case, you might want to read over the recommendations in the thread "
Want to learn to program....", or look over the
web development book recommendations on StackOverflow. Otherwise you'll be stuck relying on the help of others, or paying others to do your work.
Anyway, I hate to see you arguing with each other
It's nothing serious. Technical discussions may
seem combative, but it's really just a no-nonsense communication style. Arguments can be discussions or fights, and this certainly isn't a fight.
As for drop-in PHP code, here's a rewrite of nobackseat's with my suggestions factored in (not that it doesn't have room for improvement). No concern for site design has been taken into account; you'll have to style and structure the HTML as you see fit. That said, the styling should include something like the following:
Code:
.poem, .poem li {
list-style-type: none;
margin: 0;
padding: 0;
}
#line {
width: 50%;
}
In the following code, I'm assuming column `poem` has been renamed `line`, which is a closer description of what's stored in the column. This can be done with the following SQL statement or in phpMyAdmin, accessible through cPanel.
Code:
ALTER TABLE `poem` CHANGE COLUMN `poem` `line` VARCHAR(255);
Alternatively, change "line" in any SQL statement to "poem".
We first create a file called "config.php" to hold all configuration data. This file gathers together the stuff that might need to be edited by a site administrator (as opposed to a developer); code outside of this file shouldn't need to be altered, unless you're adding features (such as supporting more than one poem) or fixing bugs. This file should be given
permissions mode 600 to protect the sensitive data from prying eyes, which you can do in
cPanel or your FTP program.
PHP:
<?php
$cfg = array(
'db' => array(
'name' => '...', // the database name, e.g. trigger_poetry
'user' => '...', // the DB username
'password' => '...'
)
);
?>
"localDB.php" defines the code responsible for creating a DB connection (in this case a function named
localDBConnect).
PHP:
<?php
function localDBConnect($dbName=Null) {
global $cfg;
if (is_null($dbName)) {
$dbName = $cfg['db']['name'];
}
static $db = array();
if (empty($db[$dbName])) {
$db[$dbName] = new PDO("mysql:host=localhost;dbname=$dbName", $cfg['db']['user'], $cfg['db']['password']);
}
$db[$dbName]->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $db[$dbName];
}
?>
"poem.php" displays the poem and a form to enter a new line.
PHP:
<?php
function printPoem() {
try {
// turn on output buffering so we can discard poem if an exception occurs
ob_start();
$db = localDBConnect();
$lines = $db->query('SELECT line FROM poem ORDER BY ID');
echo " <ol class='poem'>\n";
foreach ($lines as $line) {
echo " <li>$line[0]</li>\n";
}
echo " </ol> <hr />\n";
ob_end_flush();
printNewPoemLineForm();
} catch (PDOException $exc) {
ob_end_clean();
error_log($exc);
echo "Looks like we're having internal problems. An error has been logged and we'll look into it.";
// saffolding: display the exception in the browser
global $dbgLvl;
if ($dbgLvl) {
echo '<pre>', $exc, '</pre>';
}
}
}
function printNewPoemLineForm() {
static $formNotices = array(
'line' => array('empty' => "New line was blank. Please type something if you wish to add a line."),
'form' => array()
);
?>
<script type="text/javascript" src="js/form.js"></script>
<form action='line.php' method='POST' onsubmit="return validate(this)">
<?php
$line='';
if (isset($_REQUEST['notices'])) {
$notices = array_intersect_key($_REQUEST['notices'], $formNotices);
if ($notices) {
if (! empty($_REQUEST['line'])) {
// keep the line, if it exists, so user doesn't need to re-enter it
$line = htmlspecialchars($_REQUEST['line']);
}
echo "<ul class='notices'>\n";
foreach ($notices as $field => $msgId) {
if (isset($formNotices[$field][$msgId])) {
$msg = $formNotices[$field][$msgId];
} else {
$msg = strip_tags($msgId);
}
echo ' <li>', $msg, "</li>\n";
}
echo " </ul>\n";
}
}
?>
<label for="line">New line:</label>
<input name="line" id="line" value="<?php echo $line; ?>"/>
<button name='action' value='add'>Add line</button>
</form>
<?php
}
?>
To use poem.php, include it in your index.php (e.g. with"
<?php include_once('poem.php'); ?>") and call
printPoem(). Error messages can be passed to the page via the 'notices' parameter, though the sample code only supports a limited variety of notices. How notices are handled could use quite a bit of cleanup.
poem.php also references "js/form.js", which can be used for client-side validation. In it, define
validate(). It's not strictly necessary, but good for
usability's sake (I'll post more on this later).
"line.php" handles requests to add a line to the poem.
PHP:
<?php
include_once('config.php');
include_once('localDB.php');
$_REQUEST['line'] = trim($_REQUEST['line']);
if (empty($_REQUEST['line'])) {
header('Location: ' . dirname($_SERVER['REQUEST_URI']) . "?notices[line]=empty");
} else {
try {
$dbConn = localDBConnect();
$insertLineStmt = $dbConn->prepare('INSERT INTO `poem` (`line`, `ip`) VALUES (:line, :ip)');
$insertLineStmt->execute(array(':line' => strip_tags($_REQUEST['line'], '<b><i><u><s><em><strong><strike><pre><del><ins><sup><sub>'),
':ip' => $_SERVER['REMOTE_ADDR']));
header('Location: ' . dirname($_SERVER['REQUEST_URI']));
} catch (PDOException $exc) {
error_log($exc);
header('Location: '
. dirname($_SERVER['REQUEST_URI'])
. "?notices[form]=" + urlencode("Whoops... I had an internal problem with my database. It's been logged, and we'll look into it."));
}
}
?>
A major flaw that's yet to be addressed is flood-prevention. Someone could submit many new lines in a short period. To handle this, we could implement a wait period, so people must wait (say) 2 minutes before they can post a new line (wait period can be set in config.php). This could be based on IP addresses or user accounts, which is a little more complex to implement. For the former, people could use proxies to get around flood-detection. With IP checking, we'd also better include a limit on the overall frequency of submissions: keep an
exponential running average of the time between submissions; whenever this exceeds some value (set in config.php), submitted lines aren't added. Just be sure to print messages informing users when and why their submissions are throttled. We could also implement only the global throttling and skip per-IP/per-user throttling.