578 lines
22 KiB
PHP
578 lines
22 KiB
PHP
<?php
|
|
|
|
/* --------------------------------------------------------------------
|
|
|
|
This file is part of Chevereto Free.
|
|
https://chevereto.com/free
|
|
|
|
(c) Rodolfo Berrios <rodolfo@chevereto.com>
|
|
|
|
For the full copyright and license information, please view the LICENSE
|
|
file that was distributed with this source code.
|
|
|
|
--------------------------------------------------------------------- */
|
|
|
|
namespace CHV;
|
|
|
|
use G;
|
|
use Exception;
|
|
use SEOURLify;
|
|
|
|
class Album
|
|
{
|
|
public static function getSingle($id, $sumview = false, $pretty = true, $requester = null)
|
|
{
|
|
$tables = DB::getTables();
|
|
$query = 'SELECT * FROM ' . $tables['albums'] . "\n";
|
|
$joins = [
|
|
'LEFT JOIN ' . $tables['users'] . ' ON ' . $tables['albums'] . '.album_user_id = ' . $tables['users'] . '.user_id'
|
|
];
|
|
|
|
if ($requester) {
|
|
if (!is_array($requester)) {
|
|
$requester = User::getSingle($requester, 'id');
|
|
}
|
|
|
|
}
|
|
|
|
$query .= implode("\n", $joins) . "\n";
|
|
$query .= 'WHERE album_id=:album_id;' . "\n";
|
|
|
|
if ($sumview) {
|
|
$query .= 'UPDATE ' . $tables['albums'] . ' SET album_views = album_views + 1 WHERE album_id=:album_id';
|
|
}
|
|
|
|
try {
|
|
$db = DB::getInstance();
|
|
$db->query($query);
|
|
$db->bind(':album_id', $id);
|
|
$album_db = $db->fetchSingle();
|
|
if (!$album_db) {
|
|
return $album_db;
|
|
}
|
|
|
|
if ($sumview) {
|
|
$album_db['album_views'] += 1;
|
|
// Track stats
|
|
Stat::track([
|
|
'action' => 'update',
|
|
'table' => 'albums',
|
|
'value' => '+1',
|
|
'user_id' => $album_db['album_user_id'],
|
|
]);
|
|
}
|
|
|
|
if ($requester) {
|
|
$album_db['album_liked'] = (bool) $album_db['like_user_id'];
|
|
}
|
|
$return = $album_db;
|
|
$return = $pretty ? self::formatArray($return) : $return;
|
|
return $return;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function getMultiple($ids, $pretty = false)
|
|
{
|
|
if (!is_array($ids)) {
|
|
throw new AlbumException('Expecting $ids array in ' . __METHOD__, 100);
|
|
}
|
|
if (count($ids) == 0) {
|
|
throw new AlbumException('Null $ids provided in ' . __METHOD__, 100);
|
|
}
|
|
|
|
$tables = DB::getTables();
|
|
$query = 'SELECT * FROM ' . $tables['albums'] . "\n";
|
|
$joins = array(
|
|
'LEFT JOIN ' . $tables['users'] . ' ON ' . $tables['albums'] . '.album_user_id = ' . $tables['users'] . '.user_id'
|
|
);
|
|
|
|
$query .= implode("\n", $joins) . "\n";
|
|
$query .= 'WHERE album_id IN (' . join(',', $ids) . ')' . "\n";
|
|
|
|
try {
|
|
$db = DB::getInstance();
|
|
$db->query($query);
|
|
$db_rows = $db->fetchAll();
|
|
if ($pretty) {
|
|
$return = [];
|
|
foreach ($db_rows as $k => $v) {
|
|
$return[$k] = self::formatArray($v);
|
|
}
|
|
return $return;
|
|
}
|
|
return $db_rows;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function sumView($id, $album = [])
|
|
{
|
|
try {
|
|
if (!G\is_integer($id)) {
|
|
throw new Exception('Invalid $id in ' . __METHOD__);
|
|
}
|
|
if ($album['id'] !== $id) {
|
|
$album = self::getSingle($id, false);
|
|
if (!$album) {
|
|
throw new Exception(sprintf('Invalid album %s in ' . __METHOD__, $id));
|
|
}
|
|
}
|
|
$increment = '+1';
|
|
DB::increment('albums', ['views' => $increment], ['id' => $id]);
|
|
Stat::track([
|
|
'action' => 'update',
|
|
'table' => 'albums',
|
|
'value' => $increment,
|
|
'user_id' => $album['album_user_id'],
|
|
]);
|
|
$_SESSION['album_view_stock'][] = $id;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function getUrl($album_id, $title = null)
|
|
{
|
|
if ($title == null) {
|
|
$seo = null;
|
|
} else {
|
|
$seo = SEOURLify::filter($title);
|
|
}
|
|
$url = $seo ? ($seo . '.' . $album_id) : $album_id;
|
|
return G\get_base_url(getSetting('route_album') . '/' . $url);
|
|
}
|
|
|
|
// $name, $user_id = null, $privacy = 'public', $description = '', $password = null
|
|
public static function insert($values)
|
|
{
|
|
if (!$values['user_id']) {
|
|
$values['user_id'] = null;
|
|
}
|
|
if (!$values['description']) {
|
|
$values['description'] = '';
|
|
}
|
|
if (!$values['password']) {
|
|
$values['password'] = null;
|
|
}
|
|
if ($values['privacy'] == 'password' && !G\check_value($values['password'])) {
|
|
throw new AlbumException('Missing album $password', 101);
|
|
}
|
|
// Handle flood
|
|
$flood = self::handleFlood();
|
|
if ($flood) {
|
|
throw new AlbumException(
|
|
_s(
|
|
'Flooding detected. You can only upload %limit% %content% per %time%',
|
|
[
|
|
'%content%' => _n('album', 'albums', $flood['limit']),
|
|
'%limit%' => $flood['limit'],
|
|
'%time%' => $flood['by']
|
|
]
|
|
),
|
|
130
|
|
);
|
|
}
|
|
if (!$values['name']) {
|
|
$values['name'] = _s('Untitled') . ' ' . G\datetime();
|
|
}
|
|
$privacyOpts = ['public', 'password', 'private_but_link'];
|
|
if (Login::isLoggedUser()) {
|
|
$privacyOpts[] = 'private';
|
|
}
|
|
if (in_array($values['privacy'], $privacyOpts) == false) {
|
|
$values['privacy'] = 'public';
|
|
}
|
|
G\nullify_string($values['description']);
|
|
if (empty($values['creation_ip'])) {
|
|
$values['creation_ip'] = G\get_client_ip();
|
|
}
|
|
$album_array = [
|
|
'name' => $values['name'],
|
|
'user_id' => $values['user_id'],
|
|
'date' => G\datetime(),
|
|
'date_gmt' => G\datetimegmt(),
|
|
'privacy' => $values['privacy'],
|
|
'password' => $values['privacy'] == 'password' ? $values['password'] : null,
|
|
'description' => $values['description'],
|
|
'creation_ip' => $values['creation_ip']
|
|
];
|
|
try {
|
|
$insert = DB::insert('albums', $album_array);
|
|
// +1 on user
|
|
if (Login::isLoggedUser()) {
|
|
DB::increment('users', ['album_count' => '+1'], ['id' => $values['user_id']]);
|
|
} else {
|
|
// Save this upload into "session" record
|
|
if (!isset($_SESSION['guest_albums'])) {
|
|
$_SESSION['guest_albums'] = [];
|
|
}
|
|
$_SESSION['guest_albums'][] = $insert;
|
|
}
|
|
// Track stats
|
|
Stat::track([
|
|
'action' => 'insert',
|
|
'table' => 'albums',
|
|
'value' => '+1',
|
|
'date_gmt' => $album_array['date_gmt']
|
|
]);
|
|
return $insert;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
// Move contents $from albums to another album
|
|
public static function moveContents($from, $to)
|
|
{
|
|
if (!$from) { // Could be int or array (multiple)
|
|
throw new AlbumException('Expecting first parameter, ' . gettype($from) . ' given in ' . __METHOD__, 100);
|
|
}
|
|
|
|
if (!$to) {
|
|
$to = null;
|
|
}
|
|
|
|
$ids = is_array($from) ? $from : array($from);
|
|
|
|
try {
|
|
$db = DB::getInstance();
|
|
$db->query('UPDATE ' . DB::getTable('images') . ' SET image_album_id=:image_album_id WHERE image_album_id IN (' . implode(',', $ids) . ')');
|
|
$db->bind(':image_album_id', $to);
|
|
$images = $db->exec();
|
|
if ($images) {
|
|
$images_affected = $db->rowCount();
|
|
// Update the old and new albums to +ids
|
|
$db->query(
|
|
'UPDATE ' . DB::getTable('albums') . ' SET album_image_count = 0 WHERE album_id IN (' . implode(',', $ids) . ');' .
|
|
'UPDATE ' . DB::getTable('albums') . ' SET album_image_count = album_image_count + ' . $images_affected . ' WHERE album_id=:album_id;'
|
|
);
|
|
$db->bind(':album_id', $to);
|
|
$db->exec();
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function addImage($album_id, $id)
|
|
{
|
|
return self::addImages($album_id, array($id));
|
|
}
|
|
|
|
public static function addImages($album_id, $ids)
|
|
{
|
|
|
|
// $album_id can be null.. Remember the user stream
|
|
|
|
if (!is_array($ids) or count($ids) == 0) {
|
|
throw new AlbumException('Expecting array values, ' . gettype($values) . ' given in ' . __METHOD__, 100);
|
|
}
|
|
|
|
try {
|
|
|
|
// Get the images
|
|
$images = Image::getMultiple($ids, true);
|
|
|
|
// Get the albums
|
|
$albums = [];
|
|
|
|
foreach ($images as $k => $v) {
|
|
if ($v['album']['id'] and $v['album']['id'] !== $album_id) {
|
|
$album_k = $v['album']['id'];
|
|
if (!array_key_exists($album_k, $albums)) {
|
|
$albums[$album_k] = [];
|
|
}
|
|
$albums[$album_k][] = $v['id'];
|
|
}
|
|
}
|
|
|
|
$db = DB::getInstance();
|
|
$db->query('UPDATE `' . DB::getTable('images') . '` SET `image_album_id`=:image_album_id WHERE `image_id` IN (' . implode(',', $ids) . ')');
|
|
$db->bind(':image_album_id', $album_id);
|
|
$exec = $db->exec();
|
|
if ($exec and $db->rowCount() > 0) {
|
|
// Update the new album
|
|
if (!is_null($album_id)) {
|
|
self::updateImageCount($album_id, $db->rowCount());
|
|
}
|
|
// Update the old albums
|
|
if (count($albums) > 0) {
|
|
$album_query = '';
|
|
$album_query_tpl = 'UPDATE `' . DB::getTable('albums') . '` SET `album_image_count` = GREATEST(`album_image_count` - :counter, 0) WHERE `album_id` = :album_id;';
|
|
foreach ($albums as $k => $v) {
|
|
$album_query .= strtr($album_query_tpl, [':counter' => count($v), ':album_id' => $k]);
|
|
}
|
|
$db = DB::getInstance();
|
|
$db->query($album_query);
|
|
$db->exec();
|
|
}
|
|
}
|
|
return $exec;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function update($id, $values)
|
|
{
|
|
if (array_key_exists('description', $values)) {
|
|
G\nullify_string($values['description']);
|
|
}
|
|
try {
|
|
return DB::update('albums', $values, ['id' => $id]);
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
// Delete album, return the number of deleted images
|
|
public static function delete($id)
|
|
{
|
|
try {
|
|
// Get the user id
|
|
$user_id = DB::get('albums', ['id' => $id])[0]['album_user_id'];
|
|
// Get album
|
|
$album = self::getSingle($id);
|
|
if (!$album) {
|
|
return false;
|
|
}
|
|
// Delete album, the easy part
|
|
$delete = DB::delete('albums', ['id' => $id]);
|
|
if (!$delete) {
|
|
return false;
|
|
}
|
|
// Delete album images
|
|
$db = DB::getInstance();
|
|
$db->query('SELECT image_id FROM ' . DB::getTable('images') . ' WHERE image_album_id=:image_album_id');
|
|
$db->bind(':image_album_id', $id);
|
|
$album_image_ids = $db->fetchAll();
|
|
// Delete the files
|
|
$images_deleted = 0;
|
|
foreach ($album_image_ids as $k => $v) {
|
|
if (Image::delete($v['image_id'], false)) { // We will update the user counts (image + album) at once
|
|
$images_deleted++;
|
|
}
|
|
}
|
|
// Update user
|
|
if ($user_id) {
|
|
$user = User::getSingle($user_id, 'id');
|
|
$user_updated_counts = [
|
|
'album_count' => '-1',
|
|
'image_count' => '-' . $images_deleted
|
|
];
|
|
DB::increment('users', $user_updated_counts, ['id' => $user_id]);
|
|
}
|
|
// Track stats
|
|
Stat::track([
|
|
'action' => 'delete',
|
|
'table' => 'albums',
|
|
'value' => '-1',
|
|
'date_gmt' => $album['date_gmt']
|
|
]);
|
|
return $images_deleted;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function deleteMultiple($ids)
|
|
{
|
|
if (!is_array($ids)) {
|
|
throw new AlbumException('Expecting array argument, ' . gettype($ids) . ' given in ' . __METHOD__, 100);
|
|
}
|
|
$affected = 0;
|
|
foreach ($ids as $id) {
|
|
$affected += self::delete($id);
|
|
}
|
|
return $affected;
|
|
}
|
|
|
|
public static function updateImageCount($id, $counter = 1, $operator = '+')
|
|
{
|
|
try {
|
|
$query = 'UPDATE `' . DB::getTable('albums') . '` SET `album_image_count` = ';
|
|
if (in_array($operator, ['+', '-'])) {
|
|
$query .= 'GREATEST(`album_image_count` ' . $operator . ' ' . $counter . ', 0)';
|
|
} else {
|
|
$query .= $counter;
|
|
}
|
|
$query .= ' WHERE `album_id` = :album_id';
|
|
$db = DB::getInstance();
|
|
$db->query($query);
|
|
$db->bind(':album_id', $id);
|
|
$exec = $db->exec();
|
|
return $exec;
|
|
} catch (Exception $e) {
|
|
throw new AlbumException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function fill(&$album, &$user = [])
|
|
{
|
|
$album['id_encoded'] = $album['id'] ? encodeID($album['id']) : null;
|
|
if ($album['name'] == null && $user['id']) {
|
|
$album['name'] = _s("%s's images", $user['name_short']);
|
|
}
|
|
if ($album['id'] == null) {
|
|
$album['url'] = $user ? User::getUrl($user['username']) : null;
|
|
$album['url_short'] = $album['url'];
|
|
} else {
|
|
$album['url'] = self::getUrl($album['id_encoded'], getSetting('seo_album_urls') ? $album['name'] : '');
|
|
$album['url_short'] = self::getUrl($album['id_encoded'], '');
|
|
}
|
|
$album['name_html'] = G\safe_html($album['name']);
|
|
if ($album['privacy'] == null) {
|
|
$album['privacy'] = "public";
|
|
}
|
|
switch ($album['privacy']) {
|
|
case 'private_but_link':
|
|
$album['privacy_notes'] = _s('Note: This content is private but anyone with the link will be able to see this.');
|
|
break;
|
|
case 'password':
|
|
$album['privacy_notes'] = _s('Note: This content is password protected. Remember to pass the content password to share.');
|
|
break;
|
|
case 'private':
|
|
$album['privacy_notes'] = _s('Note: This content is private. Change privacy to "public" to share.');
|
|
break;
|
|
default:
|
|
$album['privacy_notes'] = null;
|
|
break;
|
|
}
|
|
|
|
$private_str = _s('Private');
|
|
$privacy_to_label = [
|
|
'public' => _s('Public'),
|
|
'private' => $private_str . '/' . _s('Me'),
|
|
'private_but_link' => $private_str . '/' . _s('Link'),
|
|
'password' => $private_str . '/' . _s('Password'),
|
|
];
|
|
|
|
$album['privacy_readable'] = $privacy_to_label[$album['privacy']];
|
|
$album['name_with_privacy_readable'] = $album['name'] . ' (' . $album['privacy_readable'] . ')';
|
|
$album['name_with_privacy_readable_html'] = G\safe_html($album['name_with_privacy_readable']);
|
|
$album['name_truncated'] = G\truncate($album['name'], 28);
|
|
$album['name_truncated_html'] = G\safe_html($album['name_truncated']);
|
|
|
|
if (!empty($user)) {
|
|
User::fill($user);
|
|
}
|
|
}
|
|
|
|
public static function formatArray($dbrow, $safe = false)
|
|
{
|
|
try {
|
|
$output = DB::formatRow($dbrow);
|
|
self::fill($output, $output['user']);
|
|
$output['views_label'] = _n('view', 'views', $output['views']);
|
|
$output['how_long_ago'] = time_elapsed_string($output['date_gmt']);
|
|
|
|
if ($output['images_slice']) {
|
|
foreach ($output['images_slice'] as $k => $v) {
|
|
$output['images_slice'][$k] = Image::formatArray($output['images_slice'][$k]);
|
|
$output['images_slice'][$k]['flag'] = $output['images_slice'][$k]['nsfw'] ? 'unsafe' : 'safe';
|
|
}
|
|
}
|
|
|
|
if ($safe) {
|
|
unset($output['id'], $output['privacy_extra']);
|
|
unset($output['user']['id']);
|
|
}
|
|
|
|
return $output;
|
|
} catch (Exception $e) {
|
|
throw new ImageException($e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
public static function checkPassword($password, $user_password)
|
|
{
|
|
return G\timing_safe_compare($password, $user_password);
|
|
}
|
|
|
|
public static function storeUserPasswordHash($album_id, $user_password)
|
|
{
|
|
$_SESSION['password']['album'][$album_id] = password_hash($user_password, PASSWORD_BCRYPT);
|
|
}
|
|
|
|
public static function checkSessionPassword($album = [])
|
|
{
|
|
$user_password_hash = $_SESSION['password']['album'][$album['id']];
|
|
if (!isset($user_password_hash) || !password_verify($album['password'], $user_password_hash)) {
|
|
unset($_SESSION['password']['album'][$album['id']]);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Handle album creation flood
|
|
protected static function handleFlood()
|
|
{
|
|
$logged_user = Login::getUser();
|
|
if (!$logged_user or $logged_user['is_admin']) {
|
|
return false;
|
|
}
|
|
$flood_limit = [
|
|
'minute' => 20,
|
|
'hour' => 200,
|
|
'day' => 400,
|
|
'week' => 2000,
|
|
'month' => 10000
|
|
];
|
|
try {
|
|
$db = DB::getInstance();
|
|
$flood_db = $db->queryFetchSingle(
|
|
"SELECT
|
|
COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MINUTE), 1, NULL)) AS minute,
|
|
COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR), 1, NULL)) AS hour,
|
|
COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 DAY), 1, NULL)) AS day,
|
|
COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 WEEK), 1, NULL)) AS week,
|
|
COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH), 1, NULL)) AS month
|
|
FROM " . DB::getTable('albums') . " WHERE album_creation_ip='" . G\get_client_ip() . "' AND album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH)"
|
|
);
|
|
} catch (Exception $e) {
|
|
} // Silence
|
|
|
|
$is_flood = false;
|
|
$flood_by = '';
|
|
foreach (['minute', 'hour', 'day', 'week', 'month'] as $v) {
|
|
if ($flood_limit[$v] > 0 and $flood_db[$v] >= $flood_limit[$v]) {
|
|
$flood_by = $v;
|
|
$is_flood = true;
|
|
break;
|
|
}
|
|
}
|
|
if ($is_flood) {
|
|
if (!$_SESSION['flood_albums_notify'][$flood_by]) {
|
|
try {
|
|
$message_report = '<html><body>' . "\n";
|
|
$message_report .= strtr('Flooding IP <a href="' . G\get_base_url('search/images/?q=ip:%ip') . '">%ip</a>', ['%ip' => G\get_client_ip()]) . '<br>';
|
|
$message_report .= 'User <a href="' . $logged_user['url'] . '">' . $logged_user['name'] . '</a><br>';
|
|
$message_report .= '<br>';
|
|
$message_report .= '<b>Albums per time period</b>' . "<br>";
|
|
$message_report .= 'Minute: ' . $flood_db['minute'] . "<br>";
|
|
$message_report .= 'Hour: ' . $flood_db['hour'] . "<br>";
|
|
$message_report .= 'Week: ' . $flood_db['day'] . "<br>";
|
|
$message_report .= 'Month: ' . $flood_db['week'] . "<br>";
|
|
$message_report .= '</body></html>';
|
|
send_mail(getSetting('email_incoming_email'), 'Flood report user ID ' . $logged_user['id'], $message_report);
|
|
$_SESSION['flood_albums_notify'][$flood_by] = true;
|
|
} catch (Exception $e) {
|
|
} // Silence
|
|
}
|
|
return ['flood' => true, 'limit' => $flood_limit[$flood_by], 'count' => $flood_db[$flood_by], 'by' => $flood_by];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class AlbumException extends Exception
|
|
{
|
|
}
|