15 Commits

17 changed files with 143 additions and 94 deletions

11
TODO.md
View File

@@ -1,4 +1,11 @@
TODO: TODO:
* Taggen door gebruikers * Gebruikers *persoons*tags laten verwijderen
* Album management
* Bij taggen van user: thumbnail setten
* Grid herberekenen; captions weglaten.
* Sortering: alleen *albums* ASC, personen DESC!
* Album/tag management

View File

@@ -23,7 +23,7 @@ $user->updateAccessTime();
Registry::set('user', $user); Registry::set('user', $user);
// Handle errors our own way. // Handle errors our own way.
//set_error_handler('ErrorHandler::handleError'); set_error_handler('ErrorHandler::handleError');
ini_set("display_errors", DEBUG ? "On" : "Off"); ini_set("display_errors", DEBUG ? "On" : "Off");
// The real magic starts here! // The real magic starts here!

View File

@@ -30,8 +30,8 @@ class Login extends HTMLController
header('Location: ' . base64_decode($_POST['redirect_url'])); header('Location: ' . base64_decode($_POST['redirect_url']));
elseif (isset($_SESSION['login_url'])) elseif (isset($_SESSION['login_url']))
{ {
unset($_SESSION['redirect_url']); header('Location: ' . $_SESSION['login_url']);
header('Location: ' . $_SESSION['redirect_url']); unset($_SESSION['login_url']);
} }
else else
header('Location: ' . BASEURL . '/'); header('Location: ' . BASEURL . '/');

View File

@@ -14,9 +14,6 @@ class ManageTags extends HTMLController
if (!Registry::get('user')->isAdmin()) if (!Registry::get('user')->isAdmin())
throw new NotAllowedException(); throw new NotAllowedException();
if (isset($_REQUEST['create']) && isset($_POST['tag']))
$this->handleTagCreation();
$options = [ $options = [
'columns' => [ 'columns' => [
'id_post' => [ 'id_post' => [
@@ -92,36 +89,4 @@ class ManageTags extends HTMLController
parent::__construct('Tag management - Page ' . $table->getCurrentPage() .' - ' . SITE_TITLE); parent::__construct('Tag management - Page ' . $table->getCurrentPage() .' - ' . SITE_TITLE);
$this->page->adopt(new TabularData($table)); $this->page->adopt(new TabularData($table));
} }
private function handleTagCreation()
{
header('Content-Type: text/json; charset=utf-8');
// It better not already exist!
if (Tag::exactMatch($_POST['tag']))
{
echo '{"error":"Tag already exists!"}';
exit;
}
$label = htmlentities(trim($_POST['tag']));
$slug = strtr(strtolower($label), [' ' => '-']);
$tag = Tag::createNew([
'tag' => $label,
'slug' => $slug,
]);
// Did we succeed?
if (!$tag)
{
echo '{"error":"Could not create tag."}';
exit;
}
echo json_encode([
'label' => $tag->tag,
'id_tag' => $tag->id_tag,
]);
exit;
}
} }

View File

@@ -14,14 +14,22 @@ class ProvideAutoSuggest extends JSONController
if (!Registry::get('user')->isLoggedIn()) if (!Registry::get('user')->isLoggedIn())
throw new NotAllowedException(); throw new NotAllowedException();
if (!isset($_GET['type'])) if (!isset($_REQUEST['type']))
throw new UnexpectedValueException('Unsupported autosuggest request.'); throw new UnexpectedValueException('Unsupported autosuggest request.');
if ($_GET['type'] === 'tags' && isset($_GET['data'])) if ($_REQUEST['type'] === 'tags' && isset($_REQUEST['data']))
return $this->handleTagSearch();
if ($_REQUEST['type'] === 'createtag' && isset($_REQUEST['tag']))
return $this->handleTagCreation();
}
private function handleTagSearch()
{ {
$data = array_unique(explode(' ', urldecode($_GET['data']))); {
$data = array_unique(explode(' ', urldecode($_REQUEST['data'])));
$data = array_filter($data, function($item) { $data = array_filter($data, function($item) {
return strlen($item) >= 3; return strlen($item) >= 2;
}); });
$this->payload = ['items' => []]; $this->payload = ['items' => []];
@@ -30,7 +38,7 @@ class ProvideAutoSuggest extends JSONController
if (count($data) === 0) if (count($data) === 0)
return; return;
$results = Tag::match($data); $results = Tag::matchPeople($data);
foreach ($results as $id_tag => $tag) foreach ($results as $id_tag => $tag)
$this->payload['items'][] = [ $this->payload['items'][] = [
'label' => $tag, 'label' => $tag,
@@ -38,4 +46,35 @@ class ProvideAutoSuggest extends JSONController
]; ];
} }
} }
private function handleTagCreation()
{
// It better not already exist!
if (Tag::exactMatch($_REQUEST['tag']))
{
$this->payload = ['error' => true, 'msg' => "Tag already exists!"];
return;
}
$label = htmlentities(trim($_REQUEST['tag']));
$slug = strtr($label, [' ' => '-']);
$tag = Tag::createNew([
'tag' => $label,
'kind' => 'Person',
'slug' => $slug,
]);
// Did we succeed?
if (!$tag)
{
$this->payload = ['error' => true, 'msg' => "Could not create tag."];
return;
}
$this->payload = [
'success' => true,
'label' => $tag->tag,
'id_tag' => $tag->id_tag,
];
}
} }

View File

@@ -18,6 +18,9 @@ class ViewPhoto extends HTMLController
if (empty($photo)) if (empty($photo))
throw new NotFoundException(); throw new NotFoundException();
if (!empty($_POST))
$this->handleTagging($photo->getImage());
parent::__construct($photo->getTitle() . ' - ' . SITE_TITLE); parent::__construct($photo->getTitle() . ' - ' . SITE_TITLE);
$page = new PhotoPage($photo->getImage()); $page = new PhotoPage($photo->getImage());
@@ -47,4 +50,21 @@ class ViewPhoto extends HTMLController
if (Registry::get('user')->isAdmin()) if (Registry::get('user')->isAdmin())
$this->admin_bar->appendItem(BASEURL . '/editasset/?id=' . $photo->getId(), 'Edit this photo'); $this->admin_bar->appendItem(BASEURL . '/editasset/?id=' . $photo->getId(), 'Edit this photo');
} }
private function handleTagging(Image $photo)
{
header('Content-Type: text/json; charset=utf-8');
// Are we tagging a photo?
if (!isset($_POST['id_tag']))
{
echo json_encode(['error' => true, 'msg' => 'Invalid tag request.']);
exit;
}
// We are!
$photo->linkTags([(int) $_POST['id_tag']]);
echo json_encode(['success' => true]);
exit;
}
} }

View File

@@ -39,6 +39,7 @@ class ViewPhotoAlbum extends HTMLController
{ {
$back_link = BASEURL . '/people/'; $back_link = BASEURL . '/people/';
$back_link_title = 'Back to "People"'; $back_link_title = 'Back to "People"';
$is_person = true;
} }
$header_box = new AlbumHeaderBox($title, $description, $back_link, $back_link_title); $header_box = new AlbumHeaderBox($title, $description, $back_link, $back_link_title);
@@ -84,7 +85,7 @@ class ViewPhotoAlbum extends HTMLController
} }
// Load a photo mosaic for the current tag. // Load a photo mosaic for the current tag.
list($mosaic, $total_count) = $this->getPhotoMosaic($id_tag, $page); list($mosaic, $total_count) = $this->getPhotoMosaic($id_tag, $page, !isset($is_person));
if (isset($mosaic)) if (isset($mosaic))
{ {
$index = new PhotosIndex($mosaic, Registry::get('user')->isAdmin()); $index = new PhotosIndex($mosaic, Registry::get('user')->isAdmin());
@@ -111,13 +112,13 @@ class ViewPhotoAlbum extends HTMLController
($page > 1 ? 'page/' . $page . '/' : '')); ($page > 1 ? 'page/' . $page . '/' : ''));
} }
public function getPhotoMosaic($id_tag, $page) public function getPhotoMosaic($id_tag, $page, $sort_linear)
{ {
// Create an iterator. // Create an iterator.
list($this->iterator, $total_count) = AssetIterator::getByOptions([ list($this->iterator, $total_count) = AssetIterator::getByOptions([
'id_tag' => $id_tag, 'id_tag' => $id_tag,
'order' => 'date_captured', 'order' => 'date_captured',
'direction' => $id_tag > 0 ? 'asc' : 'desc', 'direction' => $sort_linear ? 'asc' : 'desc',
'limit' => self::PER_PAGE, 'limit' => self::PER_PAGE,
'page' => $page, 'page' => $page,
], true); ], true);

View File

@@ -14,7 +14,7 @@ class Email
$boundary = uniqid('sr'); $boundary = uniqid('sr');
if (empty($headers)) if (empty($headers))
$headers .= "From: HashRU Pics <no-reply@pics.hashru.nl>\r\n"; $headers .= "From: HashRU Pics <no-reply@aaronweb.net>\r\n";
// Set up headers. // Set up headers.
$headers .= "MIME-Version: 1.0\r\n"; $headers .= "MIME-Version: 1.0\r\n";
@@ -44,7 +44,7 @@ class Email
if (DEBUG) if (DEBUG)
return file_put_contents(BASEDIR . '/mail_dumps.txt', "To: \"$addressee\" <$address>\r\n$headers\r\nSubject: $subject\r\n" . self::wrapLines($message), FILE_APPEND); return file_put_contents(BASEDIR . '/mail_dumps.txt', "To: \"$addressee\" <$address>\r\n$headers\r\nSubject: $subject\r\n" . self::wrapLines($message), FILE_APPEND);
else else
return mail("\"$addressee\" <$address>", $subject, $message, $headers, '-fbounces@pics.hashru.nl'); return mail("\"$addressee\" <$address>", $subject, $message, $headers);
} }
public static function wrapLines($body, $maxlength = 80, $break = "\r\n") public static function wrapLines($body, $maxlength = 80, $break = "\r\n")

View File

@@ -81,7 +81,7 @@ class Member extends User
'emailaddress' => 'string-255', 'emailaddress' => 'string-255',
'password_hash' => 'string-255', 'password_hash' => 'string-255',
'creation_time' => 'int', 'creation_time' => 'int',
'ip_address' => 'string-15', 'ip_address' => 'string-45',
'is_admin' => 'int', 'is_admin' => 'int',
], $new_user, ['id_user']); ], $new_user, ['id_user']);

View File

@@ -226,6 +226,9 @@ class Tag
if (!isset($data['id_parent'])) if (!isset($data['id_parent']))
$data['id_parent'] = 0; $data['id_parent'] = 0;
if (!isset($data['description']))
$data['description'] = '';
if (!isset($data['count'])) if (!isset($data['count']))
$data['count'] = 0; $data['count'] = 0;
@@ -297,6 +300,23 @@ class Tag
['tokens' => '%' . strtolower(implode('%', $tokens)) . '%']); ['tokens' => '%' . strtolower(implode('%', $tokens)) . '%']);
} }
public static function matchPeople($tokens)
{
if (!is_array($tokens))
$tokens = explode(' ', $tokens);
return Registry::get('db')->queryPair('
SELECT id_tag, tag
FROM tags
WHERE LOWER(tag) LIKE {string:tokens} AND
kind = {string:person}
ORDER BY tag ASC',
[
'tokens' => '%' . strtolower(implode('%', $tokens)) . '%',
'person' => 'Person',
]);
}
public static function exactMatch($tag) public static function exactMatch($tag)
{ {
if (!is_string($tag)) if (!is_string($tag))

View File

@@ -588,6 +588,20 @@ a#previous_photo:hover, a#next_photo:hover {
margin: 25px 3.5% 25px 0; margin: 25px 3.5% 25px 0;
width: 68.5%; width: 68.5%;
} }
#sub_photo #tag_list {
list-style: none;
margin: 1em 0;
padding: 0;
}
#sub_photo #tag_list li {
display: inline;
}
#sub_photo #tag_list li:after {
content: ', ';
}
#sub_photo #tag_list li:last-child:after {
content: '';
}
#photo_exif_box { #photo_exif_box {
background: #fff; background: #fff;

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -69,7 +69,7 @@ AutoSuggest.prototype.onType = function(input, event) {
} }
var tokens = input.value.split(/\s+/).filter(function(token) { var tokens = input.value.split(/\s+/).filter(function(token) {
return token.length >= 3; return token.length >= 2;
}); });
if (tokens.length === 0) { if (tokens.length === 0) {
@@ -173,6 +173,6 @@ TagAutoSuggest.prototype.fillContainer = function(response) {
}; };
TagAutoSuggest.prototype.createNewTag = function(callback) { TagAutoSuggest.prototype.createNewTag = function(callback) {
var request_uri = this.baseurl + '/managetags/?create'; var request_uri = this.baseurl + '/suggest/?type=createtag';
var request = new HttpRequest('post', request_uri, 'tag=' + encodeURIComponent(this.input.value), callback, this); var request = new HttpRequest('post', request_uri, 'tag=' + encodeURIComponent(this.input.value), callback, this);
} }

View File

@@ -4,14 +4,14 @@ function enableKeyDownNavigation() {
var target = document.getElementById("previous_photo").href; var target = document.getElementById("previous_photo").href;
if (target) { if (target) {
event.preventDefault(); event.preventDefault();
document.location.href = target; document.location.href = target + '#photo_frame';
} }
} }
else if (event.keyCode == 39) { else if (event.keyCode == 39) {
var target = document.getElementById("next_photo").href; var target = document.getElementById("next_photo").href;
if (target) { if (target) {
event.preventDefault(); event.preventDefault();
document.location.href = target; document.location.href = target + '#photo_frame';
} }
} }
}, false); }, false);
@@ -53,13 +53,13 @@ function enableTouchNavigation() {
var target = document.getElementById("previous_photo").href; var target = document.getElementById("previous_photo").href;
if (target) { if (target) {
event.preventDefault(); event.preventDefault();
document.location.href = target; document.location.href = target + '#photo_frame';
} }
} else { } else {
var target = document.getElementById("next_photo").href; var target = document.getElementById("next_photo").href;
if (target) { if (target) {
event.preventDefault(); event.preventDefault();
document.location.href = target; document.location.href = target + '#photo_frame';
} }
} }
} }

View File

@@ -17,6 +17,7 @@ UploadQueue.prototype.addEvents = function() {
that.hideSpinner(); that.hideSpinner();
that.submit.disabled = false; that.submit.disabled = false;
}; };
that.addPreviewBoxForQueueSlot(i);
that.addPreviewForFile(that.queue.files[i], i, callback); that.addPreviewForFile(that.queue.files[i], i, callback);
}; };
}); });
@@ -33,6 +34,12 @@ UploadQueue.prototype.clearPreviews = function() {
this.current_upload_index = -1; this.current_upload_index = -1;
} }
UploadQueue.prototype.addPreviewBoxForQueueSlot = function(index) {
var preview_box = document.createElement('div');
preview_box.id = 'upload_preview_' + index;
this.preview_area.appendChild(preview_box);
};
UploadQueue.prototype.addPreviewForFile = function(file, index, callback) { UploadQueue.prototype.addPreviewForFile = function(file, index, callback) {
if (!file) { if (!file) {
return false; return false;
@@ -42,30 +49,19 @@ UploadQueue.prototype.addPreviewForFile = function(file, index, callback) {
preview.title = file.name; preview.title = file.name;
preview.style.maxHeight = '150px'; preview.style.maxHeight = '150px';
var preview_box = document.createElement('div'); var preview_box = document.getElementById('upload_preview_' + index);
preview_box.id = 'upload_preview_' + index;
preview_box.appendChild(preview); preview_box.appendChild(preview);
var reader = new FileReader(); var reader = new FileReader();
var that = this; var that = this;
var appendMe = function() { reader.addEventListener('load', function() {
preview.src = reader.result; preview.src = reader.result;
if (callback) { if (callback) {
preview.addEventListener('load', function() { preview.addEventListener('load', function() {
callback(); callback();
}); });
} }
that.preview_area.appendChild(preview_box); }, false);
};
var waitForMe = function() {
var previews = that.preview_area.childNodes;
if (previews.length === 0 || previews[previews.length - 1].id === 'upload_preview_' + (index - 1)) {
appendMe();
} else {
setTimeout(waitForMe, 10);
}
};
reader.addEventListener('load', waitForMe, false);
reader.readAsDataURL(file); reader.readAsDataURL(file);
}; };

0
server Normal file → Executable file
View File

View File

@@ -134,7 +134,7 @@ class PhotoPage extends SubTemplate
{ {
echo ' echo '
<h3>Tags</h3> <h3>Tags</h3>
<ul>'; <ul id="tag_list">';
foreach ($this->photo->getTags() as $tag) foreach ($this->photo->getTags() as $tag)
{ {
@@ -153,9 +153,7 @@ class PhotoPage extends SubTemplate
echo ' echo '
<div> <div>
<h3>Link tags</h3> <h3>Link tags</h3>
<ul id="tag_list"> <p style="position: relative"><input type="text" id="new_tag" placeholder="Type to link a new tag"></p>
<li id="new_tag_container"><input type="text" id="new_tag" placeholder="Type to link a new tag"></li>
</ul>
</div> </div>
<script type="text/javascript" src="', BASEURL, '/js/ajax.js"></script> <script type="text/javascript" src="', BASEURL, '/js/ajax.js"></script>
<script type="text/javascript" src="', BASEURL, '/js/autosuggest.js"></script> <script type="text/javascript" src="', BASEURL, '/js/autosuggest.js"></script>
@@ -166,26 +164,15 @@ class PhotoPage extends SubTemplate
listElement: "tag_list", listElement: "tag_list",
baseUrl: "', BASEURL, '", baseUrl: "', BASEURL, '",
appendCallback: function(item) { appendCallback: function(item) {
if (document.getElementById("linked_tag_" + item.id_tag)) { var request = new HttpRequest("post", "', $this->photo->getPageUrl(), '",
return; "id_tag=" + item.id_tag, function(response) {
}
var newCheck = document.createElement("input");
newCheck.type = "checkbox";
newCheck.name = "tag[" + item.id_tag + "]";
newCheck.id = "linked_tag_" + item.id_tag;
newCheck.title = "Uncheck to delete";
newCheck.checked = "checked";
var newNode = document.createElement("li"); var newNode = document.createElement("li");
newNode.appendChild(newCheck);
var newLabel = document.createTextNode(item.label); var newLabel = document.createTextNode(item.label);
newNode.appendChild(newLabel); newNode.appendChild(newLabel);
var list = document.getElementById("tag_list"); var list = document.getElementById("tag_list");
var input = document.getElementById("new_tag_container"); list.appendChild(newNode);
list.insertBefore(newNode, input); }, this);
} }
}); });
}, 100); }, 100);