Initial commit.

This is to be the new HashRU website based on the Aaronweb.net/Kabuki CMS.
This commit is contained in:
2016-09-01 23:13:23 +02:00
commit ab0e4efbcb
68 changed files with 8054 additions and 0 deletions

159
controllers/EditAsset.php Normal file
View File

@@ -0,0 +1,159 @@
<?php
/*****************************************************************************
* EditAsset.php
* Contains the asset management controller
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class EditAsset extends HTMLController
{
public function __construct()
{
// Ensure it's just admins at this point.
if (!Registry::get('user')->isAdmin())
throw new NotAllowedException();
if (empty($_GET['id']))
throw new Exception('Invalid request.');
$asset = Asset::fromId($_GET['id']);
if (empty($asset))
throw new NotFoundException('Asset not found');
if (isset($_REQUEST['delete']))
throw new Exception('Not implemented.');
if (!empty($_POST))
{
if (isset($_GET['updatethumb']))
{
$image = $asset->getImage();
return $this->updateThumb($image);
}
// Key info
if (isset($_POST['title'], $_POST['date_captured'], $_POST['priority']))
{
$date_captured = !empty($_POST['date_captured']) ? new DateTime($_POST['date_captured']) : null;
$asset->setKeyData(htmlentities($_POST['title']), $date_captured, intval($_POST['priority']));
}
// Handle tags
$new_tags = [];
if (isset($_POST['tag']) && is_array($_POST['tag']))
foreach ($_POST['tag'] as $id_tag => $bool)
if (is_numeric($id_tag))
$new_tags[] = $id_tag;
$current_tags = array_keys($asset->getTags());
$tags_to_unlink = array_diff($current_tags, $new_tags);
$asset->unlinkTags($tags_to_unlink);
$tags_to_link = array_diff($new_tags, $current_tags);
$asset->linkTags($tags_to_link);
// Meta data
if (isset($_POST['meta_key'], $_POST['meta_value']))
{
$new_meta = array_filter(array_combine($_POST['meta_key'], $_POST['meta_value']), function($e) {
return !empty($e);
});
$asset->setMetaData($new_meta);
}
// A replacement file?
if (isset($_FILES['replacement'], $_POST['replacement_target']) && !empty($_FILES['replacement']['tmp_name']))
{
if ($_POST['replacement_target'] === 'full')
{
$asset->replaceFile($_FILES['replacement']['tmp_name']);
if ($asset->isImage())
{
$image = $asset->getImage();
$image->removeAllThumbnails();
}
}
elseif (preg_match('~^thumb_(\d+)x(\d+)(_c[best]?)?$~', $_POST['replacement_target']))
{
$image = $asset->getImage();
if (($replace_result = $image->replaceThumbnail($_POST['replacement_target'], $_FILES['replacement']['tmp_name'])) !== 0)
throw new Exception('Could not replace thumbnail \'' . $_POST['replacement_target'] . '\' with the uploaded file. Error code: ' . $replace_result);
}
}
header('Location: ' . BASEURL . '/editasset/?id=' . $asset->getId());
}
// Get list of thumbnails
$thumbs = $this->getThumbs($asset);
$page = new EditAssetForm($asset, $thumbs);
parent::__construct('Edit asset \'' . $asset->getTitle() . '\' (' . $asset->getFilename() . ') - ' . SITE_TITLE);
$this->page->adopt($page);
}
private function getThumbs(Asset $asset)
{
$path = $asset->getPath();
$thumbs = [];
$metadata = $asset->getMeta();
foreach ($metadata as $key => $meta)
{
if (!preg_match('~^thumb_(?<width>\d+)x(?<height>\d+)(?<suffix>_c(?<method>[best]?))?$~', $key, $thumb))
continue;
$has_crop_boundary = isset($metadata['crop_' . $thumb['width'] . 'x' . $thumb['height']]);
$has_custom_image = isset($metadata['custom_' . $thumb['width'] . 'x' . $thumb['height']]);
$thumbs[] = [
'dimensions' => [(int) $thumb['width'], (int) $thumb['height']],
'cropped' => !$has_custom_image && (!empty($thumb['suffix']) || $has_crop_boundary),
'crop_method' => !$has_custom_image && !empty($thumb['method']) ? $thumb['method'] : (!empty($thumb['suffix']) ? 'c' : null),
'crop_region' => $has_crop_boundary ? $metadata['crop_' . $thumb['width'] . 'x' . $thumb['height']] : null,
'custom_image' => $has_custom_image,
'filename' => $meta,
'full_path' => THUMBSDIR . '/' . $path . '/' . $meta,
'url' => THUMBSURL . '/' . $path . '/' . $meta,
'status' => file_exists(THUMBSDIR . '/' . $path . '/' . $meta),
];
}
return $thumbs;
}
private function updateThumb(Image $image)
{
$data = json_decode($_POST['data']);
$meta = $image->getMeta();
// Set new crop boundary.
$crop_key = 'crop_' . $data->thumb_width . 'x' . $data->thumb_height;
$crop_value = $data->crop_width . ',' . $data->crop_height . ',' . $data->source_x . ',' . $data->source_y;
$meta[$crop_key] = $crop_value;
// If we uploaded a custom thumbnail, stop considering it such.
$custom_key = 'custom_' . $data->thumb_width . 'x' . $data->thumb_height;
if (isset($meta[$custom_key]))
unset($meta[$custom_key]);
// Force a rebuild of related thumbnails.
$thumb_key = 'thumb_' . $data->thumb_width . 'x' . $data->thumb_height;
foreach ($meta as $meta_key => $meta_value)
if ($meta_key === $thumb_key || strpos($meta_key, $thumb_key . '_') !== false)
unset($meta[$meta_key]);
$image->setMetaData($meta);
$payload = [
'key' => $crop_key,
'value' => $crop_value,
'url' => $image->getThumbnailUrl($data->thumb_width, $data->thumb_height, 'exact'),
];
header('Content-Type: text/json; charset=utf-8');
echo json_encode($payload);
exit;
}
}

195
controllers/EditUser.php Normal file
View File

@@ -0,0 +1,195 @@
<?php
/*****************************************************************************
* EditUser.php
* Contains the edit user controller.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class EditUser extends HTMLController
{
public function __construct()
{
// Ensure it's just admins at this point.
if (!Registry::get('user')->isAdmin())
throw new NotAllowedException();
// Who are we, again?
$current_user = Registry::get('user');
$id_user = isset($_GET['id']) ? (int) $_GET['id'] : 0;
if (empty($id_user) && !isset($_GET['add']))
throw new UnexpectedValueException('Requested user not found or not requesting a new user.');
// Adding a user?
if (isset($_GET['add']))
{
parent::__construct('Add a new user');
$view = new DummyBox('Add a new user');
$this->page->adopt($view);
$this->page->addClass('edituser');
}
// Deleting one?
elseif (isset($_GET['delete']))
{
// Don't be stupid.
if ($current_user->getUserId() == $id_user)
trigger_error('Sorry, I cannot allow you to delete yourself.', E_USER_ERROR);
// So far so good?
$user = Member::fromId($id_user);
if (Session::validateSession('get') && $user->delete())
{
header('Location: ' . BASEURL . '/manageusers/');
exit;
}
else
trigger_error('Cannot delete user: an error occured while processing the request.', E_USER_ERROR);
}
// Editing one, then, surely.
else
{
$user = Member::fromId($id_user);
parent::__construct('Edit user \'' . $user->getFullName() . '\'');
$view = new DummyBox('Edit user \'' . $user->getFullName() . '\'');
$this->page->adopt($view);
$this->page->addClass('edituser');
}
// Session checking!
if (empty($_POST))
Session::resetSessionToken();
else
Session::validateSession();
if ($id_user && !($current_user->isAdmin() && $current_user->getUserId() == $id_user))
$after_form = '<a href="' . BASEURL . '/edituser/?id=' . $id_user . '&delete&' . Session::getSessionTokenKey() . '=' . Session::getSessionToken() . '" class="btn btn-danger" onclick="return confirm(\'Are you sure you want to delete this user? You cannot undo this!\');">Delete user</a>';
elseif (!$id_user)
$after_form = '<button name="submit_and_new" class="btn">Save and add another</button>';
else
$after_form = '';
$form = new Form([
'request_url' => BASEURL . '/edituser/?' . ($id_user ? 'id=' . $id_user : 'add'),
'content_below' => $after_form,
'fields' => [
'first_name' => [
'type' => 'text',
'label' => 'First name',
'size' => 50,
'maxlength' => 255,
],
'surname' => [
'type' => 'text',
'label' => 'Surname',
'size' => 50,
'maxlength' => 255,
],
'slug' => [
'type' => 'text',
'label' => 'URL slug',
'size' => 50,
'maxlength' => 255,
],
'emailaddress' => [
'type' => 'text',
'label' => 'Email address',
'size' => 50,
'maxlength' => 255,
],
'password1' => [
'type' => 'password',
'label' => 'Password',
'size' => 50,
'maxlength' => 255,
'is_optional' => true,
],
'password2' => [
'type' => 'password',
'label' => 'Password (repeat)',
'size' => 50,
'maxlength' => 255,
'is_optional' => true,
],
'is_admin' => [
'header' => 'Privileges',
'type' => 'checkbox',
'label' => 'This user ' . ($id_user ? 'has' : 'should have') . ' administrative privileges.',
'is_optional' => true,
],
],
]);
// Create the form, add in default values.
$form->setData($id_user ? $user->getProps() : $_POST);
$formview = new FormView($form);
$view->adopt($formview);
if (!empty($_POST))
{
$form->verify($_POST);
// Anything missing?
if (!empty($form->getMissing()))
return $formview->adopt(new DummyBox('Some data missing', 'Please fill out the following fields: ' . implode(', ', $form->getMissing())));
$data = $form->getData();
// Just to be on the safe side.
$data['first_name'] = htmlentities(trim($data['first_name']));
$data['surname'] = htmlentities(trim($data['surname']));
$data['emailaddress'] = trim($data['emailaddress']);
// Make sure there's a slug.
if (empty($data['slug']))
$data['slug'] = $data['first_name'];
// Quick stripping.
$data['slug'] = strtr(strtolower($data['slug']), [' ' => '-', '--' => '-', '&' => 'and', '=>' => '', "'" => "", ":"=> "", '/' => '-', '\\' => '-']);
// Checkboxes, fun!
$data['is_admin'] = empty($data['is_admin']) ? 0 : 1;
// If it looks like an e-mail address...
if (!empty($data['emailaddress']) && !preg_match('~^[^ ]+@[^ ]+\.[a-z]+$~', $data['emailaddress']))
return $formview->adopt(new DummyBox('Email addresses invalid', 'The email address you entered is not a valid email address.'));
// Check whether email address is already linked to an account in the database -- just not to the account we happen to be editing, of course.
elseif (!empty($data['emailaddress']) && Member::exists($data['emailaddress']) && !($id_user && $user->getEmailAddress() == $data['emailaddress']))
return $formview->adopt(new DummyBox('Email address already in use', 'Another account is already using the e-mail address you entered.'));
// Setting passwords? We'll need two!
if (!$id_user || !empty($data['password1']) && !empty($data['password2']))
{
if (strlen($data['password1']) < 6 || !preg_match('~[^A-z]~', $data['password1']))
return $formview->adopt(new DummyBox('Password not acceptable', 'Please fill in a password that is at least six characters long and contains at least one non-alphabetic character (e.g. a number or symbol).'));
elseif ($data['password1'] !== $data['password2'])
return $formview->adopt(new DummyBox('Passwords do not match', 'The passwords you entered do not match. Please try again.'));
else
$data['password'] = $data['password1'];
unset($data['password1'], $data['password2']);
}
// Creating a new user?
if (!$id_user)
{
$return = Member::createNew($data);
if ($return === false)
return $formview->adopt(new DummyBox('Cannot create this user', 'Something went wrong while creating the user...'));
if (isset($_POST['submit_and_new']))
{
header('Location: ' . BASEURL . '/edituser/?add');
exit;
}
}
// Just updating?
else
$user->update($data);
// Redirect to the user management page.
header('Location: ' . BASEURL . '/manageusers/');
exit;
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*****************************************************************************
* HTMLController.php
* Contains the key HTML controller
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
/**
* The abstract class that allows easy creation of html pages.
*/
abstract class HTMLController
{
protected $page;
protected $admin_bar;
public function __construct($title)
{
header('Content-Type: text/html; charset=utf-8');
$this->page = new MainTemplate($title);
if (Registry::get('user')->isAdmin())
{
$this->page->appendStylesheet(BASEURL . '/css/admin.css');
$this->admin_bar = new AdminBar();
$this->page->adopt($this->admin_bar);
}
}
public function showContent()
{
$this->page->html_main();
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*****************************************************************************
* JSONController.php
* Contains the key JSON controller
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
/**
* The abstract class that allows easy creation of json replies.
*/
class JSONController
{
protected $payload;
public function showContent()
{
header('Content-Type: text/json; charset=utf-8');
echo json_encode($this->payload);
}
}

56
controllers/Login.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
/*****************************************************************************
* Login.php
* Contains the controller for logging the user in.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class Login extends HTMLController
{
public function __construct()
{
// No need to log in twice, dear heart!
if (Registry::get('user')->isLoggedIn())
{
if (Registry::get('user')->isAdmin())
header('Location: ' . BASEURL . '/admin/');
else
header('Location: ' . BASEURL . '/');
exit;
}
// Sanity check
$login_error = false;
if (isset($_POST['emailaddress'], $_POST['password']))
{
if (Authentication::checkPassword($_POST['emailaddress'], $_POST['password']))
{
parent::__construct('Login');
$_SESSION['user_id'] = Authentication::getUserId($_POST['emailaddress']);
if (isset($_POST['redirect_url']))
header('Location: ' . base64_decode($_POST['redirect_url']));
elseif (isset($_SESSION['login_url']))
header('Location: ' . $_SESSION['redirect_url']);
else
header('Location: ' . BASEURL . '/admin/');
exit;
}
else
$login_error = true;
}
parent::__construct('Log in');
$this->page->appendStylesheet(BASEURL . '/css/admin.css');
$form = new LogInForm('Log in');
if ($login_error)
$form->setErrorMessage('Invalid email address or password.');
// Tried anything? Be helpful, at least.
if (isset($_POST['emailaddress']))
$form->setEmail($_POST['emailaddress']);
$this->page->adopt($form);
}
}

20
controllers/Logout.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
/*****************************************************************************
* Logout.php
* Contains the controller for logging the user out.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class Logout extends HTMLController
{
public function __construct()
{
// Clear the entire sesssion.
$_SESSION = [];
// Back to the frontpage you go.
header('Location: ' . BASEURL);
exit;
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*****************************************************************************
* ManageErrors.php
* Contains the controller for managing errors.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class ManageErrors extends HTMLController
{
public function __construct()
{
// Ensure it's just admins at this point.
if (!Registry::get('user')->isAdmin())
throw new NotAllowedException();
// Flushing, are we?
if (isset($_POST['flush']))
ErrorLog::flush();
$options = [
'title' => 'Error log',
'form' => [
'action' => BASEURL . '/manageerrors/',
'method' => 'post',
'class' => 'floatright',
'buttons' => [
'flush' => [
'type' => 'submit',
'caption' => 'Delete all',
],
],
],
'columns' => [
'id' => [
'value' => 'id_entry',
'header' => '#',
'is_sortable' => true,
],
'message' => [
'parse' => [
'type' => 'function',
'data' => function($row) {
return $row['message'] . '<br><div><a onclick="this.parentNode.childNodes[1].style.display=\'block\';this.style.display=\'none\';">Show debug info</a>' .
'<pre style="display: none">' . $row['debug_info'] . '</pre></div>' .
'<small><a href="' . BASEURL . $row['request_uri'] . '">' . $row['request_uri'] . '</a></small>';
}
],
'header' => 'Message / URL',
'is_sortable' => false,
],
'file' => [
'value' => 'file',
'header' => 'File',
'is_sortable' => true,
],
'line' => [
'value' => 'line',
'header' => 'Line',
'is_sortable' => true,
],
'time' => [
'parse' => [
'type' => 'timestamp',
'data' => [
'timestamp' => 'time',
'pattern' => 'long',
],
],
'header' => 'Time',
'is_sortable' => true,
],
'ip' => [
'value' => 'ip_address',
'header' => 'IP',
'is_sortable' => true,
],
'uid' => [
'header' => 'UID',
'is_sortable' => true,
'parse' => [
'link' => BASEURL . '/member/?id={ID_USER}',
'data' => 'id_user',
],
],
],
'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0,
'sort_order' => !empty($_GET['order']) ? $_GET['order'] : '',
'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : '',
'no_items_label' => "No errors to display -- we're all good!",
'items_per_page' => 20,
'index_class' => 'floatleft',
'base_url' => BASEURL . '/manageerrors/',
'get_count' => 'ErrorLog::getCount',
'get_data' => function($offset = 0, $limit = 20, $order = '', $direction = 'down') {
if (!in_array($order, ['id_entry', 'file', 'line', 'time', 'ipaddress', 'id_user']))
$order = 'id_entry';
$data = Registry::get('db')->queryAssocs('
SELECT *
FROM log_errors
ORDER BY {raw:order}
LIMIT {int:offset}, {int:limit}',
[
'order' => $order . ($direction === 'up' ? ' ASC' : ' DESC'),
'offset' => $offset,
'limit' => $limit,
]);
return [
'rows' => $data,
'order' => $order,
'direction' => $direction,
];
},
];
$error_log = new GenericTable($options);
parent::__construct('Error log - Page ' . $error_log->getCurrentPage() .' - ' . SITE_TITLE);
$this->page->adopt(new TabularData($error_log));
}
}

122
controllers/ManageTags.php Normal file
View File

@@ -0,0 +1,122 @@
<?php
/*****************************************************************************
* ManageTags.php
* Contains the controller with the admin's list of tags.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class ManageTags extends HTMLController
{
public function __construct()
{
// Ensure it's just admins at this point.
if (!Registry::get('user')->isAdmin())
throw new NotAllowedException();
if (isset($_REQUEST['create']) && isset($_POST['tag']))
$this->handleTagCreation();
$options = [
'columns' => [
'id_post' => [
'value' => 'id_tag',
'header' => 'ID',
'is_sortable' => true,
],
'tag' => [
'header' => 'Tag',
'is_sortable' => true,
'parse' => [
'link' => BASEURL . '/managetag/?id={ID_TAG}',
'data' => 'tag',
],
],
'slug' => [
'header' => 'Slug',
'is_sortable' => true,
'parse' => [
'link' => BASEURL . '/managetag/?id={ID_TAG}',
'data' => 'slug',
],
],
'count' => [
'header' => 'Cardinality',
'is_sortable' => true,
'value' => 'count',
],
],
'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0,
'sort_order' => !empty($_GET['order']) ? $_GET['order'] : null,
'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : null,
'title' => 'Manage tags',
'no_items_label' => 'No tags meet the requirements of the current filter.',
'items_per_page' => 25,
'base_url' => BASEURL . '/managetags/',
'get_data' => function($offset = 0, $limit = 15, $order = '', $direction = 'up') {
if (!in_array($order, ['id_post', 'tag', 'slug', 'count']))
$order = 'tag';
if (!in_array($direction, ['up', 'down']))
$direction = 'up';
$data = Registry::get('db')->queryAssocs('
SELECT *
FROM tags
ORDER BY {raw:order}
LIMIT {int:offset}, {int:limit}',
[
'order' => $order . ($direction == 'up' ? ' ASC' : ' DESC'),
'offset' => $offset,
'limit' => $limit,
]);
return [
'rows' => $data,
'order' => $order,
'direction' => ($direction == 'up' ? 'up' : 'down'),
];
},
'get_count' => function() {
return Registry::get('db')->queryValue('
SELECT COUNT(*)
FROM tags');
}
];
$table = new GenericTable($options);
parent::__construct('Tag management - Page ' . $table->getCurrentPage() .' - ' . SITE_TITLE);
$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;
}
}

131
controllers/ManageUsers.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
/*****************************************************************************
* ManageUsers.php
* Contains the controller with the list of users.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class ManageUsers extends HTMLController
{
public function __construct()
{
// Ensure it's just admins at this point.
if (!Registry::get('user')->isAdmin())
throw new NotAllowedException();
$options = [
'form' => [
'action' => BASEURL . '/edituser/',
'method' => 'get',
'class' => 'floatright',
'buttons' => [
'add' => [
'type' => 'submit',
'caption' => 'Add new user',
],
],
],
'columns' => [
'id_user' => [
'value' => 'id_user',
'header' => 'ID',
'is_sortable' => true,
],
'surname' => [
'header' => 'Last name',
'is_sortable' => true,
'parse' => [
'link' => BASEURL . '/edituser/?id={ID_USER}',
'data' => 'surname',
],
],
'first_name' => [
'header' => 'First name',
'is_sortable' => true,
'parse' => [
'link' => BASEURL . '/edituser/?id={ID_USER}',
'data' => 'first_name',
],
],
'slug' => [
'header' => 'Slug',
'is_sortable' => true,
'parse' => [
'link' => BASEURL . '/edituser/?id={ID_USER}',
'data' => 'slug',
],
],
'emailaddress' => [
'value' => 'emailaddress',
'header' => 'Email address',
'is_sortable' => true,
],
'last_action_time' => [
'parse' => [
'type' => 'timestamp',
'data' => [
'timestamp' => 'last_action_time',
'pattern' => 'long',
],
],
'header' => 'Last activity',
'is_sortable' => true,
],
'ip_address' => [
'is_sortable' => true,
'value' => 'ip_address',
'header' => 'IP address',
],
'is_admin' => [
'is_sortable' => true,
'header' => 'Admin?',
'parse' => [
'type' => 'function',
'data' => function($row) {
return $row['is_admin'] ? 'yes' : 'no';
}
],
],
],
'start' => !empty($_GET['start']) ? (int) $_GET['start'] : 0,
'sort_order' => !empty($_GET['order']) ? $_GET['order'] : '',
'sort_direction' => !empty($_GET['dir']) ? $_GET['dir'] : '',
'title' => 'Manage users',
'no_items_label' => 'No users meet the requirements of the current filter.',
'items_per_page' => 15,
'index_class' => 'floatleft',
'base_url' => BASEURL . '/manageusers/',
'get_data' => function($offset = 0, $limit = 15, $order = '', $direction = 'down') {
if (!in_array($order, ['id_user', 'surname', 'first_name', 'slug', 'emailaddress', 'last_action_time', 'ip_address', 'is_admin']))
$order = 'id_user';
$data = Registry::get('db')->queryAssocs('
SELECT *
FROM users
ORDER BY {raw:order}
LIMIT {int:offset}, {int:limit}',
[
'order' => $order . ($direction == 'up' ? ' ASC' : ' DESC'),
'offset' => $offset,
'limit' => $limit,
]);
return [
'rows' => $data,
'order' => $order,
'direction' => $direction,
];
},
'get_count' => function() {
return Registry::get('db')->queryValue('
SELECT COUNT(*)
FROM users');
}
];
$table = new GenericTable($options);
parent::__construct('User management - Page ' . $table->getCurrentPage() .' - ' . SITE_TITLE);
$this->page->adopt(new TabularData($table));
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*****************************************************************************
* ProvideAutoSuggest.php
* Contains the autosuggest provider.
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class ProvideAutoSuggest extends JSONController
{
public function __construct()
{
// Ensure it's just admins at this point.
if (!Registry::get('user')->isAdmin())
throw new NotAllowedException();
if (!isset($_GET['type']))
throw new UnexpectedValueException('Unsupported autosuggest request.');
if ($_GET['type'] === 'tags' && isset($_GET['data']))
{
$data = array_unique(explode(' ', urldecode($_GET['data'])));
$data = array_filter($data, function($item) {
return strlen($item) >= 3;
});
$this->payload = ['items' => []];
// Nothing to look for?
if (count($data) === 0)
return;
$results = Tag::match($data);
foreach ($results as $id_tag => $tag)
$this->payload['items'][] = [
'label' => $tag,
'id_tag' => $id_tag,
];
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*****************************************************************************
* UploadMedia.php
* Contains the media uploading controller
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class UploadMedia extends HTMLController
{
public function __construct()
{
// Ensure it's just admins at this point.
if (!Registry::get('user')->isAdmin())
throw new NotAllowedException();
$page = new MediaUploader();
parent::__construct('Upload new media - ' . SITE_TITLE);
$this->page->adopt($page);
// Are we saving something?
if (isset($_POST['save']))
{
if (empty($_FILES) || empty($_FILES['new_asset']))
return;
// Any tags?
$new_tags = [];
if (isset($_POST['tag']) && is_array($_POST['tag']))
{
foreach ($_POST['tag'] as $id_tag => $bool)
if (is_numeric($id_tag))
$new_tags[] = $id_tag;
}
var_dump($_FILES);
var_dump($_POST);
foreach ($_FILES['new_asset']['tmp_name'] as $num => $uploaded_file)
{
if (empty($uploaded_file))
continue;
$asset = Asset::createNew([
'filename_to_copy' => $uploaded_file,
'preferred_filename' => $_FILES['new_asset']['name'][$num],
'title' => !empty($_POST['title'][$num]) ? $_POST['title'][$num] : null,
]);
$asset->linkTags($new_tags);
}
// Prevent uploading twice.
header('Location: ' . BASEURL . '/uploadmedia/');
exit;
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*****************************************************************************
* ViewPeople.php
* Contains the people index controller
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class ViewPeople extends HTMLController
{
const PER_PAGE = 24;
public function __construct()
{
// Fetch subalbums.
// !!! TODO: pagination.
$subalbums = Tag::getPeople();
// What assets are we using?
$id_assets = array_map(function($album) {
return (int) $album['id_asset_thumb'];
}, $subalbums);
// Fetch assets for thumbnails.
$assets = Asset::fromIds($id_assets, 'object');
// Build album list.
$albums = [];
foreach ($subalbums as $album)
{
$albums[$album['id_tag']] = [
'id_tag' => $album['id_tag'],
'caption' => $album['tag'],
'link' => BASEURL . '/' . $album['slug'] . '/',
'thumbnail' => !empty($album['id_asset_thumb']) ? $assets[$album['id_asset_thumb']]->getImage() : null,
];
}
$index = new AlbumIndex($albums);
parent::__construct('People - ' . SITE_TITLE);
$this->page->adopt($index);
$this->page->setCanonicalUrl(BASEURL . '/people/');
}
}

View File

@@ -0,0 +1,130 @@
<?php
/*****************************************************************************
* ViewPhotoAlbum.php
* Contains the photo album index controller
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class ViewPhotoAlbum extends HTMLController
{
protected $iterator;
protected $total_count;
protected $base_url;
const PER_PAGE = 24;
public function __construct($title = 'Photos - ' . SITE_TITLE)
{
// Viewing an album?
if (isset($_GET['tag']))
{
$tag = Tag::fromSlug($_GET['tag']);
$id_tag = $tag->id_tag;
$title = $tag->tag;
$description = !empty($tag->description) ? '<p>' . $tag->description . '</p>' : '';
// Can we go up a level?
if ($tag->id_parent != 0)
{
$ptag = Tag::fromId($tag->id_parent);
$description .= '<p><a href="' . BASEURL . '/' . (!empty($ptag->slug) ? $ptag->slug . '/' : '') . '">&laquo; Go back to &quot;' . $ptag->tag . '&quot;</a></p>';
}
elseif ($tag->kind === 'Person')
$description .= '<p><a href="' . BASEURL . '/people/">&laquo; Go back to &quot;People&quot;</a></p>';
}
// View the album root.
else
{
$id_tag = 1;
$title = 'Albums';
$description = '';
}
// What page are we at?
$page = isset($_GET['page']) ? (int) $_GET['page'] : 1;
parent::__construct($title . ' - Page ' . $page . ' - ' . SITE_TITLE);
if ($id_tag !== 1)
$this->page->adopt(new DummyBox($title, $description, 'page_title_box'));
// Fetch subalbums, but only if we're on the first page.
if ($page === 1)
{
$albums = $this->getAlbums($id_tag);
$index = new AlbumIndex($albums);
$this->page->adopt($index);
}
// Load a photo mosaic for the current tag.
list($mosaic, $total_count) = $this->getPhotoMosaic($id_tag, $page);
if (isset($mosaic))
$this->page->adopt(new PhotosIndex($mosaic, Registry::get('user')->isAdmin()));
// Make a page index as needed, while we're at it.
if ($total_count > self::PER_PAGE)
{
$index = new PageIndex([
'recordCount' => $total_count,
'items_per_page' => self::PER_PAGE,
'start' => (isset($_GET['page']) ? $_GET['page'] - 1 : 0) * self::PER_PAGE,
'base_url' => BASEURL . '/' . (isset($_GET['tag']) ? $_GET['tag'] . '/' : ''),
'page_slug' => 'page/%PAGE%/',
]);
$this->page->adopt(new Pagination($index));
}
// Set the canonical url.
$this->page->setCanonicalUrl(BASEURL . '/' . (isset($_GET['tag']) ? $_GET['tag'] . '/' : ''));
}
public function getPhotoMosaic($id_tag, $page)
{
// Create an iterator.
list($this->iterator, $total_count) = AssetIterator::getByOptions([
'id_tag' => $id_tag,
'order' => 'date_captured',
'direction' => 'desc',
'limit' => self::PER_PAGE,
'page' => $page,
], true);
$mosaic = $total_count > 0 ? new PhotoMosaic($this->iterator) : null;
return [$mosaic, $total_count];
}
private function getAlbums($id_tag)
{
// Fetch subalbums.
$subalbums = Tag::getAlbums($id_tag);
// What assets are we using?
$id_assets = array_map(function($album) {
return (int) $album['id_asset_thumb'];
}, $subalbums);
// Fetch assets for thumbnails.
$assets = Asset::fromIds($id_assets, 'object');
// Build album list.
$albums = [];
foreach ($subalbums as $album)
{
$albums[$album['id_tag']] = [
'id_tag' => $album['id_tag'],
'caption' => $album['tag'],
'link' => BASEURL . '/' . $album['slug'] . '/',
'thumbnail' => !empty($album['id_asset_thumb']) ? $assets[$album['id_asset_thumb']]->getImage() : null,
];
}
return $albums;
}
public function __destruct()
{
if (isset($this->iterator))
$this->iterator->clean();
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*****************************************************************************
* ViewTimeline.php
* Contains the photo timeline index controller
*
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
*****************************************************************************/
class ViewTimeline extends HTMLController
{
protected $iterator;
protected $total_count;
protected $base_url;
const PER_PAGE = 24;
public function __construct($title = 'Photos - ' . SITE_TITLE)
{
// What page are we at?
$page = isset($_GET['page']) ? (int) $_GET['page'] : 1;
parent::__construct('Timeline - Page ' . $page . ' - ' . SITE_TITLE);
// Load a photo mosaic.
list($this->iterator, $total_count) = AssetIterator::getByOptions([
'order' => 'date_captured',
'direction' => 'desc',
'limit' => self::PER_PAGE,
'page' => $page,
], true);
$mosaic = $total_count > 0 ? new PhotoMosaic($this->iterator) : null;
if (isset($mosaic))
$this->page->adopt(new PhotosIndex($mosaic, Registry::get('user')->isAdmin()));
// Make a page index as needed, while we're at it.
if ($total_count > self::PER_PAGE)
{
$index = new PageIndex([
'recordCount' => $total_count,
'items_per_page' => self::PER_PAGE,
'start' => (isset($_GET['page']) ? $_GET['page'] - 1 : 0) * self::PER_PAGE,
'base_url' => BASEURL . '/timeline/',
'page_slug' => 'page/%PAGE%/',
]);
$this->page->adopt(new Pagination($index));
}
// Set the canonical url.
$this->page->setCanonicalUrl(BASEURL . '/timeline/');
}
public function __destruct()
{
if (isset($this->iterator))
$this->iterator->clean();
}
}