<?php
/**
* PHP versions 5
*
* phTagr : Tag, Browse, and Share Your Photos.
* Copyright 2006-2012, Sebastian Felis (hide@address.com)
*
* Licensed under The GPL-2.0 License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2006-2012, Sebastian Felis (hide@address.com)
* @link http://www.phtagr.org phTagr
* @package Phtagr
* @since phTagr 2.2b3
* @license GPL-2.0 (http://www.opensource.org/licenses/GPL-2.0)
*/
class Media extends AppModel
{
var $name = 'Media';
var $belongsTo = array(
'User' => array());
var $hasMany = array(
'Comment' => array('dependent' => true),
'File' => array('className' => 'MyFile'));
var $hasAndBelongsToMany = array(
'Group' => array(),
'Tag' => array(),
'Category' => array(),
'Location' => array('order' => 'Location.type'));
var $_aclMap = array(
ACL_LEVEL_GROUP => 'gacl',
ACL_LEVEL_USER => 'uacl',
ACL_LEVEL_OTHER => 'oacl');
var $actsAs = array('Type', 'Flag', 'Cache', 'Exclude');
function beforeDelete($cascade = true) {
// Delete media cache files
$this->unbindAll();
$this->set($this->findById($this->id));
$this->deleteCache();
return true;
}
function afterDelete() {
$this->File->unlinkMedia($this->id);
}
/**
* Unlink a file form a media. This function takes care of deleting the
* media. The media will be deleted if the file is dependent or no other
* dependent file is left for the media.
*
* @param data Media model data or media Id. If it is false, it will use
* $this->id.
* @param fileId File id of the file to be unlinked
*/
function unlinkFile(&$data, $fileId) {
if (is_numeric($data)) {
$mediaId = $data;
} elseif (isset($data['Media']['id'])) {
$mediaId = $data['Media']['id'];
} elseif (isset($data['id'])) {
$mediaId = $data['id'];
} else {
$mediaId = $this->id;
}
if (!$mediaId || $fileId <= 0) {
Logger::err("Invalid input");
return false;
}
$media = $this->findById($mediaId);
if (!$media) {
Logger::warn("Could not found media with id $mediaId");
return false;
}
$fileIds = Set::extract("/File/id", $media);
if (!in_array($fileId, $fileIds)) {
Logger::warn("Media $mediaId does not have file $fileId");
return false;
}
// Delete if: 1. only one file. 2. file with fileId is dependent. Or 3. if
// no other file is dependend
if (count($fileIds) == 1) {
$delete = true;
} else {
$delete = true;
foreach ($media['File'] as $file) {
if ($file['id'] == $fileId) {
if ($this->File->hasFlag($file, FILE_FLAG_DEPENDENT)) {
$delete = true;
break;
}
continue;
}
if ($this->File->hasFlag($file, FILE_FLAG_DEPENDENT)) {
$delete = false;
}
}
}
if ($delete) {
Logger::info("Delete media $mediaId");
$this->delete($mediaId);
} else {
$this->File->unlinkMedia(false, $fileId);
}
return true;
}
function addDefaultAcl(&$data, $user) {
if (!$data) {
$data =& $this->data;
}
if (!isset($user) || !isset($user['User']['id'])) {
Logger::err("User data is not correct! Media ACL will be wrong!");
Logger::trace($user);
}
// Access control values
$acl = $this->User->Option->getDefaultAcl($user);
$data['Media']['user_id'] = $user['User']['id'];
$data['Media']['gacl'] = $acl['gacl'];
$data['Media']['uacl'] = $acl['uacl'];
$data['Media']['oacl'] = $acl['oacl'];
$data['Group']['Group'] = array($acl['groupId']);
return $data;
}
/**
* Returns the file model by its type
*
* @param data Media model data
* @param fileType Required file type. Default is FILE_TYPE_IMAGE
* @param fullModel If true returns the full associated file model. If false
* returns only the file model of the media without associations
* @return Fals on error, null if file was not found
*/
function getFile($data, $fileType = FILE_TYPE_IMAGE, $fullModel = true) {
if (!$data) {
$data = $this->data;
}
if (!isset($data['File'])) {
Logger::err("Precondition failed");
return false;
}
foreach ($data['File'] as $file) {
if ($file['type'] == $fileType) {
if ($fullModel) {
return $this->File->findById($file['id']);
} else {
return array('File' => $file);
}
}
}
return null;
}
function canRead(&$media, &$user) {
return $this->checkAccess(&$media, &$user, ACL_READ_PREVIEW, ACL_READ_MASK, null);
}
function canReadOriginal(&$media, &$user) {
return $this->checkAccess(&$media, &$user, ACL_READ_ORIGINAL, ACL_READ_MASK, null);
}
function canWrite(&$media, &$user) {
return $this->checkAccess(&$media, &$user, ACL_WRITE_TAG, ACL_WRITE_MASK, null);
}
function canWriteAcl(&$media, &$user) {
return ($media['Media']['user_id'] == $user['User']['id'] ||
$user['User']['role'] >= ROLE_ADMIN);
}
/**
* Returns true if current user is allowed of the current flag
*
* @param data Current Media array
* @param user Current User array
* @param flag Flag bit which should be checkt
* @param mask Bitmask for the flag which should be checkt
* @param userGroupIds Array of user's group ids. If groups is null, it will be created
* by the user's data.
* @return True is user is allowed, False otherwise
*/
function checkAccess(&$data, &$user, $flag, $mask, $userGroupIds = array()) {
if (!$data || !$user || !isset($data['Media']) || !isset($user['User'])) {
Logger::err("precondition failed");
return false;
}
// check for public access
if (($data['Media']['oacl'] & $mask) >= $flag)
return true;
// check for members
if ($user['User']['role'] >= ROLE_USER &&
($data['Media']['uacl'] & $mask) >= $flag)
return true;
if ($userGroupIds === null) {
$userGroupIds = Set::extract('/Group/id', $user);
$userGroupIds = am($userGroupIds, Set::extract('/Member/id', $user));
}
$mediaGroupIds = Set::extract('/Group/id', $data);
// user groups and media groups must match to gain access via common group
$match = array_intersect($mediaGroupIds, $userGroupIds);
if ($user['User']['role'] >= ROLE_GUEST &&
($data['Media']['gacl'] & $mask) >= $flag &&
count($match) > 0) {
return true;
}
// Media owner and admin check
if ($user['User']['id'] == $data['Media']['user_id'] ||
$user['User']['role'] == ROLE_ADMIN) {
return true;
}
return false;
}
/**
* Set the access flags of write and read options according to the current user
*
* @param data Reference of the Media array
* @param user User array
* @return $data of Media data with the access flags
*/
function setAccessFlags(&$data, $user) {
if (!isset($data)) {
return $data;
}
// at least dummy user
$user = am(array('User' => array('id' => -1, 'role' => ROLE_NOBODY), 'Member' => array()), $user);
//Logger::debug($user);
$oacl = $data['Media']['oacl'];
$uacl = $data['Media']['uacl'];
$gacl = $data['Media']['gacl'];
$userGroupIds = Set::extract('/Group/id', $user);
$userGroupIds = am($userGroupIds, Set::extract('/Member/id', $user));
$data['Media']['canWriteTag'] = $this->checkAccess(&$data, &$user, ACL_WRITE_TAG, ACL_WRITE_MASK, &$userGroupIds);
$data['Media']['canWriteMeta'] = $this->checkAccess(&$data, &$user, ACL_WRITE_META, ACL_WRITE_MASK, &$userGroupIds);
$data['Media']['canWriteCaption'] = $this->checkAccess(&$data, &$user, ACL_WRITE_CAPTION, ACL_WRITE_MASK, &$userGroupIds);
$data['Media']['canReadPreview'] = $this->checkAccess(&$data, &$user, ACL_READ_PREVIEW, ACL_READ_MASK, &$userGroupIds);
$data['Media']['canReadHigh'] = $this->checkAccess(&$data, &$user, ACL_READ_HIGH, ACL_READ_MASK, &$userGroupIds);
$data['Media']['canReadOriginal'] = $this->checkAccess(&$data, &$user, ACL_READ_ORIGINAL, ACL_READ_MASK, &$userGroupIds);
if (($data['Media']['oacl'] & ACL_READ_PREVIEW) > 0) {
$data['Media']['visibility'] = ACL_LEVEL_OTHER;
} elseif (($data['Media']['uacl'] & ACL_READ_PREVIEW) > 0) {
$data['Media']['visibility'] = ACL_LEVEL_USER;
} elseif (($data['Media']['gacl'] & ACL_READ_PREVIEW) > 0) {
$data['Media']['visibility'] = ACL_LEVEL_GROUP;
} else {
$data['Media']['visibility'] = ACL_LEVEL_PRIVATE;
}
$data['Media']['isOwner'] = ($data['Media']['user_id'] == $user['User']['id']) ? true : false;
$data['Media']['canWriteAcl'] = $this->checkAccess(&$data, &$user, 1, 0, &$userGroupIds);
$data['Media']['isDirty'] = (($data['Media']['flag'] & MEDIA_FLAG_DIRTY) > 0) ? true : false;
return $data;
}
/**
* Increase the ACL level. It checks the current flag and increases the ACL
* level of lower ACL levels (first level is ACL_LEVEL_GROUP, second level is
* ACL_LEVEL_USER and the third level is ACL_LEVEL_OTHER).
*
* @param data Array of image data
* @param flag Threshold flag which indicates the upper inclusive bound
* @param mask Bit mask of flag
* @param level Highes ACL level which should be increased
*/
function _increaseAcl(&$data, $flag, $mask, $level) {
//Logger::debug("Increase: {$data['Media']['gacl']},{$data['Media']['uacl']},{$data['Media']['oacl']}: $flag/$mask ($level)");
if ($level>ACL_LEVEL_OTHER)
return;
for ($l=ACL_LEVEL_GROUP; $l<=$level; $l++) {
$name = $this->_aclMap[$l];
if (($data['Media'][$name]&($mask))<$flag)
$data['Media'][$name]=($data['Media'][$name]&(~$mask))|$flag;
}
//Logger::debug("Increase (result): {$data['Media']['gacl']},{$data['Media']['uacl']},{$data['Media']['oacl']}: $flag/$mask ($level)");
}
/**
* Decrease the ACL level. Decreases the ACL level of higher ACL levels
* according to the current flag (first level is ACL_LEVEL_GROUP, second level
* is ACL_LEVEL_USER and the third level is ACL_LEVEL_OTHER). The decreased ACL
* value is the ACL value of the higher levels which is less than the current
* threshold or it is zero if no lower ACL value is available.
*
* @param data Array of image data
* @param flag Threshold flag which indicates the upper exlusive bound
* @param mask Bit mask of flag
* @param level Lower ACL level which should be downgraded
*/
function _decreaseAcl(&$data, $flag, $mask, $level) {
//Logger::debug("Decrease: {$data['Media']['gacl']},{$data['Media']['uacl']},{$data['Media']['oacl']}: $flag/$mask ($level)");
if ($level<ACL_LEVEL_GROUP)
return;
for ($l=ACL_LEVEL_OTHER; $l>=$level; $l--) {
$name = $this->_aclMap[$l];
// Evaluate the available ACL value which is lower than the threshold
if ($l==ACL_LEVEL_OTHER)
$lower = 0;
else {
$next = $this->_aclMap[$l+1];
$lower = $data['Media'][$next]&($mask);
}
$lower=($lower<$flag)?$lower:0;
if (($data['Media'][$name]&($mask))>=$flag)
$data['Media'][$name]=($data['Media'][$name]&(~$mask))|$lower;
}
//Logger::debug("Decrease (result): {$data['Media']['gacl']},{$data['Media']['uacl']},{$data['Media']['oacl']}: $flag/$mask ($level)");
}
function setAcl(&$data, $flag, $mask, $level) {
if ($level<ACL_LEVEL_KEEP || $level>ACL_LEVEL_OTHER)
return false;
if ($level==ACL_LEVEL_KEEP)
return $data;
if ($level>=ACL_LEVEL_GROUP)
$this->_increaseAcl(&$data, $flag, $mask, $level);
if ($level<ACL_LEVEL_OTHER)
$this->_decreaseAcl(&$data, $flag, $mask, $level+1);
return $data;
}
/**
* Generates a has and belongs to many relation query for the image
*
* @param id Id of the image
* @param model Model name
* @return Array of the relation model
*/
function _optimizedHabtm($id, $model) {
if (!isset($this->hasAndBelongsToMany[$model]['cacheQuery'])) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$table = $db->fullTableName($this->{$model}->table, false, false);
$alias = $this->{$model}->alias;
$key = $this->{$model}->primaryKey;
$joinTable = $this->hasAndBelongsToMany[$model]['joinTable'];
$joinTable = $db->fullTableName($joinTable, false, false);
$joinAlias = $this->hasAndBelongsToMany[$model]['with'];
$foreignKey = $this->hasAndBelongsToMany[$model]['foreignKey'];
$associationForeignKey = $this->hasAndBelongsToMany[$model]['associationForeignKey'];
$sql = "SELECT `$alias`.* FROM `$table` AS `$alias`, `$joinTable` AS `$joinAlias` WHERE `$alias`.`$key` = `$joinAlias`.`$associationForeignKey` AND `$joinAlias`.`$foreignKey`=%d";
if (!empty($this->hasAndBelongsToMany[$model]['order'])) {
$sql .= " ORDER BY ".$this->hasAndBelongsToMany[$model]['order'];
}
$this->hasAndBelongsToMany[$model]['cacheQuery'] = $sql;
}
$sql = sprintf($this->hasAndBelongsToMany[$model]['cacheQuery'], $id);
$result = $this->query($sql);
$list = array();
if ($result) {
foreach ($result as $item) {
$list[] = &$item[$model];
}
}
return $list;
}
/**
* Generates a has one relation query for the image
*
* @param modelId Id of the related model
* @param model Model name
* @return Array of the relation model
*/
function _optimizedBelongsTo($modelId, $model) {
if (!$modelId)
return array();
$db =& ConnectionManager::getDataSource($this->useDbConfig);
if (!isset($this->belongsTo[$model]['cacheQuery'])) {
$table = $db->fullTableName($this->{$model}->table, false, false);
$alias = $this->{$model}->alias;
$key = $this->{$model}->primaryKey;
$tp = $this->tablePrefix;
$sql = "SELECT `$alias`.* FROM `$table` AS `$alias` WHERE `$alias`.`$key`=%d";
$this->belongsTo[$model]['cacheQuery'] = $sql;
}
$sql = sprintf($this->belongsTo[$model]['cacheQuery'], $modelId);
$result = $this->query($sql);
if (count($result))
return $result[0][$model];
else
return array();
}
/**
* Generates a has one relation query for the image
*
* @param modelId Id of the related model
* @param model Model name
* @return Array of the relation model
*/
function _optimizedHasMany($modelId, $model) {
if (!$modelId) {
return array();
}
$db =& ConnectionManager::getDataSource($this->useDbConfig);
if (!isset($this->hasMany[$model]['cacheQuery'])) {
$config = $this->hasMany[$model];
$table = $db->fullTableName($this->{$model}->table, false, false);
$alias = $this->{$model}->alias;
$key = $config['foreignKey'];
$tp = $this->tablePrefix;
$sql = "SELECT `$alias`.* FROM `$table` AS `$alias` WHERE `$alias`.`$key`=%d";
$this->hasMany[$model]['cacheQuery'] = $sql;
}
$sql = sprintf($this->hasMany[$model]['cacheQuery'], $modelId);
//Logger::debug($sql);
$result = $this->query($sql);
if (count($result)) {
$tmp = array();
foreach ($result as $data) {
$tmp[] = $data[$model];
}
return $tmp;
} else {
return array();
}
}
/**
* The function Model::find slows down the hole search. This function builds
* the query manually for speed optimazation
*
* @param id Media id
* @return Return the image Array as find
*/
function optimizedRead($id) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$myTable = $db->fullTableName($this->table, false, false);
$sql = "SELECT Media.* FROM `$myTable` AS Media WHERE Media.id = $id";
$result = $this->query($sql);
if (!$result)
return array();
$image = &$result[0];
foreach ($this->belongsTo as $model => $config) {
$name = Inflector::underscore(Inflector::singularize($model));
$image[$model] = $this->_optimizedBelongsTo($image['Media'][$name.'_id'], $model);
}
foreach ($this->hasMany as $model => $config) {
$name = Inflector::underscore(Inflector::singularize($model));
$image[$model] = $this->_optimizedHasMany($image['Media']['id'], $model);
}
foreach ($this->hasAndBelongsToMany as $model => $config) {
$image[$model] = $this->_optimizedHabtm($id, $model);
}
return $image;
}
/**
* Build join for ACL condition
*/
function buildAclJoin($alias) {
$this->bindModel(array('hasMany' => array($alias => array('className' => 'GroupsMedia'))));
$config = $this->hasMany[$alias];
$foreignKey = $config['foreignKey'];
$join = array(
'table' => $this->GroupsMedia,
'alias' => $alias,
'type' => 'LEFT',
'conditions' => array("`{$this->alias}`.`{$this->primaryKey}` = `$alias`.`{$foreignKey}`")
);
return $join;
}
/**
* Build ACL query for media.
*
* @param user Current user
* @param userId User id of own user or foreign user. If user id is equal with
* the id of the current user, the user is treated as 'My Medias'. Otherwise
* the default acl will apply
* @param level Level of ACL which image must be have. Default is ACL_READ_PREVIEW.
* @return returns ACL query
*/
function buildAclQuery($user, $userId = 0, $level = ACL_READ_PREVIEW) {
$level = intval($level);
$conditions = array();
$joins = array();
if ($userId > 0 && $user['User']['id'] == $userId) {
// My Medias
if ($user['User']['role'] >= ROLE_USER) {
$conditions['Media.user_id'] = $userId;
} elseif ($user['User']['role'] == ROLE_GUEST) {
$groupIds = Set::extract('/Member/id', $user);
if (count($groupIds)) {
$conditions['AclGroups.group_id'] = $groupIds;
$conditions['Media.gacl >='] = $level;
$joins[] = $this->buildAclJoin('AclGroups');
} else {
// no images
$conditions[] = "1 = 0";
}
}
} else {
// Another user, if set
if ($userId > 0) {
$conditions['Media.user_id'] = $userId;
}
// General ACL
if ($user['User']['role'] < ROLE_ADMIN) {
$acl = array();
// All images of group on Guests and Users
if ($user['User']['role'] >= ROLE_GUEST) {
$groupIds = Set::extract('/Group/id', $user);
$groupIds = am($groupIds, Set::extract('/Member/id', $user));
if (count($groupIds)) {
$acl['AND'] = array(
'AclGroups.group_id' => $groupIds,
'Media.gacl >=' => $level);
$joins[] = $this->buildAclJoin('AclGroups');
}
}
if ($user['User']['role'] >= ROLE_USER) {
// Own image
if ($userId == 0) {
$acl['Media.user_id'] = $user['User']['id'];
}
// Other users
$acl['Media.uacl >='] = $level;
}
// Public
$acl['Media.oacl >='] = $level;
if (count($acl) == 1) {
$conditions = am($conditions, $acl);
} else {
$conditions['OR'] = $acl;
}
}
}
return array('joins' => $joins, 'conditions' => $conditions);
}
function updateRanking($data) {
if (!isset($data['Media']['id'])) {
Logger::warn("Precondition failed");
return false;
}
$timediff = time() - strtotime($data['Media']['lastview']);
$ranking = (0.9 * $data['Media']['ranking']) + (0.1 / ($timediff + 1));
$data['Media']['ranking'] = $ranking;
$data['Media']['lastview'] = date("Y-m-d H:i:s", time());
$data['Media']['clicks']++;
if (!$this->save($data['Media'], true, array('clicks', 'ranking', 'lastview'))) {
Logger::err("Could not save new ranking data");
return false;
} else {
Logger::trace("Update ranking of media {$data['Media']['id']} to $ranking with {$data['Media']['clicks']} click(s)");
return true;
}
}
/**
* Create tag cloud of HABTM model assoziation
*
* @param array $user Current User
* @param string $assoc HABTM model assoziation
* @param int $num Maximum tags
* @return array Map from name to hits
*/
function cloud($user, $assoc = 'Tag', $num = 50) {
if (!isset($this->hasAndBelongsToMany[$assoc])) {
return array();
}
$myTable = $this->tablePrefix.$this->table;
$table = $this->{$assoc}->tablePrefix.$this->{$assoc}->table;
$alias = $this->{$assoc}->alias;
$key = $this->{$assoc}->primaryKey;
$config = $this->hasAndBelongsToMany[$assoc];
$joinTable = $this->tablePrefix.$config['joinTable'];
$joinAlias = $config['with'];
$foreignKey = $config['foreignKey'];
$associationForeignKey = $config['associationForeignKey'];
$aclQuery = $this->buildAclQuery($user);
$fields = array("`$alias`.`name`", "COUNT(`$alias`.`name`) AS hits");
$joins = am(array(
"JOIN `$joinTable` AS `$joinAlias` ON `$alias`.`$key` = `$joinAlias`.`$associationForeignKey`",
"JOIN `$myTable` AS `{$this->alias}` ON `$joinAlias`.`$foreignKey` = `{$this->alias}`.`{$this->primaryKey}`"
), $aclQuery['joins']);
$conditions = $aclQuery['conditions'];
$query = array(
'fields' => $fields,
'joins' => $joins,
'conditions' => $conditions,
'group' => "`$alias`.`name`",
'order' => "hits DESC",
'limit' => $num,
'page' => 0
);
$data = $this->{$assoc}->find('all', $query);
if (count($data)) {
$data = Set::combine($data, "{n}.$assoc.name", "{n}.0.hits");
}
return $data;
}
/**
* Deletes all HABTM association from images of a given user like Tag, Categories
*
* @param userId User ID
*/
function _deleteHasAndBelongsToManyFromUser($userId) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$table = $db->fullTableName($this->table, false, false);
$alias = $this->alias;
$key = $this->primaryKey;
Logger::info("Delete HasAndBelongsToMany Media association of user '$userId'");
foreach ($this->hasAndBelongsToMany as $model => $data) {
$joinTable = $db->fullTableName($data['joinTable'], false, false);
$joinAlias = $data['with'];
$foreignKey = $data['foreignKey'];
$sql = "DELETE FROM `$joinAlias`".
" USING `$joinTable` AS `$joinAlias`, `$table` AS `$alias`".
" WHERE `$alias`.`user_id` = $userId AND `$alias`.`$key` = `$joinAlias`.`$foreignKey`";
Logger::debug("Delete $model HABTM associations");
$this->query($sql);
}
}
function _deleteHasManyFromUser($userId) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
$table = $db->fullTableName($this->table, false, false);
$alias = $this->alias;
$key = $this->primaryKey;
Logger::info("Delete HasMany Media assosciation of user '$userId'");
foreach ($this->hasMany as $model => $data) {
if (!isset($data['dependent']) || !$data['dependent']) {
continue;
}
$manyTable = $db->fullTableName($this->{$model}->table, false, false);
$foreignKey = $data['foreignKey'];
$sql = "DELETE FROM `$model`".
" USING `$manyTable` AS `$model`, `$table` AS `$alias`".
" WHERE `$alias`.`user_id` = $userId AND `$alias`.`$key` = `$model`.`$foreignKey`";
Logger::debug("Delete $model HasMany associations");
$this->query($sql);
}
}
function deleteFromUser($userId) {
$this->bindModel(array(
'hasMany' => array(
'Comment' => array('dependent' => true)
)));
$this->_deleteHasAndBelongsToManyFromUser($userId);
$this->_deleteHasManyFromUser($userId);
$this->deleteAll("Media.user_id = $userId");
}
/**
* Count all media given by the group IDs
* @param groupIds Single group ID value or array of group IDs
* @return Count of media which are assigned to the given groups
*/
function countByGroupId($groupIds) {
$this->unbindModel(array('belongsTo' => array('Group')));
return $this->find('count', array(
'conditions' => array('Group.id' => $groupIds),
'joins' => array("JOIN `{$this->tablePrefix}groups` AS `Group` ON `Media`.`group_id` = `Group`.`id`")));
}
/**
* Returns the rotation of the media
*
* @return One of 0, 90, 180, 270 degree
*/
function getRotationInDegree($media) {
$degree = 0;
$data = $this->stripAlias($media);
switch ($data['orientation']) {
case 1: break;
case 3: $degree = 180; break;
case 6: $degree = 90; break;
case 8: $degree = 270; break;
default:
Logger::warn("Unsupported rotation flag: {$data['orientation']} for {$this->toString($data)}");
break;
}
return $degree;
}
/**
* Split the geo information to latitude and longitude
*
* @param data Model Data
* @param geo Geo data string
* @return Model data
*/
function splitGeo(&$data, $geo) {
$numbers = preg_split('/\s*,\s*/', trim($geo));
if (count($numbers) != 2) {
Logger::debug("Invalid geo input: $geo");
return;
} elseif ($numbers[0] == "-") {
$data['Media']['latitude'] = '-';
$data['Media']['longitude'] = '-';
return;
}
// validate numbers
foreach ($numbers as $number) {
if (!preg_match('/^[+-]?\d+(\.\d+)?$/', $number)) {
Logger::debug("Invalid geo input number: $number");
return;
}
}
$data['Media']['latitude'] = $numbers[0];
$data['Media']['longitude'] = $numbers[1];
}
function rotate(&$data, $orientation, $rotation) {
$rotateClockwise = array(
1 => 6, 6 => 3, 3 => 8, 8 => 1,
2 => 5, 5 => 4, 4 => 7, 7 => 2
);
$rotated = $orientation;
switch ($rotation) {
case '270': $rotated = $rotateClockwise[$rotated];
case '180': $rotated = $rotateClockwise[$rotated];
case '90': $rotated = $rotateClockwise[$rotated];
default: break;
}
if ($rotated != $orientation) {
$data['Media']['orientation'] = $rotated;
}
}
/**
* Check acl group of the user and set it as media group id
*
* @param data Data input
* @param user Current user
*/
function prepareGroupData(&$data, &$user) {
if (!isset($data['Group']['id'])) {
return;
}
$groupId = $data['Group']['id'];
$groupIds = Set::extract('/Group/id', $this->Group->getGroupsForMedia($user));
$groupIds[] = -1; // no group
if (in_array($groupId, $groupIds)) {
$data['Media']['group_id'] = $groupId;
} else {
$data['Media']['group_id'] = 0;
}
return $data;
}
/**
* Update ACL of media
*
* @param target Target model data
* @param media Media model data
* @param data Update data
*/
function updateAcl(&$target, &$media, &$data) {
$fields = array('gacl', 'uacl', 'oacl');
// copy acl fields to target
foreach ($fields as $field) {
$target['Media'][$field] = $media['Media'][$field];
}
// Higher grants first
if (!empty($data['Media']['writeMeta'])) {
$this->setAcl(&$target, ACL_WRITE_META, ACL_WRITE_MASK, $data['Media']['writeMeta']);
}
if (!empty($data['Media']['writeTag'])) {
$this->setAcl(&$target, ACL_WRITE_TAG, ACL_WRITE_MASK, $data['Media']['writeTag']);
}
if (!empty($data['Media']['readOriginal'])) {
$this->setAcl(&$target, ACL_READ_ORIGINAL, ACL_READ_MASK, $data['Media']['readOriginal']);
}
if (!empty($data['Media']['readPreview'])) {
$this->setAcl(&$target, ACL_READ_PREVIEW, ACL_READ_MASK, $data['Media']['readPreview']);
}
// Remove unchanged values
foreach ($fields as $field) {
if ($target['Media'][$field] == $media['Media'][$field]) {
unset($target['Media'][$field]);
}
}
}
/**
* Prepare the input data for edit
*
* @param type $data User input data
* @param type $user Current user
* @return array Array of add and removals
*/
function prepareMultiEditData(&$data, &$user) {
$tmp = array();
if (!empty($data['Media']['geo'])) {
$this->splitGeo(&$data, $data['Media']['geo']);
}
$fields = array('name', 'description', 'date', 'latitude', 'longitude', 'rotation', 'readPreview', 'readOriginal', 'writeTag', 'writeMeta');
foreach ($fields as $field) {
if (!empty($data['Media'][$field])) {
$tmp['Media'][$field] = $data['Media'][$field];
}
}
$group = $this->Group->prepareMultiEditData(&$data, &$user);
if ($group) {
$tmp['Group'] = $group['Group'];
}
$tag = $this->Tag->prepareMultiEditData(&$data);
if ($tag) {
$tmp['Tag'] = $tag['Tag'];
}
$category = $this->Category->prepareMultiEditData(&$data);
if ($category) {
$tmp['Category'] = $category['Category'];
}
$location = $this->Location->prepareMultiEditData(&$data);
if ($location) {
$tmp['Location'] = $location['Location'];
}
if (!count($tmp)) {
return false;
}
return $tmp;
}
function editMulti(&$media, &$data) {
$tmp = array('Media' => array('id' => $media['Media']['id'], 'user_id' => $media['Media']['user_id']));
if ($media['Media']['canWriteTag']) {
$tag = $this->Tag->editMetaMulti(&$media, &$data);
if ($tag) {
$tmp['Tag'] = $tag['Tag'];
}
}
if ($media['Media']['canWriteMeta']) {
$category = $this->Category->editMetaMulti(&$media, &$data);
if ($category) {
$tmp['Category'] = $category['Category'];
}
$location = $this->Location->editMetaMulti(&$media, &$data);
if ($location) {
$tmp['Location'] = $location['Location'];
}
$fields = array('latitude', 'longitude');
foreach ($fields as $field) {
if (empty($data['Media'][$field])) {
continue;
} else if ($data['Media'][$field] == '-') {
$tmp['Media'][$field] = null;
} else {
$tmp['Media'][$field] = $data['Media'][$field];
}
}
}
if ($media['Media']['canWriteCaption']) {
$fields = array('name', 'caption', 'date');
foreach ($fields as $field) {
if (empty($data['Media'][$field])) {
continue;
} else {
$tmp['Media'][$field] = $data['Media'][$field];
}
}
if (isset($data['Media']['rotation'])) {
$this->rotate(&$tmp, $media['Media']['orientation'], $data['Media']['rotation']);
}
}
if (count($tmp) != 1 || count($tmp['Media']) != 2) {
$tmp['Media']['flag'] = ($media['Media']['flag'] | MEDIA_FLAG_DIRTY);
}
if ($media['Media']['canWriteAcl']) {
$groups = $this->Group->editMetaMulti(&$media, &$data);
if ($groups) {
$tmp['Group'] = $groups['Group'];
}
$this->updateAcl(&$tmp, &$media, &$data);
}
if (count($tmp) == 1 && count($tmp['Media']) == 2) {
return false;
}
return $tmp;
}
/**
* Creates an new media data with updated values of given data
*
* @param type $media Media model data array
* @param type $data Input data array
* @param type $user Current user
* @return type
*/
function editSingle(&$media, &$data, &$user) {
$tmp = array('Media' => array('id' => $media['Media']['id'], 'user_id' => $media['Media']['user_id']));
if ($media['Media']['canWriteTag']) {
$tag = $this->Tag->editMetaSingle(&$media, &$data);
if (isset($tag['Tag'])) {
$tmp['Tag'] = $tag['Tag'];
}
}
if ($media['Media']['canWriteMeta']) {
$category = $this->Category->editMetaSingle(&$media, &$data);
if (isset($category['Category'])) {
$tmp['Category'] = $category['Category'];
}
$location = $this->Location->editMetaSingle(&$media, &$data);
if (isset($location['Location'])) {
$tmp['Location'] = $location['Location'];
}
if (!empty($data['Media']['geo'])) {
$this->splitGeo(&$data, $data['Media']['geo']);
}
$fields = array('latitude', 'longitude', 'altitude');
foreach ($fields as $field) {
if (isset($data['Media'][$field]) && $media['Media'][$field] !== $data['Media'][$field]) {
$tmp['Media'][$field] = $data['Media'][$field];
}
}
}
if ($media['Media']['canWriteCaption']) {
$fields = array('name', 'caption', 'date', 'latitude', 'longitude');
foreach ($fields as $field) {
if (isset($data['Media'][$field]) && $media['Media'][$field] !== $data['Media'][$field]) {
$tmp['Media'][$field] = $data['Media'][$field];
}
}
if (isset($data['Media']['rotation'])) {
$this->rotate(&$tmp, $media['Media']['orientation'], $data['Media']['rotation']);
}
}
if (count($tmp) != 1 || count($tmp['Media']) != 2) {
$tmp['Media']['flag'] = ($media['Media']['flag'] | MEDIA_FLAG_DIRTY);
}
if ($media['Media']['canWriteAcl']) {
$groups = $this->Group->editMetaSingle(&$media, &$data, &$user);
if (isset($groups['Group'])) {
$tmp['Group'] = $groups['Group'];
}
$this->updateAcl(&$tmp, &$media, &$data);
}
// Unchanged data
if (count($tmp) == 1 && count($tmp['Media']) == 2) {
return false;
}
return $tmp;
}
}
?>