summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Pritz <bluewind@xinu.at>2014-08-29 17:41:33 +0200
committerFlorian Pritz <bluewind@xinu.at>2014-08-29 17:43:19 +0200
commitd7621c2c01074bdcabd6a8c1a8e95c95d00319bf (patch)
treebfcff5599f26939f185bd0208538b56f39a3dc57
parent08c68f84f4a8519912e31f85823694186de804cf (diff)
add multipaste support
Signed-off-by: Florian Pritz <bluewind@xinu.at>
-rw-r--r--application/config/migration.php2
-rw-r--r--application/controllers/file.php506
-rw-r--r--application/libraries/Output_cache.php80
-rw-r--r--application/migrations/013_add_multipaste.php36
-rw-r--r--application/models/mfile.php118
-rw-r--r--application/models/mmultipaste.php160
-rw-r--r--application/views/file/file_info.php2
-rw-r--r--application/views/file/fragments/alert-wide.php3
-rw-r--r--application/views/file/fragments/thumbnail.php9
-rw-r--r--application/views/file/fragments/uploads_table.php26
-rw-r--r--application/views/file/html_footer.php4
-rw-r--r--application/views/file/html_header.php89
-rw-r--r--application/views/file/html_paste_footer.php2
-rw-r--r--application/views/file/html_paste_header.php79
-rw-r--r--application/views/file/multipaste_info.php26
-rw-r--r--application/views/file/upload_form.php1
-rw-r--r--application/views/file/upload_history.php2
-rw-r--r--application/views/file/upload_history_thumbnails.php9
-rw-r--r--application/views/file_plaintext/upload_history.php4
-rw-r--r--application/views/footer.php4
-rw-r--r--data/css/style.css44
-rw-r--r--data/js/script.js34
22 files changed, 939 insertions, 301 deletions
diff --git a/application/config/migration.php b/application/config/migration.php
index d27107cd4..75e9bc19b 100644
--- a/application/config/migration.php
+++ b/application/config/migration.php
@@ -21,7 +21,7 @@ $config['migration_enabled'] = true;
| be upgraded / downgraded to.
|
*/
-$config['migration_version'] = 12;
+$config['migration_version'] = 13;
/*
diff --git a/application/controllers/file.php b/application/controllers/file.php
index 51d6cf156..cbda8bea2 100644
--- a/application/controllers/file.php
+++ b/application/controllers/file.php
@@ -13,6 +13,7 @@ class File extends MY_Controller {
"upload_history",
"do_upload",
"do_delete",
+ "do_multipaste",
);
function __construct()
@@ -20,6 +21,7 @@ class File extends MY_Controller {
parent::__construct();
$this->load->model('mfile');
+ $this->load->model('mmultipaste');
if (is_cli_client()) {
$this->var->view_dir = "file_plaintext";
@@ -34,10 +36,13 @@ class File extends MY_Controller {
$this->load->library("../controllers/tools");
return $this->tools->index();
}
+
// Try to guess what the user would like to do.
$id = $this->uri->segment(1);
if (!empty($_FILES)) {
$this->do_upload();
+ } elseif (strpos($id, "m-") === 0 && $this->mmultipaste->id_exists($id)) {
+ $this->_download();
} elseif ($id != "file" && $this->mfile->id_exists($id)) {
$this->_download();
} elseif ($id && $id != "file") {
@@ -52,124 +57,151 @@ class File extends MY_Controller {
$id = $this->uri->segment(1);
$lexer = urldecode($this->uri->segment(2));
- $filedata = $this->mfile->get_filedata($id);
- $file = $this->mfile->file($filedata['hash']);
+ $is_multipaste = false;
+ if ($this->mmultipaste->id_exists($id)) {
+ $is_multipaste = true;
- if (!$this->mfile->valid_id($id)) {
- $this->_non_existent();
- return;
- }
+ if(!$this->mmultipaste->valid_id($id)) {
+ return $this->_non_existent();
+ }
+ $files = $this->mmultipaste->get_files($id);
+ } elseif ($this->mfile->id_exists($id)) {
+ if (!$this->mfile->valid_id($id)) {
+ return $this->_non_existent();
+ }
- // don't allow unowned files to be downloaded
- if ($filedata["user"] == 0) {
- $this->_non_existent();
- return;
+ $files = array($this->mfile->get_filedata($id));
+ } else {
+ assert(0);
}
- // helps to keep traffic low when reloading
- $etag = $filedata["hash"]."-".$filedata["date"];
+ assert($files !== false);
+ assert(is_array($files));
+ assert(count($files) >= 1);
- // autodetect the lexer for highlighting if the URL contains a / after the ID (/ID/)
- // /ID/lexer disables autodetection
- $autodetect_lexer = !$lexer && substr_count(ltrim($this->uri->uri_string(), "/"), '/') >= 1;
+ // don't allow unowned files to be downloaded
+ foreach ($files as $filedata) {
+ if ($filedata["user"] == 0) {
+ return $this->_non_existent();
+ }
+ }
- if ($autodetect_lexer) {
- $lexer = $this->mfile->autodetect_lexer($filedata["mimetype"], $filedata["filename"]);
+ $etag = "";
+ foreach ($files as $filedata) {
+ $etag = sha1($etag.$filedata["hash"]);
}
- // resolve aliases
- // this is mainly used for compatibility
- $lexer = $this->mfile->resolve_lexer_alias($lexer);
+ // handle some common "lexers" here
+ switch ($lexer) {
+ case "":
+ break;
- // create the qr code for /ID/
- if ($lexer == "qr") {
+ case "qr":
handle_etag($etag);
header("Content-disposition: inline; filename=\"".$id."_qr.png\"\n");
header("Content-Type: image/png\n");
passthru('qrencode -s 10 -o - '.escapeshellarg(site_url($id).'/'));
exit();
+
+ case "info":
+ return $this->_display_info($id);
+
+ default:
+ if ($is_multipaste) {
+ show_error("Invalid action \"".htmlspecialchars($lexer)."\"");
+ }
+ break;
}
$this->load->driver("ddownload");
// user wants the plain file
if ($lexer == 'plain') {
+ assert(count($files) == 1);
handle_etag($etag);
- $this->ddownload->serveFile($file, $filedata["filename"], "text/plain");
- exit();
- }
- if ($lexer == 'info') {
- $this->_display_info($id);
- return;
+ $filedata = $files[0];
+ $filepath = $this->mfile->file($filedata["hash"]);
+ $this->ddownload->serveFile($filepath, $filedata["filename"], "text/plain");
+ exit();
}
- // if there is no mimetype mapping we can't highlight it
- $can_highlight = $this->mfile->can_highlight($filedata["mimetype"]);
+ $this->load->library("output_cache");
- $filesize_too_big = filesize($file) > $this->config->item('upload_max_text_size');
+ foreach ($files as $key => $filedata) {
+ $file = $this->mfile->file($filedata['hash']);
- if (!$can_highlight || $filesize_too_big || !$lexer) {
- // prevent javascript from being executed and forbid frames
- // this should allow us to serve user submitted HTML content without huge security risks
- foreach (array("X-WebKit-CSP", "X-Content-Security-Policy", "Content-Security-Policy") as $header_name) {
- header("$header_name: default-src 'none'; img-src *; media-src *; font-src *; style-src 'unsafe-inline' *; script-src 'none'; object-src *; frame-src 'none'; ");
+ // autodetect the lexer for highlighting if the URL contains a / after the ID (/ID/)
+ // /ID/lexer disables autodetection
+ $autodetect_lexer = !$lexer && substr_count(ltrim($this->uri->uri_string(), "/"), '/') >= 1;
+ $autodetect_lexer = $is_multipaste ? true : $autodetect_lexer;
+ if ($autodetect_lexer) {
+ $lexer = $this->mfile->autodetect_lexer($filedata["mimetype"], $filedata["filename"]);
}
- handle_etag($etag);
- $this->ddownload->serveFile($file, $filedata["filename"], $filedata["mimetype"]);
- exit();
- }
-
- $this->data['title'] = htmlspecialchars($filedata['filename']);
- $this->data['id'] = $id;
- header("Content-Type: text/html\n");
+ // resolve aliases
+ // this is mainly used for compatibility
+ $lexer = $this->mfile->resolve_lexer_alias($lexer);
- $this->data['current_highlight'] = htmlspecialchars($lexer);
- $this->data['timeout'] = $this->mfile->get_timeout_string($id);
- $this->data['lexers'] = $this->mfile->get_lexers();
- $this->data['filedata'] = $filedata;
-
- // highlight the file and cache the result
- $highlit = cache_function($filedata['hash'].'_'.$lexer, 100, function() use ($file, $lexer){
- $CI =& get_instance();
- $ret = array();
- if ($lexer == "rmd") {
- ob_start();
+ // if there is no mimetype mapping we can't highlight it
+ $can_highlight = $this->mfile->can_highlight($filedata["mimetype"]);
- echo '<div class="code content table markdownrender">'."\n";
- echo '<div class="table-row">'."\n";
- echo '<div class="table-cell">'."\n";
- passthru('perl '.FCPATH.'scripts/Markdown.pl '.escapeshellarg($file), $ret["return_value"]);
- echo '</div></div></div>';
+ $filesize_too_big = filesize($file) > $this->config->item('upload_max_text_size');
- $ret["output"] = ob_get_clean();
- } else {
- $ret = $CI->_colorify($file, $lexer);
- }
-
- if ($ret["return_value"] != 0) {
- $tmp = $CI->_colorify($file, "text");
- $ret["output"] = $tmp["output"];
+ if (!$can_highlight || $filesize_too_big || !$lexer) {
+ if (!$is_multipaste) {
+ // prevent javascript from being executed and forbid frames
+ // this should allow us to serve user submitted HTML content without huge security risks
+ foreach (array("X-WebKit-CSP", "X-Content-Security-Policy", "Content-Security-Policy") as $header_name) {
+ header("$header_name: default-src 'none'; img-src *; media-src *; font-src *; style-src 'unsafe-inline' *; script-src 'none'; object-src *; frame-src 'none'; ");
+ }
+ handle_etag($etag);
+ $this->ddownload->serveFile($file, $filedata["filename"], $filedata["mimetype"]);
+ exit();
+ } else {
+ switch ($filedata["mimetype"]) {
+ // TODO: handle video/audio
+ // TODO: handle more image formats (thumbnails needs to be improved)
+ case "image/jpeg":
+ case "image/png":
+ case "image/gif":
+ $filedata["tooltip"] = $this->_tooltip_for_image($filedata);
+ $this->output_cache->add_merge(
+ array("items" => array($filedata)),
+ 'file/fragments/thumbnail'
+ );
+
+ break;
+
+ default:
+ $this->output_cache->add_merge(
+ array("items" => array($filedata)),
+ 'file/fragments/uploads_table'
+ );
+ break;
+ }
+ continue;
+ }
}
- return $ret;
- });
- if ($highlit["return_value"] != 0) {
- $this->data["error_message"] = "<p>Error trying to process the file.
- Either the lexer is unknown or something is broken.
- Falling back to plain text.</p>";
+ $this->output_cache->add_function(function() use ($filedata, $lexer, $is_multipaste) {
+ $this->_highlight_file($filedata, $lexer, $is_multipaste);
+ });
}
- // Don't use append_output because the output class does too
+ // TODO: move lexers json to dedicated URL
+ $this->data['lexers'] = $this->mfile->get_lexers();
+
+ // Output everything
+ // Don't use the output class/append_output because it does too
// much magic ({elapsed_time} and {memory_usage}).
// Direct echo puts us on the safe side.
echo $this->load->view($this->var->view_dir.'/html_header', $this->data, true);
- echo $highlit["output"];
+ $this->output_cache->render();
echo $this->load->view($this->var->view_dir.'/html_footer', $this->data, true);
}
- private function _colorify($file, $lexer)
+ private function _colorify($file, $lexer, $anchor_id = false)
{
$return_value = 0;
$output = "";
@@ -205,10 +237,15 @@ class File extends MY_Controller {
$line = str_replace("<div class=\"highlight\"><pre>", "", $line);
}
+ $anchor = "n$line_number";
+ if ($anchor_id !== false) {
+ $anchor = "n-$anchor_id-$line_number";
+ }
+
// Be careful not to add superflous whitespace here (we are in a <pre>)
$output .= "<div class=\"table-row\">"
- ."<a href=\"#n$line_number\" class=\"linenumber table-cell\">"
- ."<span class=\"anchor\" id=\"n$line_number\"> </span>"
+ ."<a href=\"#$anchor\" class=\"linenumber table-cell\">"
+ ."<span class=\"anchor\" id=\"$anchor\"> </span>"
."</a>"
."<span class=\"line table-cell\">".$line."</span>\n";
$output .= "</div>";
@@ -223,16 +260,111 @@ class File extends MY_Controller {
);
}
- function _display_info($id)
+ private function _highlight_file($filedata, $lexer, $is_multipaste)
{
- $this->data["title"] .= " - Info $id";
- $this->data["filedata"] = $this->mfile->get_filedata($id);
- $this->data["id"] = $id;
- $this->data['timeout'] = $this->mfile->get_timeout_string($id);
+ // highlight the file and cache the result, fall back to plain text if $lexer fails
+ foreach (array($lexer, "text") as $lexer) {
+ $highlit = cache_function($filedata['hash'].'_'.$lexer, 100,
+ function() use ($filedata, $lexer, $is_multipaste) {
+ $file = $this->mfile->file($filedata['hash']);
+ if ($lexer == "rmd") {
+ ob_start();
+
+ echo '<div class="code content table markdownrender">'."\n";
+ echo '<div class="table-row">'."\n";
+ echo '<div class="table-cell">'."\n";
+ passthru('perl '.FCPATH.'scripts/Markdown.pl '.escapeshellarg($file), $return_value);
+ echo '</div></div></div>';
+
+ return array(
+ "output" => ob_get_clean(),
+ "return_value" => $return_value,
+ );
+ } else {
+ return get_instance()->_colorify($file, $lexer, $is_multipaste ? $filedata["id"] : false);
+ }
+ });
- $this->load->view('header', $this->data);
- $this->load->view($this->var->view_dir.'/file_info', $this->data);
- $this->load->view('footer', $this->data);
+ if ($highlit["return_value"] == 0) {
+ break;
+ } else {
+ $message = "Error trying to process the file. Either the lexer is unknown or something is broken.";
+ if ($lexer != "text") {
+ $message .= " Falling back to plain text.";
+ }
+ $this->output_cache->render_now(
+ array("error_message" => "<p>$message</p>"),
+ "file/fragments/alert-wide"
+ );
+ }
+ }
+
+ $data = array_merge($this->data, array(
+ 'title' => htmlspecialchars($filedata['filename']),
+ 'id' => $filedata["id"],
+ 'current_highlight' => htmlspecialchars($lexer),
+ 'timeout' => $this->mfile->get_timeout_string($filedata["id"]),
+ 'filedata' => $filedata,
+ ));
+
+ $this->output_cache->render_now($data, $this->var->view_dir.'/html_paste_header');
+ $this->output_cache->render_now($highlit["output"]);
+ $this->output_cache->render_now($data, $this->var->view_dir.'/html_paste_footer');
+ }
+
+ private function _tooltip_for_image($filedata)
+ {
+ $filesize = format_bytes($filedata["filesize"]);
+ $dimensions = $this->mfile->image_dimension($this->mfile->file($filedata["hash"]));
+ $upload_date = date("r", $filedata["date"]);
+
+ $tooltip = "${filedata["id"]} - $filesize<br>";
+ $tooltip .= "$upload_date<br>";
+ $tooltip .= "$dimensions - ${filedata["mimetype"]}<br>";
+
+ return $tooltip;
+ }
+
+ private function _display_info($id)
+ {
+ if ($this->mmultipaste->id_exists($id)) {
+ $files = $this->mmultipaste->get_files($id);
+
+ $this->data["title"] .= " - Info $id";
+
+ $multipaste = $this->mmultipaste->get_multipaste($id);
+ $total_size = 0;
+ $timeout = -1;
+ foreach($files as $filedata) {
+ $total_size += $filedata["filesize"];
+ $file_timeout = $this->mfile->get_timeout($filedata["id"]);
+ if ($timeout == -1 || ($timeout > $file_timeout && $file_timeout >= 0)) {
+ $timeout = $file_timeout;
+ }
+ }
+
+ $data = array_merge($this->data, array(
+ 'timeout_string' => $timeout >= 0 ? date("r", $timeout) : "Never",
+ 'upload_date' => $multipaste["date"],
+ 'id' => $id,
+ 'size' => $total_size,
+ 'file_count' => count($files),
+ ));
+
+ $this->load->view('header', $this->data);
+ $this->load->view($this->var->view_dir.'/multipaste_info', $data);
+ $this->load->view('footer', $this->data);
+ return;
+ } elseif ($this->mfile->id_exists($id)) {
+ $this->data["title"] .= " - Info $id";
+ $this->data["filedata"] = $this->mfile->get_filedata($id);
+ $this->data["id"] = $id;
+ $this->data['timeout'] = $this->mfile->get_timeout_string($id);
+
+ $this->load->view('header', $this->data);
+ $this->load->view($this->var->view_dir.'/file_info', $this->data);
+ $this->load->view('footer', $this->data);
+ }
}
function _non_existent()
@@ -416,20 +548,10 @@ class File extends MY_Controller {
unset($query[$key]);
continue;
}
-
- $filesize = format_bytes($item["filesize"]);
- $dimensions = $this->mfile->image_dimension($this->mfile->file($item["hash"]));
- $upload_date = date("r", $item["date"]);
-
- $query[$key]["filesize"] = $filesize;
- $query[$key]["tooltip"] = "
- ${item["id"]} - $filesize<br>
- $upload_date
- $dimensions - ${item["mimetype"]}<br>
- ";
+ $query[$key]["tooltip"] = $this->_tooltip_for_image($item);
}
- $this->data["query"] = $query;
+ $this->data["items"] = $query;
$this->load->view('header', $this->data);
$this->load->view($this->var->view_dir.'/upload_history_thumbnails', $this->data);
@@ -462,23 +584,40 @@ class File extends MY_Controller {
$order = is_cli_client() ? "ASC" : "DESC";
- $query = $this->db->query("
+ $items = $this->db->query("
SELECT ".implode(",", array_keys($fields))."
FROM files
WHERE user = ?
- ORDER BY date $order
", array($user))->result_array();
+ $query = $this->db->query("
+ SELECT m.url_id id, sum(f.filesize) filesize, m.date, '' hash, '' mimetype, concat(count(*), ' file(s)') filename
+ FROM multipaste m
+ JOIN multipaste_file_map mfm ON m.multipaste_id = mfm.multipaste_id
+ JOIN files f ON f.id = mfm.file_url_id
+ WHERE m.user_id = ?
+ GROUP BY m.url_id
+ ", array($user))->result_array();
+
+ $items = array_merge($items, $query);
+ uasort($items, function($a, $b) use ($order) {
+ if ($order == "ASC") {
+ return $a["date"] - $b["date"];
+ } else {
+ return $b["date"] - $a["date"];
+ }
+ });
+
if (static_storage("response_type") == "json") {
- return send_json_reply($query);
+ return send_json_reply($items);
}
- foreach($query as $key => $item) {
- $query[$key]["filesize"] = format_bytes($item["filesize"]);
+ foreach($items as $key => $item) {
+ $items[$key]["filesize"] = format_bytes($item["filesize"]);
if (is_cli_client()) {
// Keep track of longest string to pad plaintext output correctly
foreach($fields as $length_key => $value) {
- $len = mb_strlen($query[$key][$length_key]);
+ $len = mb_strlen($items[$key][$length_key]);
if ($len > $lengths[$length_key]) {
$lengths[$length_key] = $len;
}
@@ -496,7 +635,7 @@ class File extends MY_Controller {
) sub
", array($user))->row_array();
- $this->data["query"] = $query;
+ $this->data["items"] = $items;
$this->data["lengths"] = $lengths;
$this->data["fields"] = $fields;
$this->data["total_size"] = format_bytes($total_size["sum"]);
@@ -511,6 +650,7 @@ class File extends MY_Controller {
$this->muser->require_access("apikey");
$ids = $this->input->post("ids");
+ $userid = $this->muser->get_userid();
$errors = array();
$deleted = array();
$deleted_count = 0;
@@ -522,24 +662,38 @@ class File extends MY_Controller {
foreach ($ids as $id) {
$total_count++;
+ $next = false;
+
+ foreach (array($this->mfile, $this->mmultipaste) as $model) {
+ if ($model->id_exists($id)) {
+ if ($model->get_owner($id) !== $userid) {
+ $errors[] = array(
+ "id" => $id,
+ "reason" => "wrong owner",
+ );
+ continue;
+ }
+ if ($model->delete_id($id)) {
+ $deleted[] = $id;
+ $deleted_count++;
+ $next = true;
+ } else {
+ $errors[] = array(
+ "id" => $id,
+ "reason" => "unknown error",
+ );
+ }
+ }
+ }
- if (!$this->mfile->id_exists($id)) {
- $errors[] = array(
- "id" => $id,
- "reason" => "doesn't exist",
- );
+ if ($next) {
continue;
}
- if ($this->mfile->delete_id($id)) {
- $deleted[] = $id;
- $deleted_count++;
- } else {
- $errors[] = array(
- "id" => $id,
- "reason" => "unknown error",
- );
- }
+ $errors[] = array(
+ "id" => $id,
+ "reason" => "doesn't exist",
+ );
}
if (static_storage("response_type") == "json") {
@@ -560,6 +714,62 @@ class File extends MY_Controller {
$this->load->view('footer', $this->data);
}
+ function do_multipaste()
+ {
+ $this->muser->require_access("apikey");
+
+ $ids = $this->input->post("ids");
+ $errors = array();
+
+ if (!$ids || !is_array($ids)) {
+ show_error("No IDs specified");
+ }
+
+ if (count(array_unique($ids)) != count($ids)) {
+ show_error("Duplicate IDs are not supported");
+ }
+
+ foreach ($ids as $id) {
+ if (!$this->mfile->id_exists($id)) {
+ $errors[] = array(
+ "id" => $id,
+ "reason" => "doesn't exist",
+ );
+ }
+
+ $filedata = $this->mfile->get_filedata($id);
+ if ($filedata["user"] != $this->muser->get_userid()) {
+ $errors[] = array(
+ "id" => $id,
+ "reason" => "not owned by you",
+ );
+ }
+ }
+
+ if (!empty($errors)) {
+ $errorstring = "";
+ foreach ($errors as $error) {
+ $errorstring .= $error["id"]." ".$error["reason"]."<br>\n";
+ }
+ show_error($errorstring);
+ }
+
+ $limits = $this->muser->get_upload_id_limits();
+ $url_id = $this->mmultipaste->new_id($limits[0], $limits[1]);
+
+ $multipaste_id = $this->mmultipaste->get_multipaste_id($url_id);
+ assert($multipaste_id !== false);
+
+ foreach ($ids as $id) {
+ $this->db->insert("multipaste_file_map", array(
+ "file_url_id" => $id,
+ "multipaste_id" => $multipaste_id,
+ ));
+ }
+
+ return $this->_show_url(array($url_id), false);
+ }
+
function delete()
{
$this->muser->require_access("apikey");
@@ -570,19 +780,29 @@ class File extends MY_Controller {
$id = $this->uri->segment(3);
$this->data["id"] = $id;
+ $userid = $this->muser->get_userid();
- if ($id && !$this->mfile->id_exists($id)) {
- show_error("Unknown ID '$id'.", 404);
+ foreach (array($this->mfile, $this->mmultipaste) as $model) {
+ if ($model->id_exists($id)) {
+ if ($model->get_owner($id) !== $userid) {
+ echo "You don't own this file\n";
+ return;
+ }
+ if ($model->delete_id($id)) {
+ echo "$id has been deleted.\n";
+ } else {
+ echo "Deletion failed. Unknown error\n";
+ }
+ return;
+ }
}
- if ($this->mfile->delete_id($id)) {
- echo "$id has been deleted.\n";
- } else {
- echo "Deletion failed. Do you really own that file?\n";
- }
+ show_error("Unknown ID '$id'.", 404);
}
// Handle pastes
+ // TODO: merge with do_upload and also merge the forms
+ // TODO: add support for multiple textareas (+ view)
function do_paste()
{
// stateful clients get a cookie to claim the ID later
@@ -628,6 +848,7 @@ class File extends MY_Controller {
$ids = array();
$extension = $this->input->post('extension');
+ $multipaste = $this->input->post('multipaste');
$files = getNormalizedFILES();
@@ -697,6 +918,21 @@ class File extends MY_Controller {
$ids[] = $id;
}
+ if ($multipaste !== false) {
+ $multipaste_url_id = $this->mmultipaste->new_id($limits[0], $limits[1]);
+
+ $multipaste_id = $this->mmultipaste->get_multipaste_id($multipaste_url_id);
+ assert($multipaste_id !== false);
+
+ foreach ($ids as $id) {
+ $this->db->insert("multipaste_file_map", array(
+ "file_url_id" => $id,
+ "multipaste_id" => $multipaste_id,
+ ));
+ }
+ $ids[] = $multipaste_url_id;
+ }
+
$this->_show_url($ids, $extension);
}
@@ -774,16 +1010,16 @@ class File extends MY_Controller {
foreach($query->result_array() as $row) {
$file = $this->mfile->file($row['hash']);
if (!file_exists($file)) {
- $this->db->query('DELETE FROM files WHERE id = ? LIMIT 1', array($row['id']));
+ $this->mfile->delete_id($row["id"]);
continue;
}
if ($row["user"] == 0 || filesize($file) > $small_upload_size) {
if (filemtime($file) < $oldest_time) {
unlink($file);
- $this->db->query('DELETE FROM files WHERE hash = ?', array($row['hash']));
+ $this->mfile->delete_hash($row["hash"]);
} else {
- $this->db->query('DELETE FROM files WHERE id = ? LIMIT 1', array($row['id']));
+ $this->mfile->delete_id($row["id"]);
if ($this->mfile->stale_hash($row["hash"])) {
unlink($file);
}
@@ -838,7 +1074,6 @@ class File extends MY_Controller {
$id = $this->uri->segment(3);
-
$file_data = $this->mfile->get_filedata($id);
if (empty($file_data)) {
@@ -847,14 +1082,7 @@ class File extends MY_Controller {
}
$hash = $file_data["hash"];
-
- $this->db->query("
- DELETE FROM files
- WHERE hash = ?
- ", array($hash));
-
- unlink($this->mfile->file($hash));
-
+ $this->mfile->delete_hash($hash);
echo "removed hash \"$hash\"\n";
}
diff --git a/application/libraries/Output_cache.php b/application/libraries/Output_cache.php
new file mode 100644
index 000000000..224e9f95a
--- /dev/null
+++ b/application/libraries/Output_cache.php
@@ -0,0 +1,80 @@
+<?php
+/*
+ * Copyright 2014 Florian "Bluewind" Pritz <bluewind@server-speed.net>
+ *
+ * Licensed under AGPLv3
+ * (see COPYING for full license text)
+ *
+ */
+
+class Output_cache {
+ private $output_cache = array();
+
+ /**
+ * Combine multiple objects for the same view into one
+ * @param data data to pass to the view
+ * @param view view path
+ */
+ public function add_merge($data, $view)
+ {
+ assert($view !== NULL);
+
+ // combine multiple objects for the same view into one
+ $count = count($this->output_cache);
+ if ($count > 0 && $this->output_cache[$count - 1]["view"] === $view) {
+ $this->output_cache[$count - 1]["data"] = array_merge_recursive($this->output_cache[$count - 1]["data"], $data);
+ } else {
+ $this->add($data, $view);
+ }
+ }
+
+ /**
+ * Add some data that will be output directly if view is NULL or passed
+ * to the view otherweise.
+ *
+ * @param data data to pass to view or output
+ * @param view view path or NULL
+ */
+ public function add($data, $view = NULL)
+ {
+ $this->output_cache[] = array(
+ "view" => $view,
+ "data" => $data,
+ );
+ }
+
+ /**
+ * Add a function that will be excuted when render() is called.
+ * This function is supposed to use render_now() to output data.
+ *
+ * @param data_function
+ */
+ public function add_function($data_function)
+ {
+ $this->output_cache[] = array(
+ "view" => NULL,
+ "data_function" => $data_function,
+ );
+ }
+
+ public function render_now($data, $view = NULL)
+ {
+ if ($view !== NULL) {
+ echo get_instance()->load->view($view, $data, true);
+ } else {
+ echo $data;
+ }
+ }
+
+ public function render()
+ {
+ while ($output = array_shift($this->output_cache)) {
+ if (isset($output["data_function"])) {
+ $output["data_function"]();
+ } else {
+ $data = $output["data"];
+ $this->render_now($data, $output["view"]);
+ }
+ }
+ }
+}
diff --git a/application/migrations/013_add_multipaste.php b/application/migrations/013_add_multipaste.php
new file mode 100644
index 000000000..edb4a0748
--- /dev/null
+++ b/application/migrations/013_add_multipaste.php
@@ -0,0 +1,36 @@
+<?php
+defined('BASEPATH') OR exit('No direct script access allowed');
+
+class Migration_add_multipaste extends CI_Migration {
+
+ public function up()
+ {
+ $this->db->query('
+ CREATE TABLE `multipaste` (
+ `url_id` varchar(255) NOT NULL,
+ `multipaste_id` int(11) NOT NULL AUTO_INCREMENT,
+ `user_id` int(11) NOT NULL,
+ `date` int(11) NOT NULL,
+ PRIMARY KEY (`url_id`),
+ UNIQUE KEY `multipaste_id` (`multipaste_id`),
+ KEY `user_id` (`user_id`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;');
+
+ $this->db->query('
+ CREATE TABLE `multipaste_file_map` (
+ `multipaste_id` int(11) NOT NULL,
+ `file_url_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+ `sort_order` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (`sort_order`),
+ UNIQUE KEY `multipaste_id` (`multipaste_id`,`file_url_id`),
+ KEY `multipaste_file_map_ibfk_2` (`file_url_id`),
+ CONSTRAINT `multipaste_file_map_ibfk_1` FOREIGN KEY (`multipaste_id`) REFERENCES `multipaste` (`multipaste_id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `multipaste_file_map_ibfk_2` FOREIGN KEY (`file_url_id`) REFERENCES `files` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;');
+ }
+
+ public function down()
+ {
+ show_error("downgrade not supported");
+ }
+}
diff --git a/application/models/mfile.php b/application/models/mfile.php
index c900b297c..427c74a18 100644
--- a/application/models/mfile.php
+++ b/application/models/mfile.php
@@ -71,15 +71,14 @@ class Mfile extends CI_Model {
function get_filedata($id)
{
$sql = '
- SELECT hash, filename, mimetype, date, user, filesize
+ SELECT id, hash, filename, mimetype, date, user, filesize
FROM `files`
WHERE `id` = ?
LIMIT 1';
$query = $this->db->query($sql, array($id));
- if ($query->num_rows() == 1) {
- $return = $query->result_array();
- return $return[0];
+ if ($query->num_rows() > 0) {
+ return $query->row_array();
} else {
return false;
}
@@ -214,10 +213,15 @@ class Mfile extends CI_Model {
$mimetype = $this->mimetype($this->file($hash));
$filesize = filesize($this->file($hash));
- $query = $this->db->query('
- INSERT INTO `files` (`hash`, `id`, `filename`, `user`, `date`, `mimetype`, `filesize`)
- VALUES (?, ?, ?, ?, ?, ?, ?)',
- array($hash, $id, $filename, $userid, time(), $mimetype, $filesize));
+ $this->db->insert("files", array(
+ "id" => $id,
+ "hash" => $hash,
+ "filename" => $filename,
+ "date" => time(),
+ "user" => $userid,
+ "mimetype" => $mimetype,
+ "filesize" => $filesize,
+ ));
}
function adopt($id)
@@ -241,9 +245,7 @@ class Mfile extends CI_Model {
$file = $this->file($filedata['hash']);
if (!file_exists($file)) {
- if (isset($filedata["hash"])) {
- $this->db->query('DELETE FROM files WHERE hash = ?', array($filedata['hash']));
- }
+ $this->delete_hash($filedata["hash"]);
return false;
}
@@ -262,10 +264,9 @@ class Mfile extends CI_Model {
// if the file has been uploaded multiple times the mtime is the time
// of the last upload
if (filemtime($file) < $remove_before) {
- unlink($file);
- $this->db->query('DELETE FROM files WHERE hash = ?', array($filedata['hash']));
+ $this->delete_hash($filedata["hash"]);
} else {
- $this->db->query('DELETE FROM files WHERE id = ? LIMIT 1', array($id));
+ $this->delete_id($id);
}
return false;
}
@@ -273,13 +274,28 @@ class Mfile extends CI_Model {
return true;
}
- function get_timeout_string($id)
+ public function get_timeout($id)
{
$filedata = $this->get_filedata($id);
$file = $this->file($filedata["hash"]);
+ if ($this->config->item("upload_max_age") == 0) {
+ return -1;
+ }
+
if (filesize($file) > $this->config->item("small_upload_size")) {
- return date("r", $filedata["date"] + $this->config->item("upload_max_age"));
+ return $filedata["date"] + $this->config->item("upload_max_age");
+ } else {
+ return -1;
+ }
+ }
+
+ public function get_timeout_string($id)
+ {
+ $timeout = $this->get_timeout($id);
+
+ if ($timeout >= 0) {
+ return date("r", $timeout);
} else {
return "unknown";
}
@@ -301,34 +317,72 @@ class Mfile extends CI_Model {
}
}
- function delete_id($id)
+ public function delete_id($id)
{
$filedata = $this->get_filedata($id);
- $userid = $this->muser->get_userid();
- if (!$this->id_exists($id)) {
- return false;
- }
-
- $sql = '
- DELETE
- FROM `files`
- WHERE `id` = ?
- AND user = ?
- LIMIT 1';
- $this->db->query($sql, array($id, $userid));
+ // Delete the file and all multipastes using it
+ // Note that this does not delete all relations in multipaste_file_map
+ // which is actually done by a SQL contraint.
+ // TODO: make it work properly without the constraint
+ $this->db->query('
+ DELETE m, mfm, f
+ FROM files f
+ LEFT JOIN multipaste_file_map mfm ON f.id = mfm.file_url_id
+ LEFT JOIN multipaste m ON mfm.multipaste_id = m.multipaste_id
+ WHERE f.id = ?
+ ', array($id));
if ($this->id_exists($id)) {
return false;
}
- if ($this->unused_file($filedata['hash'])) {
- unlink($this->file($filedata['hash']));
- @rmdir($this->folder($filedata['hash']));
+ if ($filedata !== false) {
+ assert(isset($filedata["hash"]));
+ if ($this->unused_file($filedata['hash'])) {
+ unlink($this->file($filedata['hash']));
+ $dir = $this->folder($filedata['hash']);
+ if (count(scandir($dir)) == 2) {
+ rmdir($dir);
+ }
+ }
}
return true;
}
+ public function delete_hash($hash)
+ {
+ // Delete all files with this hash and all multipastes using any of those files
+ // Note that this does not delete all relations in multipaste_file_map
+ // which is actually done by a SQL contraint.
+ // TODO: make it work properly without the constraint
+ $this->db->query('
+ DELETE m, mfm, f
+ FROM files f
+ LEFT JOIN multipaste_file_map mfm ON f.id = mfm.file_url_id
+ LEFT JOIN multipaste m ON mfm.multipaste_id = m.multipaste_id
+ WHERE f.hash = ?
+ ', array($hash));
+
+ if (file_exists($this->file($hash))) {
+ unlink($this->file($hash));
+ $dir = $this->folder($hash);
+ if (count(scandir($dir)) == 2) {
+ rmdir($dir);
+ }
+ }
+ return true;
+ }
+
+ public function get_owner($id)
+ {
+ return $this->db->query("
+ SELECT user
+ FROM files
+ WHERE id = ?
+ ", array($id))->row_array()["user"];
+ }
+
public function get_lexers() {
return cache_function('lexers', 1800, function() {
$lexers = array();
diff --git a/application/models/mmultipaste.php b/application/models/mmultipaste.php
new file mode 100644
index 000000000..723132a50
--- /dev/null
+++ b/application/models/mmultipaste.php
@@ -0,0 +1,160 @@
+<?php
+/*
+ * Copyright 2014 Florian "Bluewind" Pritz <bluewind@server-speed.net>
+ *
+ * Licensed under AGPLv3
+ * (see COPYING for full license text)
+ *
+ */
+
+class Mmultipaste extends CI_Model {
+
+ function __construct()
+ {
+ parent::__construct();
+ $this->load->model("muser");
+ $this->load->model("mfile");
+ }
+
+ /**
+ * Returns an unused ID
+ *
+ * @param min minimal length of the resulting ID
+ * @param max maximum length of the resulting ID
+ */
+ public function new_id($min = 3, $max = 6)
+ {
+ static $id_blacklist = NULL;
+
+ if ($id_blacklist == NULL) {
+ // This prevents people from being unable to access their uploads
+ // because of URL rewriting
+ $id_blacklist = scandir(FCPATH);
+ $id_blacklist[] = "file";
+ $id_blacklist[] = "user";
+ }
+
+ $max_tries = 100;
+
+ for ($try = 0; $try < $max_tries; $try++) {
+ $id = "m-".random_alphanum($min, $max);
+
+ // TODO: try to insert the id into file_groups instead of checking with
+ // id_exists (prevents race conditio)
+ if ($this->id_exists($id) || in_array($id, $id_blacklist)) {
+ continue;
+ }
+
+ $this->db->insert("multipaste", array(
+ "url_id" => $id,
+ "user_id" => $this->muser->get_userid(),
+ "date" => time(),
+ ));
+
+ return $id;
+ }
+
+ show_error("Failed to find unused ID after $max_tries tries.");
+ }
+
+ public function id_exists($id)
+ {
+ if (!$id) {
+ return false;
+ }
+
+ $sql = '
+ SELECT multipaste.url_id
+ FROM multipaste
+ WHERE multipaste.url_id = ?
+ LIMIT 1';
+ $query = $this->db->query($sql, array($id));
+
+ if ($query->num_rows() == 1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public function valid_id($id)
+ {
+ $files = $this->get_files($id);
+ foreach ($files as $file) {
+ if (!$this->mfile->valid_id($file["id"])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function delete_id($id)
+ {
+ $this->db->query('
+ DELETE m, mfm
+ FROM multipaste m
+ LEFT JOIN multipaste_file_map mfm ON mfm.multipaste_id = m.multipaste_id
+ WHERE m.url_id = ?
+ ', array($id));
+
+ if ($this->id_exists($id)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function get_owner($id)
+ {
+ return $this->db->query("
+ SELECT user_id
+ FROM multipaste
+ WHERE url_id = ?
+ ", array($id))->row_array()["user_id"];
+ }
+
+ public function get_multipaste($id)
+ {
+ return $this->db->query("
+ SELECT url_id, user_id, date
+ FROM multipaste
+ WHERE url_id = ?
+ ", array($id))->row_array();
+ }
+
+ public function get_files($url_id)
+ {
+ $ret = array();
+
+ $query = $this->db->query("
+ SELECT mfm.file_url_id
+ FROM multipaste_file_map mfm
+ JOIN multipaste m ON m.multipaste_id = mfm.multipaste_id
+ WHERE m.url_id = ?
+ ORDER BY mfm.sort_order
+ ", array($url_id))->result_array();
+
+ foreach ($query as $row) {
+ $filedata = $this->mfile->get_filedata($row["file_url_id"]);
+ $ret[] = $filedata;
+ }
+
+ return $ret;
+ }
+
+ public function get_multipaste_id($url_id)
+ {
+ $query = $this->db->query("
+ SELECT multipaste_id
+ FROM multipaste
+ WHERE url_id = ?
+ ", array($url_id));
+
+ if ($query->num_rows() > 0) {
+ return $query->row_array()["multipaste_id"];
+ }
+
+ return false;
+ }
+
+}
diff --git a/application/views/file/file_info.php b/application/views/file/file_info.php
index 6c2772a21..0620ac9bd 100644
--- a/application/views/file/file_info.php
+++ b/application/views/file/file_info.php
@@ -1,4 +1,4 @@
-<div class="center">
+<div class="center simple-container">
<?php if($filedata): ?>
<div class="table-responive">
<table class="table" style="margin: auto">
diff --git a/application/views/file/fragments/alert-wide.php b/application/views/file/fragments/alert-wide.php
new file mode 100644
index 000000000..ae303e119
--- /dev/null
+++ b/application/views/file/fragments/alert-wide.php
@@ -0,0 +1,3 @@
+<div class="alert alert-danger alert-wide">
+ <?php echo $error_message; ?>
+</div>
diff --git a/application/views/file/fragments/thumbnail.php b/application/views/file/fragments/thumbnail.php
new file mode 100644
index 000000000..6bd82fcb9
--- /dev/null
+++ b/application/views/file/fragments/thumbnail.php
@@ -0,0 +1,9 @@
+<!-- Comment markers background: http://stackoverflow.com/a/14776780/953022 -->
+<div class="container container-wide">
+<div class="upload_thumbnails"><!--
+ <?php foreach($items as $key => $item): ?>
+ --><a href="<?php echo site_url("/".$item["id"])."/"; ?>" title="<?php echo htmlentities($item["filename"]); ?>" data-content="<?php echo htmlentities($item["tooltip"]); ?>" data-id="<?php echo $item["id"]; ?>"><img class="thumb" src="<?php echo site_url("file/thumbnail/".$item["id"]); ?>"></a><!--
+ <?php endforeach; ?>
+ -->
+</div>
+</div>
diff --git a/application/views/file/fragments/uploads_table.php b/application/views/file/fragments/uploads_table.php
new file mode 100644
index 000000000..142d19e91
--- /dev/null
+++ b/application/views/file/fragments/uploads_table.php
@@ -0,0 +1,26 @@
+<?php register_js_include("/data/js/jquery.tablesorter.min.js"); ?>
+<div class="table-responsive container-wide">
+ <p>Non-previewable file(s):</p>
+ <table class="table table-striped tablesorter">
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>Filename</th>
+ <th>Mimetype</th>
+ <th>Date</th>
+ <th>Size</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach($items as $item): ?>
+ <tr>
+ <td><a href="<?php echo site_url("/".$item["id"]) ?>/"><?php echo $item["id"] ?></a></td>
+ <td class="wrap"><?php echo htmlspecialchars($item["filename"]); ?></td>
+ <td><?php echo $item["mimetype"] ?></td>
+ <td class="nowrap" data-sort-value="<?=$item["date"]; ?>"><?php echo date("r", $item["date"]); ?></td>
+ <td><?php echo format_bytes($item["filesize"]) ?></td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/file/html_footer.php b/application/views/file/html_footer.php
index bbec7ebd1..bd07b63f9 100644
--- a/application/views/file/html_footer.php
+++ b/application/views/file/html_footer.php
@@ -1,6 +1,4 @@
- </div>
- </div>
-
+<div class="container">
<?php
$force_full_html = true;
include(FCPATH."application/views/footer.php");
diff --git a/application/views/file/html_header.php b/application/views/file/html_header.php
index 2c556720c..fdce101a2 100644
--- a/application/views/file/html_header.php
+++ b/application/views/file/html_header.php
@@ -2,95 +2,14 @@
$force_full_html = true;
include(FCPATH."application/views/header.php"); ?>
-</div>
+</div><!-- .container -->
<script type="text/javascript">
/* <![CDATA[ */
window.lexers = <?php echo json_encode($lexers); ?>;
- window.paste_base = '<?php echo site_url($id) ?>';
/* ]]> */
</script>
-<?php if (isset($error_message)) { ?>
-<div class="alert alert-danger" style="text-align: center; border-radius: 0;">
- <?php echo $error_message; ?>
-</div>
-<?php } ?>
-
-<div class="container paste-container">
- <div style="border:1px solid #ccc;">
- <div class="navbar navbar-default navbar-static-top navbar-paste">
- <ul class="nav navbar-nav navbar-left dont-float">
- <li><a href="#file-info" class="navbar-brand" data-toggle="modal"><?php echo $title ?></a></li>
- <li class="divider"></li>
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="language-toggle">
- Language: <?php echo htmlspecialchars($current_highlight); ?>
- <b class="caret"></b>
- </a>
- <div class="dropdown-menu" style="padding: 15px;">
- <form>
- <input type="text" id="language" placeholder="Language" class="form-control">
- </form>
- </div>
- </li>
- <li class="divider"></li>
- <li>
- <a href="#file-info" role="button" data-toggle="modal">Info</a>
- </li>
- <li class="divider"></li>
- <li><a href="<?php echo site_url('file/index?repaste='.$id); ?>" role="button">Repaste</a></li>
- </ul>
- <div class="btn-group navbar-right" style="margin: 8px;">
- <a id="linewrap" class="btn btn-default" rel="tooltip" title="Toggle wrapping of long lines">Linewrap</a>
- <a href="<?php echo site_url($id."/plain") ?>" class="btn btn-default" rel="tooltip" title="View as plain text">Plain</a>
- <a href="<?php echo site_url($id) ?>" class="btn btn-default" rel="tooltip" title="View as raw file (org. mime type)">Raw</a>
- <?php if ($current_highlight === 'rmd') { ?>
- <a href="<?php echo site_url($id)."/" ?>" class="btn btn-default" rel="tooltip" title="Render as Code">Code</a>
- <?php } else { ?>
- <a href="<?php echo site_url($id."/rmd") ?>" class="btn btn-default" rel="tooltip" title="Render as Markdown">Markdown</a>
- <?php } ?>
- </div>
- </div> <!-- .navbar -->
- <div id="file-info" class="modal fade" role="dialog" aria-labelledby="file-info" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal">&times;</button>
- <h3 class="modal-title">Paste Information</h3>
- </div>
- <div class="modal-body">
- <table class="table">
- <tr>
- <td style="border:0;">Filename:</td>
- <td style="border:0;"><?php echo htmlspecialchars($filedata["filename"]) ?></td>
- </tr>
- <tr>
- <td>Size:</td>
- <td><?php echo format_bytes($filedata["filesize"]) ?></td>
- </tr>
- <tr>
- <td>Mimetype:</td>
- <td><?php echo $filedata["mimetype"] ?></td>
- </tr>
- <tr>
- <td>Uploaded:</td>
- <td><?php echo date("r", $filedata["date"]) ?></td>
- </tr>
- <tr>
- <td>Removal:</td>
- <td><?php echo $timeout ?></td>
- </tr>
- </table>
- </div>
- <div class="modal-footer">
- <?php echo form_open("file/do_delete/", array("style" => "display: inline")); ?>
- <input type="hidden" name="ids[<?php echo $id; ?>]" value="<?php echo $id; ?>">
- <button class="btn btn-danger pull-left" aria-hidden="true">Delete</button>
- </form>
- <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
- </div>
- </div>
- </div>
- </div> <!-- .modal -->
- <div>
+<?php if (isset($error_message)) {
+ include 'framgents/alert-wide.php';
+} ?>
diff --git a/application/views/file/html_paste_footer.php b/application/views/file/html_paste_footer.php
new file mode 100644
index 000000000..22bc4dabb
--- /dev/null
+++ b/application/views/file/html_paste_footer.php
@@ -0,0 +1,2 @@
+</div><!-- .container .paste-container -->
+
diff --git a/application/views/file/html_paste_header.php b/application/views/file/html_paste_header.php
new file mode 100644
index 000000000..f4d3021ec
--- /dev/null
+++ b/application/views/file/html_paste_header.php
@@ -0,0 +1,79 @@
+<div class="container paste-container container-wide">
+ <div style="border:1px solid #ccc;">
+ <div class="navbar navbar-default navbar-static-top navbar-paste">
+ <ul class="nav navbar-nav navbar-left dont-float">
+ <li><a href="<?=site_url($id)."/"; ?>" class="navbar-brand" data-toggle="modal"><?php echo $title ?></a></li>
+ <li class="divider"></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="language-toggle-<?=$id; ?>">
+ Language: <?php echo htmlspecialchars($current_highlight); ?>
+ <b class="caret"></b>
+ </a>
+ <div class="dropdown-menu" style="padding: 15px;">
+ <form>
+ <input data-base-url="<?=site_url($id); ?>" type="text" id="language-<?=$id; ?>" placeholder="Language" class="form-control">
+ </form>
+ </div>
+ </li>
+ <li class="divider"></li>
+ <li>
+ <a href="#file-info-<?=$id; ?>" role="button" data-toggle="modal">Info</a>
+ </li>
+ <?php if (isset($user_logged_in) && $user_logged_in) { ?>
+ <li class="divider"></li>
+ <li><a href="<?php echo site_url('file/index?repaste='.$id); ?>" role="button">Repaste</a></li>
+ <?php } ?>
+ </ul>
+ <div class="btn-group navbar-right" style="margin: 8px;">
+ <a id="linewrap-<?=$id; ?>" class="btn btn-default" rel="tooltip" title="Toggle wrapping of long lines">Linewrap</a>
+ <a href="<?php echo site_url($id."/plain") ?>" class="btn btn-default" rel="tooltip" title="View as plain text">Plain</a>
+ <a href="<?php echo site_url($id) ?>" class="btn btn-default" rel="tooltip" title="View as raw file (org. mime type)">Raw</a>
+ <?php if ($current_highlight === 'rmd') { ?>
+ <a href="<?php echo site_url($id)."/" ?>" class="btn btn-default" rel="tooltip" title="Render as Code">Code</a>
+ <?php } else { ?>
+ <a href="<?php echo site_url($id."/rmd") ?>" class="btn btn-default" rel="tooltip" title="Render as Markdown">Markdown</a>
+ <?php } ?>
+ </div>
+ </div> <!-- .navbar -->
+ <div id="file-info-<?=$id; ?>" class="modal fade" role="dialog" aria-labelledby="file-info-<?=$id; ?>" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h3 class="modal-title">Paste Information</h3>
+ </div>
+ <div class="modal-body">
+ <table class="table">
+ <tr>
+ <td style="border:0;">Filename:</td>
+ <td style="border:0;"><?php echo htmlspecialchars($filedata["filename"]) ?></td>
+ </tr>
+ <tr>
+ <td>Size:</td>
+ <td><?php echo format_bytes($filedata["filesize"]) ?></td>
+ </tr>
+ <tr>
+ <td>Mimetype:</td>
+ <td><?php echo $filedata["mimetype"] ?></td>
+ </tr>
+ <tr>
+ <td>Uploaded:</td>
+ <td><?php echo date("r", $filedata["date"]) ?></td>
+ </tr>
+ <tr>
+ <td>Removal:</td>
+ <td><?php echo $timeout ?></td>
+ </tr>
+ </table>
+ </div>
+ <div class="modal-footer">
+ <?php echo form_open("file/do_delete/", array("style" => "display: inline")); ?>
+ <input type="hidden" name="ids[<?php echo $id; ?>]" value="<?php echo $id; ?>">
+ <button class="btn btn-danger pull-left" aria-hidden="true">Delete</button>
+ </form>
+ <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
+ </div>
+ </div>
+ </div>
+ </div> <!-- .modal -->
+ </div>
diff --git a/application/views/file/multipaste_info.php b/application/views/file/multipaste_info.php
new file mode 100644
index 000000000..5baf732a2
--- /dev/null
+++ b/application/views/file/multipaste_info.php
@@ -0,0 +1,26 @@
+<div class="center simple-container">
+ <div class="table-responive">
+ <table class="table" style="margin: auto">
+ <tr>
+ <td class="title">ID</td>
+ <td class="text"><a href="<?=site_url($id); ?>/"><?=$id; ?></a></td>
+ </tr>
+ <tr>
+ <td class="title">Number of files</td>
+ <td class="text"><?=$file_count; ?></td>
+ </tr>
+ <tr>
+ <td class="title">Date of upload</td>
+ <td class="text"><?=date("r", $upload_date); ?></td>
+ </tr>
+ <tr>
+ <td class="title">Date of removal</td>
+ <td class="text"><?=$timeout_string; ?></td>
+ </tr>
+ <tr>
+ <td class="title">Total size (including duplicates)</td>
+ <td class="text"><?=format_bytes($size); ?></td>
+ </tr>
+ </table>
+ </div>
+</div>
diff --git a/application/views/file/upload_form.php b/application/views/file/upload_form.php
index 612a1bf82..44828c53a 100644
--- a/application/views/file/upload_form.php
+++ b/application/views/file/upload_form.php
@@ -29,6 +29,7 @@
<div>
<input class="file-upload" type="file" name="file[]" multiple="multiple"><br>
</div>
+ <label><input type="checkbox" name="multipaste" value="1"> Create multipaste</label><br>
<button type="submit" id="upload_button" class="btn btn-primary">Upload it!</button>
</div>
</div>
diff --git a/application/views/file/upload_history.php b/application/views/file/upload_history.php
index 5015e9bf6..10afc53e9 100644
--- a/application/views/file/upload_history.php
+++ b/application/views/file/upload_history.php
@@ -15,7 +15,7 @@
</tr>
</thead>
<tbody>
- <?php foreach($query as $key => $item): ?>
+ <?php foreach($items as $key => $item): ?>
<tr>
<td><input type="checkbox" name="ids[<?php echo $item["id"] ?>]" value="<?php echo $item["id"] ?>" class="delete-history"></td>
<td><a href="<?php echo site_url("/".$item["id"]) ?>/"><?php echo $item["id"] ?></a></td>
diff --git a/application/views/file/upload_history_thumbnails.php b/application/views/file/upload_history_thumbnails.php
index bcafc44ca..a061d9676 100644
--- a/application/views/file/upload_history_thumbnails.php
+++ b/application/views/file/upload_history_thumbnails.php
@@ -6,14 +6,7 @@
</div>
<?php include 'nav_history.php'; ?>
-
-<!-- Comment markers background: http://stackoverflow.com/a/14776780/953022 -->
-<div class="upload_history_thumbnails"><!--
- <?php foreach($query as $key => $item): ?>
- --><a href="<?php echo site_url("/".$item["id"]); ?>" title="<?php echo htmlentities($item["filename"]); ?>" data-content="<?php echo htmlentities($item["tooltip"]); ?>" data-id="<?php echo $item["id"]; ?>"><img class="thumb" src="<?php echo site_url("file/thumbnail/".$item["id"]); ?>"></a><!--
- <?php endforeach; ?>
- -->
-</div>
+<?php include 'fragments/thumbnail.php'; ?>
<div class="row-fluid">
<div class="span12 alert alert-block alert-info">
diff --git a/application/views/file_plaintext/upload_history.php b/application/views/file_plaintext/upload_history.php
index f9a14af0b..53801494f 100644
--- a/application/views/file_plaintext/upload_history.php
+++ b/application/views/file_plaintext/upload_history.php
@@ -9,13 +9,13 @@ echo
.mb_str_pad($fields["hash"], $lengths["hash"])." | "
.mb_str_pad($fields["filesize"], $lengths["filesize"])."\n";
-foreach($query as $key => $item) {
+foreach($items as $key => $item) {
echo
mb_str_pad($item["id"], $lengths["id"])." | "
.mb_str_pad($item["filename"], $lengths["filename"])." | "
.mb_str_pad($item["mimetype"], $lengths["mimetype"])." | "
.date($dateformat, $item["date"])." | "
- .$item["hash"]." | "
+ .mb_str_pad($item["hash"], $lengths["hash"])." | "
.$item["filesize"]."\n";
}
?>
diff --git a/application/views/footer.php b/application/views/footer.php
index e748cd4e3..ae8d2e575 100644
--- a/application/views/footer.php
+++ b/application/views/footer.php
@@ -3,9 +3,9 @@ if (is_cli_client() && !isset($force_full_html)) {
return;
}
?>
- </div>
+ </div><!-- .container -->
<div id="push"></div>
-</div>
+</div> <!-- #wrap -->
<footer class="footer" id="footer">
<div class="container muted credits">
<p>Site code licensed under <a href="http://www.gnu.org/licenses/agpl-3.0.html" target="_blank">AGPL v3</a>.</p>
diff --git a/data/css/style.css b/data/css/style.css
index 5f1286e82..cd8bfa4d2 100644
--- a/data/css/style.css
+++ b/data/css/style.css
@@ -62,6 +62,10 @@
width: 200px;
}
+.file-upload {
+ width: 100%;
+}
+
.navbar-nav > li > .dropdown-menu {
margin-top: 2px;
}
@@ -119,6 +123,11 @@ textarea.text-upload {
word-wrap: normal;
}
+.alert-wide {
+ text-align: center;
+ border-radius: 0;
+}
+
code, pre, textarea {
font-family: "Dejavu sans mono", Monaco, monospace;
}
@@ -184,13 +193,23 @@ body {
padding-left: 0;
}
-.paste-container {
- padding-top: 40px;
- background: #eee;
- padding: 3px;
+.container-wide {
+ padding: 0;
max-width: 100%;
margin-left: 20px;
margin-right: 20px;
+ margin-bottom: 20px;
+}
+
+.simple-container {
+ margin-left: 20px;
+ margin-right: 20px;
+ margin-bottom: 20px;
+}
+
+.paste-container {
+ padding: 3px;
+ background: #eee;
}
.code pre {
@@ -265,9 +284,6 @@ body {
.highlight_line {
background: #ffffcc;
}
-#file-info {
- display: none;
-}
.ui-autocomplete {
z-index: 1500;
@@ -276,29 +292,29 @@ body {
.popover {
word-break: break-word;
word-wrap: normal;
+ max-width: 400px;
}
-.upload_history_thumbnails {
+.upload_thumbnails {
margin: 0 auto;
- padding-bottom: 50px;
}
-.upload_history_thumbnails img.thumb,
-.upload_history_thumbnails a {
+.upload_thumbnails img.thumb,
+.upload_thumbnails a {
width: 150px;
height: 150px;
}
-.upload_history_thumbnails a {
+.upload_thumbnails a {
margin: 1px;
display: inline-block;
}
-.upload_history_thumbnails .marked {
+.upload_thumbnails .marked {
background: red;
}
-.upload_history_thumbnails .marked img {
+.upload_thumbnails .marked img {
opacity: 0.4;
}
diff --git a/data/js/script.js b/data/js/script.js
index 2c4969685..77336a88e 100644
--- a/data/js/script.js
+++ b/data/js/script.js
@@ -10,7 +10,7 @@ function fixedEncodeURIComponent (str) {
$('.highlight_line').removeClass("highlight_line");
- if (hash.match(/^#n\d+$/) === null) {
+ if (hash.match(/^#n(?:-.+-)?\d+$/) === null) {
return;
}
@@ -25,22 +25,24 @@ function fixedEncodeURIComponent (str) {
lexer_source.push({ label: window.lexers[key], value: key });
}
- $('#language').autocomplete({
+ $('[id^=language-]').autocomplete({
source: lexer_source,
select: function(event, ui) {
- window.location = window.paste_base + '/' + fixedEncodeURIComponent(ui.item.value);
+ event.preventDefault();
+ window.location = $(event.target).data("base-url") + '/' + fixedEncodeURIComponent(ui.item.value);
}
});
- $(document).on("keyup", "#language", function(event) {
+ $(document).on("keyup", "[id^=language-]", function(event) {
if (event.keyCode == 13) {
- window.location = window.paste_base + '/' + fixedEncodeURIComponent($(this).val());
+ event.preventDefault();
+ window.location = $(event.target).data("base-url") + '/' + fixedEncodeURIComponent($(this).val());
}
});
- $('#language-toggle').click(function() {
+ $('[id^=language-toggle-]').click(function(event) {
setTimeout(function() {
- $('#language').focus();
+ $(event.target).parent().find('[id^=language-]').focus();
}, 0);
});
@@ -54,7 +56,7 @@ function fixedEncodeURIComponent (str) {
});
window.lines_wrapped = true;
- $('#linewrap').click(function() {
+ $('[id^=linewrap-]').click(function() {
if (window.lines_wrapped == true) {
$(".highlight > pre").css("white-space", "pre");
} else {
@@ -63,7 +65,7 @@ function fixedEncodeURIComponent (str) {
window.lines_wrapped = !window.lines_wrapped;
});
- $('.upload_history_thumbnails a').popover({
+ $('.upload_thumbnails a').popover({
trigger: "hover",
placement: "bottom",
html: true,
@@ -75,7 +77,7 @@ function fixedEncodeURIComponent (str) {
window.page_mode = "normal";
$('#delete_button').hide();
$("#delete_form input[id^='delete_']").remove();
- $(".upload_history_thumbnails .marked").removeClass("marked");
+ $(".upload_thumbnails .marked").removeClass("marked");
break;
default:
window.page_mode = "delete";
@@ -84,7 +86,7 @@ function fixedEncodeURIComponent (str) {
}
});
- $('.upload_history_thumbnails a').on("click", function(event) {
+ $('.upload_thumbnails a').on("click", function(event) {
if (window.page_mode == "delete") {
event.preventDefault();
var data_id = $(event.target).parent().attr("data-id");
@@ -105,8 +107,14 @@ function fixedEncodeURIComponent (str) {
});
function handle_resize() {
- var div = $('.upload_history_thumbnails');
- div.width(div.parent().width() - (div.parent().width() % div.find('a').outerWidth(true)));
+ $('.upload_thumbnails').each(function() {
+ var div = $(this);
+
+ need_multiple_lines = div.parent().width() < (div.find('a').outerWidth(true) * div.find('a').size());
+
+ div.css('margin-left', need_multiple_lines ? "auto" : "0");
+ div.width(div.parent().width() - (div.parent().width() % div.find('a').outerWidth(true)));
+ });
}
$(window).resize(function() {