PHP 주석 시스템
기초
간단한 댓글 시스템을 만들었습니다. 제 목표는 많은 프로그램을 설치하지 않고도 모든 사람의 서버에서 쉽게 사용할 수있는 시스템을 만드는 것이 었습니다. 또한 가능한 한 개인 정보 보호를 위해 만들려고 노력했습니다 (이메일 주소, 쿠키 없음). 또한 데이터베이스없이이 문제를 해결해야합니다.
기능성
- 새 의견 제출을위한 기본 양식
- 플래그 기능 (웹 사이트 소유자에게 간단한 이메일 보내기)
- 들여 쓰기 된 답변으로 답변 기능
암호
simpleComments.php
이 스크립트는 스팸 방지 ( 여기 및 여기 에서 제안 ), 댓글 보내기, 응답 및 플래그 지정과 같은 주요 기능을 제공합니다 . 특히 기능 save()외모가 다소 해키 솔루션 이라고 생각합니다 . 더 나은 대안 (데이터베이스없이)을 알고 있다면 기꺼이 듣고 싶습니다.
//The password for the AES-Encryption (has to be length=16)
$encryptionPassword = "****************"; //============================================================================================ //============================================================================================ // == // FROM HERE ON NO ADJUSTMENT NECESSARY == // == //============================================================================================ //============================================================================================ /** * Creates image * * This function creates a black image with the random exercise created by randText() on it. * Additionally the function adds some random lines to make it more difficult for bots to read * the text via OCR. The result (for example) looks like this: https://imgur.com/a/6imIE73 * * @author Philipp Wilhelm * * @since 1.0 * * @param string $rand Random exercise created by randText()
* @param int $width Width of the image (default = 200) * @param int $height Height of the image (default = 50)
* @param int $textColorRed R-RGB value for the textcolor (0-255) (default = 255) * @param int $textColorGreen G-RGB value for the textcolor (0-255) (default = 255)
* @param int $textColorBlue B-RGB value for the textcolor (0-255) (default = 255) * @param int $linesColorRed R-RGB value for the random lines (0-255) (default = 192)
* @param int $linesColorGreen G-RGB value for the random lines (0-255) (default = 192) * @param int $linesColorBlue B-RGB value for the random lines (0-255) (default = 192)
* @param int $fontSize font size of the text on the image (1-5) (default = 5) * @param int $upperLeftCornerX x-coordinate of upper-left corner of the first char (default = 18)
* @param int $upperLeftCornerY y-coordinate of the upper-left corner of the first char (default = 18) * @param int $angle angle the text will be rotated by (default = 10)
*
* @return string created image surrounded by <img>
*/
function randExer($rand, $width = 200, $height = 50, $textColorRed = 255, $textColorGreen = 255, $textColorBlue = 255, $linesColorRed = 192, $linesColorGreen = 192, $linesColorBlue = 192, $fontSize = 5, $upperLeftCornerX = 18, $upperLeftCornerY = 18, $angle = 10) { global $encryptionPassword;
$random = openssl_decrypt($rand,"AES-128-ECB", $encryptionPassword); $random = substr($random, 0, -40); //Creates a black picture $img = imagecreatetruecolor($width, $height);
//uses RGB-values to create a useable color
$textColor = imagecolorallocate($img, $textColorRed, $textColorGreen, $textColorBlue); $linesColor = imagecolorallocate($img, $linesColorRed, $linesColorGreen, $linesColorBlue);
//Adds text
imagestring($img, $fontSize, $upperLeftCornerX, $upperLeftCornerY, $random . " = ?", $textColor);
//Adds random lines to the images
for($i = 0; $i < 5; $i++) { imagesetthickness($img, rand(1, 3));
$x1 = rand(0, $width / 2);
$y1 = rand(0, $height / 2);
$x2 = $x1 + rand(0, $width / 2); $y2 = $y1 + rand(0, $height / 2);
imageline($img, $x1, $x2, $x2, $y2, $linesColor);
}
$rotate = imagerotate($img, $angle, 0); //Attribution: https://stackoverflow.com/a/22266437/13634030 ob_start(); imagejpeg($rotate);
$contents = ob_get_contents(); ob_end_clean(); $imageData = base64_encode($contents); $src = "data:" . mime_content_type($contents) . ";base64," . $imageData;
return "<img alt='' src='" . $src . "'/>"; }; /** * Returns time stamp * * This function returns the current time stamp, encrypted with AES, by using the standard function time(). * * @author Philipp Wilhelm * * @since 1.0 * * @return int time stamp */ function getTime() { global $encryptionPassword;
return openssl_encrypt(time() . bin2hex(random_bytes(20)),"AES-128-ECB", $encryptionPassword); } /** * Creates random exercise * * This function creates a random simple math-problem, by choosing two random numbers between "zero" and "ten". * The result looks like this: "three + seven" * * @author Philipp Wilhelm * * @since 1.0 * * @return string random exercise */ function randText() { global $encryptionPassword;
//Creating random (simple) math problem
$arr = array("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"); $item1 = $arr[array_rand($arr)];
$item2 = $arr[array_rand($arr)]; $random = $item1 . " + " . $item2;
$encrypted = openssl_encrypt($random . bin2hex(random_bytes(20)),"AES-128-ECB", $encryptionPassword); return $encrypted;
}
/**
* flags comment
*
* This function sends an email to the specified adress containing the id of the flagged comment
*
* @author Philipp Wilhelm
*
* @since 1.0
*
* @param string $to Email-adress the mail will be send to * @param string $url URL of the site the comment was flagged on
*
*/
function flag($to, $url) {
//Which comment was flagged?
$id = $_POST["comment"];
//At what side was the comment flagged?
$referer = $_SERVER["HTTP_REFERER"];
$subject = "FLAG"; $body = $id . " was flagged at " . $referer . ".";
//Send the mail
mail($to, $subject, $body); //Redirect to what page after flag? //(In this case to the same page) header("Location:" . $url);
exit();
}
/**
* redirects to the same page, but with the added parameter to specify to which
* comment will be answered and jumps right to the comment-form
*
*
* @author Philipp Wilhelm
*
* @since 1.0
*
* @param string $url the url of the current page * @param string $buttonName URL of the site the comment was flagged on
* @param string $urlName the "id-name" * */ function answer($url, $buttonName, $urlName) {
header("Location:" . $url . "?" . $urlName . "=" . $_POST["comment"] . "#" . $buttonName);
exit();
}
/**
* error message
*
* Redirects to the specified url to tell the user that something went wrong
* e.g. entered wrong solution to math-exercise
*
* @author Philipp Wilhelm
*
* @since 1.0
*
* @param string $urlError The specified url * */ function error($urlError) {
header("Location:" . $urlError); die(); } /** * Redirects to specified url when user enters words that are on the "blacklist" * * @author Philipp Wilhelm * * @since 1.0 * * @param string $urlBadWords The specified url to which will be redirected
*
*/
function badWords($urlBadWords) { header("Location:" . $urlBadWords);
die();
}
/**
* Redirects to same url after comment is successfully submitted - comment will be visible
* immediately
*
* @author Philipp Wilhelm
*
* @since 1.0
*
* @param string $url URL of the site * */ function success($url) {
header("Location:" . $url); die(); } /** * checks if user enters any words that are on the "blacklist" * * @author Philipp Wilhelm * * @since 1.0 * * @param string $text The user-entered text
* @param string $blackList filename of the "blacklist" * * @return boolean true if user entered a word that is on the "blacklist" * */ function isForbidden($text, $blackList) { //gets content of the blacklist-file $content = file_get_contents($blackList); $text = strtolower($text); //Creates an array with all the words from the blacklist $explode = explode(",", $content); foreach($explode as &$value) { //Pattern checks for whole words only ('hell' in 'hello' will not count) $pattern = sprintf("/\b(%s)\b/",$value); if(preg_match($pattern, $text) == 1) { return true; } } return false; } /** * saves a new comment or an answer to a comment * * @author Philipp Wilhelm * * @since 1.0 * * @param string $url Email-adress the mail will be send to
* @param string $urlError URL to the "error"-page * @param string $urlBadWords URL to redirect to , when user uses words on the "blacklist"
* @param string $blacklist filename of the blacklist * @param string $fileName filename of the file the comments are stored in
* @param string $nameInputTagName name of the input-field for the "name" * @param string $messageInputTagName name of the input-field for the "message"
* @param string exerciseInputTagName name of the input-field the math-problem is stored in
* @param string solutionInputTagName name of the input-field the user enters the solution in
* @param string $answerInputTagName in this field the id of the comment the user answers to is saved * (if answering to a question) * @param string $timeInputTagName name of the input-field the timestamp is stored in
*
*/
function save($url, $urlError, $urlBadWords, $blacklist, $fileName, $nameInputTagName, $messageInputTagName, $exerciseInputTagName, $solutionInputTagName, $answerInputTagName, $timeInputTagName) { global $encryptionPassword;
$solution = filter_input(INPUT_POST, $solutionInputTagName, FILTER_VALIDATE_INT);
$exerciseText = filter_input(INPUT_POST, $exerciseInputTagName);
if ($solution === false || $exerciseText === false) {
error($urlError); } $time = openssl_decrypt($_POST[$timeInputTagName], "AES-128-ECB", $encryptionPassword); if(!$time) {
error($urlError); } $time = substr($time, 0, -40); $t = intval($time); if(time() - $t > 300) {
error($urlError); } //Get simple math-problem (e.g. four + six) $str = openssl_decrypt($_POST[$exerciseInputTagName], "AES-128-ECB", $encryptionPassword); $str = substr($str, 0, -40); if (!$str) {
error($urlError); } $arr = array("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten");
//gets array with written numbers
$words = array_map("trim", explode("+", $str));
//gets the numbers as ints
$numbers = array_intersect($arr, $words); if (count($numbers) != 2) {
error($urlError); } $sum = array_sum(array_keys($numbers)); $urlPicture = "identicon.php/?size=24&hash=" . md5($_POST[$nameInputTagName]);
//Did user enter right solution?
if ($solution == $sum) {
$name = $_POST[$nameInputTagName]; $comment = htmlspecialchars($_POST[$messageInputTagName]);
$content = file_get_contents($fileName);
if(strcmp($content, "<p>No comments yet!</p>") == 0 || strcmp($content, "<p>No comments yet!</p>\n") == 0) {
$content = "<p>Identicons created with <a href='https://github.com/timovn/identicon'>identicon.php</a> (licensed under <a href='http://www.gnu.org/licenses/gpl-3.0.en.html'>GPL-3.0</a>).</p>"; } $id = bin2hex(random_bytes(20));
$answerID = $_POST[$answerInputTagName]; //Checks if user used any words from the blacklist if(isForbidden($comment, $blacklist)) { badWords($urlBadWords);
}
//Case the user writes a new comment (not an answer)
if(strlen($answerID) < 40) { file_put_contents($fileName,
//Needed styles
"<style>" .
".commentBox {" .
"display: block;" .
"background: LightGray;" .
"width: 90%;" .
"border-radius: 10px;" .
"padding: 10px;" .
"margin-bottom: 5px;" .
"} " .
"input[name='flag'], input[name='answer'] {" .
"border: none;" .
"padding: 0;" .
"margin: 0;" .
"margin-top: 5px;" .
"padding: 2px;" .
"background: transparent;" .
"}" .
"</style>" .
//get random avatar
"<img class='icon' style='vertical-align:middle;' src='" . $urlPicture . "'/>" . //Displaying user name "<span><b> " . $name . "</b></span> says:<br>" .
//Current UTC-time and -date
"<span style='font-size: small'>" . gmdate("d-m-Y H:i") . " UTC</span><br>" .
//The main comment
"<div class='commentBox'>" .
$comment . "<br>" . "</div>". "<div style='width: 90%; font-size: small; float: left'>" . //Flag-button "<form style='margin: 0; padding: 0; float: left;' method='POST' action='simpleComments.php'>" . "<input style='display: none;' name='comment' type='text' value='" . $id . "'/>" .
"<input style='color: red;' type='submit' name='flag' value='Flag'/>" .
"</form>" .
//Answer-button
"<form id='answer' style='margin-left: 0; padding: 0; float: left;' method='POST' action='simpleComments.php'>" .
"<input style='display: none;' name='comment' type='text' value='" . $id . "'/>" . "<input style='color: green;' type='submit' name='answer' value='Answer'/>" . "</form>" . "<!-- " . $id . " -->" .
"</div>" .
"<br><br>" .
$content); success($url);
}
//Case that user writes an answer
else {
if(strpos($content, $answerID) !== false) {
$explode = explode("<!-- " . $answerID . " -->", $content); file_put_contents($fileName,
$explode[0] . "</div>" . "<br><br>" . //Needed styles "<style>" . ".answerBox {" . "display: block;" . "background: LightGray;" . "width: 90%;" . "border-radius: 10px;" . "padding: 10px;" . "margin-bottom: 5px;" . "} " . "input[name='flag'] {" . "border: none;" . "padding: 0;" . "margin: 0;" . "margin-top: 5px;" . "padding: 2px;" . "background: transparent;" . "}" . "</style>" . "<div style='margin-left: 50px'>" . //get random avatar "<img class='icon' style='vertical-align:middle;' src='" . $urlPicture . "'/>" .
//Displaying user name
"<span><b> " . $name . "</b></span> says:<br>" . //Current UTC-time and -date "<span style='font-size: small'>" . gmdate("d-m-Y H:i") . " UTC</span><br>" . //The main comment "<div class='answerBox'>" . $comment . "<br>" .
"</div>".
//Flag-button
"<div style='width: 90%; font-size: small; float: left'>" .
"<form style='margin: 0; padding: 0; float: left;' method='POST' action='simpleComments.php'>" .
"<input style='display: none;' name='comment' type='text' value='" . $id . "'/>" . "<input style='color: red;' type='submit' name='flag' value='Flag'/>" . "</form><br><br>" . "</div>" . "<!-- " . $answerID . " -->" .
$explode[1]); success($url);
}
}
}
error($urlError); } //============================================================================================ //============================================================================================ // == // FROM HERE ON ADJUSTMENT ARE NECESSARY == // == //============================================================================================ //============================================================================================ /** * start point of the script * * @author Philipp Wilhelm * * @since 1.0 * * */ function start() { //To what email-adress should the flag-notification be send? $to = "[email protected]";
//What's the url you are using this system for? (exact link to e.g. the blog-post)
$url = "https://example.com/post001.html"; //Which page should be loaded when something goes wrong? $urlError = "https://example.com/messageError.html";
//What page should be loaded when user submits words from your "blacklist"?
$urlBadWords = "https://example.com/badWords.html"; //In which file are the comments saved? $fileName = "testComments.php";
//What's the filename of your "blacklist"?
$blackList = "blacklist.txt"; //Replace with the name-attribute of the respective input-field //No action needed here, if you didn't update form.php $nameInputTagName = "myName";
$messageInputTagName = "myMessage"; $exerciseInputTagName = "exerciseText";
$solutionInputTagName = "solution"; $answerInputTagName = "answerID";
$timeInputTagName = "time"; $buttonName = "postComment";
$urlName = "id"; if (isset($_POST["flag"])) {
flag($to, $url);
}
if (isset($_POST["answer"])) { answer($url, $buttonName, $urlName);
}
if (isset($_POST[$buttonName])) {
save($url, $urlError, $urlBadWords, $blackList, $fileName, $nameInputTagName, $messageInputTagName, $exerciseInputTagName, $solutionInputTagName, $answerInputTagName, $timeInputTagName);
}
}
start();
?>
phpcodechecker.com에서 코드를 확인 했지만 문제가 발견되지 않았습니다.
다른 파일은 검토 할 가치가 없으므로 여기에 남겨 두겠습니다.
연결
그럼에도 불구하고 다른 파일과 방법에 관심이있는 사람들은 이 프로젝트 의 저장소 를 참조하십시오 .
또한이 라이브 데모 를 테스트하려는 분들을 위해이.
질문
모든 제안을 환영합니다. 앞서 언급했듯이, 특히 save()함수 에 대한보다 우아한 솔루션에 관심이 있습니다 .
답변
초기 피드백
나는 함수 위에 docblocks의 사용을 좋아합니다. 이 save()함수는 마지막 검사를 제외하고는 들여 쓰기 수준을 제한하기 위해 조기 반환을 잘 활용합니다. $solution일치하지 않으면 즉시 $sum호출 할 수 error()있습니다. 전반적으로 그 기능은 매우 길며 단일 책임 원칙을 위반합니다 . 파일에 쓰는 기능은 각 경우에 대해 별도의 기능으로 이동할 수 있습니다 (댓글 대 답변). 스타일 시트는 CSS 파일로 이동할 수 있습니다.
이 답변 에서 언급했듯이 CSRF 토큰은 이미지 생성, 인코딩 및 디코딩의 필요성을 대체 할 수 있습니다.
제안
전역 변수
다른 사람들이 제안했듯이 전역 변수는 긍정보다 부정적인 측면이 더 많습니다 . 당신은 할 수 그것을 필요로 각 기능에 대한 암호화 암호를 전달하지만, 그 요구를하는 각 함수의 서명을 업데이트 할 필요합니다. 또 다른 옵션은를 사용하여 명명 된 상수를 만드는 것 define()입니다.
define('ENCRYPTION_PASSWORD', 'xyz');
이 작업은 include()(또는 include_once()) 또는 require()(또는 require_once())을 통해 포함 된 별도의 파일에서 수행 할 수 있으며 버전 관리와는 별개 일 수 있습니다 (예 : .env 파일).
상수는 constPHP 5.3.0 1 부터 클래스 외부 에서 키워드를 사용하여 만들 수도 있습니다 .
const ENCRYPTION_PASSWORD = 'xyz';
이미 제안했듯이 네임 스페이스가있는 클래스를 사용하는 것은 좋은 생각입니다. 클래스는 클래스에 네임 스페이스가 지정 되고 PHP 7.1 2부터 특정 가시성 을 갖는 클래스 상수 의 사용을 허용합니다 .
해당 버전이 공식적으로 지원하고 있기 때문에 희망이 코드는 PHP 7.2 이상에서 실행중인 3 .
참조로 반복
이 함수 는 참조로 값을 할당 할 때 isForbidden가리키는 파일의 내용을 반복합니다 $blacklist.
foreach($explode as &$value) {
$value루프 내에서 수정되지 않기 때문에 불필요 해 보입니다 . 배열 요소를 수정해야하는 것이 확실하지 않으면 이러한 방법을 피하는 것이 가장 좋습니다.
엄격한 평등
이미 들어 보셨을 것입니다. 즉 ===, !==가능한 경우 엄격한 비교 연산자를 사용하는 것이 좋습니다save() .
if (count($numbers) != 2) {
count()을 반환 int하고 2은 int너무 !==형식 변환이 필요 없다로 사용할 수 있습니다.
숨겨진 입력
양식에 대해 생성 된 HTML에는 다음이 포함됩니다.
<input style='display: none;'
숨겨진 입력 유형을 사용하여 약간 단순화 할 수 있습니다 .
<input type="hidden"
브라우저 콘솔이나 다른 수단을 통해 페이지를 수정하여 사용자가 입력을 표시 할 수 있지만 숨겨진 입력은 양식 값을 숨기기 위해 생성되었습니다.
이것은 단지 하나의 제안 일뿐 코드에 대한 완전한 검토가 아닙니다.
대부분의 댓글 시스템은 그 자체가 댓글이 아닌 것에 댓글을 달 때 사용됩니다. 데모 페이지 에서처럼. 이는 귀하의 코드 가 다른 사람의 페이지에 포함 된다는 것을 의미합니다 . 이것은 매우 복잡한 페이지 일 수 있습니다. 즉, 코드는 끝없이 다양한 다른 코드와 함께 있어야합니다. 해당 코드에 getTime(), error()또는 save()? 그러면 코드가 해당 페이지를 깨뜨릴 것입니다.
이것이 우리가 다른 개발자와 공유하고 싶은 코드가 거의 항상 객체 지향 프로그래밍 (OOP) 스타일로 작성되는 이유입니다. 객체와 네임 스페이스는 코드를 사용하는 사람들의 코드로부터 코드를 분리하는 데 사용됩니다.
일부 링크 :
https://phpenthusiast.com/object-oriented-php-tutorials
https://www.thoughtfulcode.com/a-complete-guide-to-php-namespaces
https://phptherightway.com
지금처럼 코드를 남겨 두더라도 선택한 이름으로 더 창의적인 사람이되는 것이 좋습니다. 예를 들어, 함수 이름 randExer()은 나에게 아무 의미가 없습니다. 더 나은 이름은 getCaptchaImageHtml(). 이 이름은 실제로 함수의 기능과 반환 내용을 설명합니다. 다른 기능에도 동일하게 적용됩니다. 그것은이다 내 의견 드문 약어는 함수 이름 피해야한다고.
전역 변수를 사용하지 마십시오.
변수가 $encryptionPassword그것을 필요로하는 모든 기능에 인수로 전달되어야한다 ( randExer, getTime, randText및 save).
그렇게하는 데에는 몇 가지 이유가 있습니다.
- 다른 전역 변수와의 충돌을 피할 수 있습니다.
- 변수에 액세스해서는 안되는 사람들이 해당 변수에 실수로 액세스하는 것을 방지합니다.
- 함수의 시그니처는 암호화 암호에 대한 종속성을 숨기므로 독자가 이러한 종속성이 있음을 이해하기 위해 함수 본문을 읽어야하기 때문에 코드를 이해하기가 더 어려워집니다. 함수의 서명 만 읽는 것으로 충분합니다.
- 기능은 테스트하기 더 쉽습니다.
- 그리고 아마 더 ...
전역 변수를 사용하는 함수는 정의상 순수 할 수 없습니다. 위에서 언급 한 이유로 순수 함수가 일반적으로 선호됩니다.
편집 : 전역 변수 문제를 해결하는 가능한 방법은 함수를 클래스 메서드로 승격하고 암호화 암호를 생성자에 전달하는 것입니다.
class ASuitableClassName
{
private string $encryptionPassword;
public function __construct(string $encryptionPassword) { $this->encryptionPassword = $encryptionPassword; } public function getTime() { return openssl_encrypt(time() . bin2hex(random_bytes(20)),"AES-128-ECB", $this->encryptionPassword);
}
// ....
}
$obj = new ASuitableClassName("****************"); $obj->getTime();