Skip to content

Commit 1c076df

Browse files
committed
Implement document creation
1 parent cecc3f6 commit 1c076df

File tree

7 files changed

+332
-1
lines changed

7 files changed

+332
-1
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
namespace BNETDocs\Controllers\Document;
4+
5+
use \BNETDocs\Libraries\CSRF;
6+
use \BNETDocs\Libraries\Common;
7+
use \BNETDocs\Libraries\Controller;
8+
use \BNETDocs\Libraries\DatabaseDriver;
9+
use \BNETDocs\Libraries\Document;
10+
use \BNETDocs\Libraries\Exceptions\UnspecifiedViewException;
11+
use \BNETDocs\Libraries\Logger;
12+
use \BNETDocs\Libraries\Router;
13+
use \BNETDocs\Libraries\User;
14+
use \BNETDocs\Libraries\UserSession;
15+
use \BNETDocs\Models\Document\Create as DocumentCreateModel;
16+
use \BNETDocs\Views\Document\CreateHtml as DocumentCreateHtmlView;
17+
18+
class Create extends Controller {
19+
20+
public function run(Router &$router) {
21+
switch ($router->getRequestPathExtension()) {
22+
case "htm": case "html": case "":
23+
$view = new DocumentCreateHtmlView();
24+
break;
25+
default:
26+
throw new UnspecifiedViewException();
27+
}
28+
$model = new DocumentCreateModel();
29+
$model->csrf_id = mt_rand();
30+
$model->csrf_token = CSRF::generate($model->csrf_id, 900); // 15 mins
31+
$model->error = null;
32+
$model->user_session = UserSession::load($router);
33+
$model->user = (isset($model->user_session) ?
34+
new User($model->user_session->user_id) : null);
35+
36+
$model->acl_allowed = ($model->user &&
37+
$model->user->getOptionsBitmask() & User::OPTION_ACL_DOCUMENT_CREATE
38+
);
39+
40+
if ($router->getRequestMethod() == "POST") {
41+
$this->handlePost($router, $model);
42+
} else if ($router->getRequestMethod() == "GET") {
43+
$model->markdown = true;
44+
}
45+
46+
ob_start();
47+
$view->render($model);
48+
$router->setResponseCode(($model->acl_allowed ? 200 : 403));
49+
$router->setResponseTTL(0);
50+
$router->setResponseHeader("Content-Type", $view->getMimeType());
51+
$router->setResponseContent(ob_get_contents());
52+
ob_end_clean();
53+
}
54+
55+
protected function handlePost(Router &$router, DocumentCreateModel &$model) {
56+
if (!$model->acl_allowed) {
57+
$model->error = "ACL_NOT_SET";
58+
return;
59+
}
60+
if (!isset(Common::$database)) {
61+
Common::$database = DatabaseDriver::getDatabaseObject();
62+
}
63+
$data = $router->getRequestBodyArray();
64+
$csrf_id = (isset($data["csrf_id" ]) ? $data["csrf_id" ] : null);
65+
$csrf_token = (isset($data["csrf_token"]) ? $data["csrf_token"] : null);
66+
$csrf_valid = CSRF::validate($csrf_id, $csrf_token);
67+
$title = (isset($data["title" ]) ? $data["title" ] : null);
68+
$markdown = (isset($data["markdown" ]) ? $data["markdown" ] : null);
69+
$content = (isset($data["content" ]) ? $data["content" ] : null);
70+
$publish = (isset($data["publish" ]) ? $data["publish" ] : null);
71+
$save = (isset($data["save" ]) ? $data["save" ] : null);
72+
73+
$model->title = $title;
74+
$model->markdown = $markdown;
75+
$model->content = $content;
76+
77+
if (!$csrf_valid) {
78+
$model->error = "INVALID_CSRF";
79+
return;
80+
}
81+
CSRF::invalidate($csrf_id);
82+
83+
if (empty($title)) {
84+
$model->error = "EMPTY_TITLE";
85+
} else if (empty($content)) {
86+
$model->error = "EMPTY_CONTENT";
87+
}
88+
89+
$options_bitmask = 0;
90+
if ($markdown) $options_bitmask |= Document::OPTION_MARKDOWN;
91+
if ($publish ) $options_bitmask |= Document::OPTION_PUBLISHED;
92+
93+
$user_id = $model->user_session->user_id;
94+
95+
try {
96+
97+
$success = Document::create(
98+
$user_id, $options_bitmask, $title, $content
99+
);
100+
101+
} catch (QueryException $e) {
102+
103+
// SQL error occurred. We can show a friendly message to the user while
104+
// also notifying this problem to staff.
105+
Logger::logException($e);
106+
107+
}
108+
109+
if (!$success) {
110+
$model->error = "INTERNAL_ERROR";
111+
} else {
112+
$model->error = false;
113+
}
114+
115+
Logger::logEvent(
116+
"document_created",
117+
$user_id,
118+
getenv("REMOTE_ADDR"),
119+
json_encode([
120+
"error" => $model->error,
121+
"options_bitmask" => $options_bitmask,
122+
"title" => $title,
123+
"content" => $content,
124+
])
125+
);
126+
}
127+
128+
}

src/controllers/News/Create.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ protected function handlePost(Router &$router, NewsCreateModel &$model) {
8585
$model->title = $title;
8686
$model->markdown = $markdown;
8787
$model->content = $content;
88-
88+
8989
if (!$csrf_valid) {
9090
$model->error = "INVALID_CSRF";
9191
return;

src/libraries/Document.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,61 @@ public function __construct($data) {
5757
}
5858
}
5959

60+
public static function create(
61+
$user_id, $options_bitmask, $title, $content
62+
) {
63+
if (!isset(Common::$database)) {
64+
Common::$database = DatabaseDriver::getDatabaseObject();
65+
}
66+
$successful = false;
67+
try {
68+
$stmt = Common::$database->prepare("
69+
INSERT INTO `documents` (
70+
`id`, `created_datetime`, `edited_datetime`, `edited_count`,
71+
`user_id`, `options_bitmask`, `title`, `content`
72+
) VALUES (
73+
NULL, NOW(), NULL, 0, :user_id, :options_bitmask, :title, :content
74+
);
75+
");
76+
$stmt->bindParam(":user_id", $user_id, PDO::PARAM_INT);
77+
$stmt->bindParam(":options_bitmask", $options_bitmask, PDO::PARAM_INT);
78+
$stmt->bindParam(":title", $title, PDO::PARAM_STR);
79+
$stmt->bindParam(":content", $content, PDO::PARAM_STR);
80+
$successful = $stmt->execute();
81+
$stmt->closeCursor();
82+
if ($successful) Common::$cache->delete("bnetdocs-documents");
83+
} catch (PDOException $e) {
84+
throw new QueryException("Cannot create document", $e);
85+
} finally {
86+
//Credits::getTopContributorsByDocuments(true); // Refresh statistics
87+
return $successful;
88+
}
89+
}
90+
91+
public static function delete($id) {
92+
if (!isset(Common::$database)) {
93+
Common::$database = DatabaseDriver::getDatabaseObject();
94+
}
95+
$successful = false;
96+
try {
97+
$stmt = Common::$database->prepare("
98+
DELETE FROM `documents` WHERE `id` = :id LIMIT 1;
99+
");
100+
$stmt->bindParam(":id", $id, PDO::PARAM_INT);
101+
$successful = $stmt->execute();
102+
$stmt->closeCursor();
103+
if ($successful) {
104+
Common::$cache->delete("bnetdocs-document-" . (int) $id);
105+
Common::$cache->delete("bnetdocs-documents");
106+
}
107+
} catch (PDOException $e) {
108+
throw new QueryException("Cannot delete document", $e);
109+
} finally {
110+
//Credits::getTopContributorsByNewsPosts(true); // Refresh statistics
111+
return $successful;
112+
}
113+
}
114+
60115
public static function getAllDocuments() {
61116
$cache_key = "bnetdocs-documents";
62117
$cache_val = Common::$cache->get($cache_key);

src/libraries/Router.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use \BNETDocs\Controllers\Attachment\Download as AttachmentDownloadController;
66
use \BNETDocs\Controllers\Comment\Create as CommentCreateController;
77
use \BNETDocs\Controllers\Credits as CreditsController;
8+
use \BNETDocs\Controllers\Document\Create as DocumentCreateController;
89
use \BNETDocs\Controllers\Document\Edit as DocumentEditController;
910
use \BNETDocs\Controllers\Document\Index as DocumentIndexController;
1011
use \BNETDocs\Controllers\Document\Popular as DocumentPopularController;
@@ -284,6 +285,9 @@ public function route(Pair &$redirect = null) {
284285
break;
285286
case "document":
286287
switch ($subpath) {
288+
case "create":
289+
$controller = new DocumentCreateController();
290+
break;
287291
case "edit": case "edit.htm": case "edit.html":
288292
$controller = new DocumentEditController();
289293
break;

src/models/Document/Create.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace BNETDocs\Models\Document;
4+
5+
use \BNETDocs\Libraries\Model;
6+
7+
class Create extends Model {
8+
9+
public $acl_allowed;
10+
public $content;
11+
public $csrf_id;
12+
public $csrf_token;
13+
public $error;
14+
public $markdown;
15+
public $title;
16+
public $user;
17+
public $user_session;
18+
19+
public function __construct() {
20+
parent::__construct();
21+
$this->acl_allowed = null;
22+
$this->content = null;
23+
$this->csrf_id = null;
24+
$this->csrf_token = null;
25+
$this->error = null;
26+
$this->markdown = null;
27+
$this->title = null;
28+
$this->user = null;
29+
$this->user_session = null;
30+
}
31+
32+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
namespace BNETDocs\Templates;
3+
4+
use \BNETDocs\Libraries\Common;
5+
use \BNETDocs\Libraries\Pair;
6+
7+
$title = "Create Document";
8+
$description = "This form allows an individual to create a document.";
9+
10+
$this->opengraph->attach(new Pair("url", "/document/create"));
11+
$this->opengraph->attach(new Pair("type", "article"));
12+
13+
switch ($this->getContext()->error) {
14+
case "ACL_NOT_SET":
15+
$message = "You do not have the privilege to create documents.";
16+
break;
17+
case "INVALID_CSRF":
18+
$message = "The Cross-Site Request Forgery token was invalid. Either the "
19+
. "create document form expired, or this may have been a malicious "
20+
. "attempt to create a document.";
21+
break;
22+
case "EMPTY_TITLE":
23+
$message = "The title of the document is required.";
24+
break;
25+
case "EMPTY_CONTENT":
26+
$message = "The content of the document is required.";
27+
break;
28+
case "INTERNAL_ERROR":
29+
$message = "An internal error occurred while processing your request. "
30+
. "Our staff has been notified of the issue. Try again later.";
31+
break;
32+
default:
33+
$message = $this->getContext()->error;
34+
}
35+
36+
$this->additional_css[] = "/a/forms.css";
37+
require("./header.inc.phtml");
38+
?>
39+
<article>
40+
<?php if ($this->getContext()->error !== false) { ?>
41+
<header>Create Document</header>
42+
<?php if (!empty($message)) { ?>
43+
<section class="red"><p><?php echo $message; ?></p></section>
44+
<?php } ?>
45+
<form method="POST" action="?">
46+
<input type="hidden" name="csrf_id" value="<?php echo
47+
htmlspecialchars($this->getContext()->csrf_id, ENT_HTML5, "UTF-8");
48+
?>"/>
49+
<input type="hidden" name="csrf_token" value="<?php echo
50+
htmlspecialchars($this->getContext()->csrf_token, ENT_HTML5, "UTF-8");
51+
?>"/>
52+
<section>
53+
<label for="title">Title:</label><br/>
54+
<input type="text" name="title" id="title" tabindex="1" required
55+
autofocus="autofocus" value="<?php echo
56+
htmlspecialchars($this->getContext()->title, ENT_HTML5, "UTF-8");
57+
?>"/>
58+
</section>
59+
<section>
60+
<label for="content">Content:</label>
61+
<span style="float:right;">
62+
<label for="markdown" title="Use markdown or use raw HTML">Markdown</label>
63+
<input type="checkbox" name="markdown" id="markdown" tabindex="3"
64+
title="Use markdown or use raw HTML" value="1"<?php
65+
if ($this->getContext()->markdown)
66+
echo " checked=\"checked\"";
67+
?>/>
68+
</span>
69+
<textarea name="content" id="content" tabindex="2" required
70+
style="height:200px;"><?php echo
71+
htmlspecialchars($this->getContext()->content, ENT_HTML5, "UTF-8");
72+
?></textarea>
73+
</section>
74+
<section>
75+
<input type="submit" name="publish" value="Publish" tabindex="4"/>
76+
<input type="submit" name="save" value="Save as Draft" tabindex="5"/>
77+
</section>
78+
</form>
79+
<?php } else { ?>
80+
<header class="green">Create Document</header>
81+
<section class="green">
82+
<p>Your document has been created.</p>
83+
<p>Use the navigation to the left to move to another page.</p>
84+
</section>
85+
<?php } ?>
86+
</article>
87+
<?php require("./footer.inc.phtml"); ?>

src/views/Document/CreateHtml.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace BNETDocs\Views\Document;
4+
5+
use \BNETDocs\Libraries\Common;
6+
use \BNETDocs\Libraries\Exceptions\IncorrectModelException;
7+
use \BNETDocs\Libraries\Model;
8+
use \BNETDocs\Libraries\Template;
9+
use \BNETDocs\Libraries\View;
10+
use \BNETDocs\Models\Document\Create as DocumentCreateModel;
11+
12+
class CreateHtml extends View {
13+
14+
public function getMimeType() {
15+
return "text/html;charset=utf-8";
16+
}
17+
18+
public function render(Model &$model) {
19+
if (!$model instanceof DocumentCreateModel) {
20+
throw new IncorrectModelException();
21+
}
22+
(new Template($model, "Document/Create"))->render();
23+
}
24+
25+
}

0 commit comments

Comments
 (0)