*
*/
/**
* Модуль управления медиа-данными (изображения, видео и т.п.)
*/
class ModuleMedia extends ModuleORM {
/**
* Список типов медиа
* Свои кастомные типы необходимо нумеровать с 1000
*/
const TYPE_IMAGE=1;
const TYPE_VIDEO=2;
/**
* Объект текущего пользователя
*
* @var ModuleUser_EntityUser|null
*/
protected $oUserCurrent;
protected $oMapper=null;
protected $aTargetTypes=array(
'topic'=>array(),
'comment'=>array(),
);
/**
* Список доступных типов медиа
*
* @var array
*/
protected $aMediaTypes=array(
self::TYPE_IMAGE,self::TYPE_VIDEO
);
/**
* Инициализация
*
*/
public function Init() {
parent::Init();
$this->oMapper=Engine::GetMapper(__CLASS__);
$this->oUserCurrent=$this->User_GetUserCurrent();
}
/**
* Возвращает список типов объектов
*
* @return array
*/
public function GetTargetTypes() {
return $this->aTargetTypes;
}
/**
* Добавляет в разрешенные новый тип
*
* @param string $sTargetType Тип
* @param array $aParams Параметры
* @return bool
*/
public function AddTargetType($sTargetType,$aParams=array()) {
if (!array_key_exists($sTargetType,$this->aTargetTypes)) {
$this->aTargetTypes[$sTargetType]=$aParams;
return true;
}
return false;
}
/**
* Проверяет разрешен ли данный тип
*
* @param string $sTargetType Тип
* @return bool
*/
public function IsAllowTargetType($sTargetType) {
return in_array($sTargetType,array_keys($this->aTargetTypes));
}
/**
* Возвращает парметры нужного типа
*
* @param string $sTargetType
*
* @return mixed
*/
public function GetTargetTypeParams($sTargetType) {
if ($this->IsAllowTargetType($sTargetType)) {
return $this->aTargetTypes[$sTargetType];
}
}
/**
* Проверяет разрешен ли тип медиа
*
* @param string $sType
*
* @return bool
*/
public function IsAllowMediaType($sType) {
return in_array($sType,$this->aMediaTypes);
}
/**
* Проверка объекта target - владелец медиа
*
* @param string $sTargetType Тип
* @param int $iTargetId ID владельца
* @return bool
*/
public function CheckTarget($sTargetType,$iTargetId) {
if (!$this->IsAllowTargetType($sTargetType)) {
return false;
}
$sMethod = 'CheckTarget'.func_camelize($sTargetType);
if (method_exists($this,$sMethod)) {
return $this->$sMethod($iTargetId);
}
return false;
}
public function Upload($aFile,$sTargetType,$sTargetId,$sTargetTmp=null) {
if (is_string($aFile)) {
return $this->UploadUrl($aFile,$sTargetType,$sTargetId,$sTargetTmp);
} else {
return $this->UploadLocal($aFile,$sTargetType,$sTargetId,$sTargetTmp);
}
}
public function UploadLocal($aFile,$sTargetType,$sTargetId,$sTargetTmp=null) {
if(!is_array($aFile) || !isset($aFile['tmp_name']) || !isset($aFile['name'])) {
return false;
}
$aPathInfo=pathinfo($aFile['name']);
$sExtension=isset($aPathInfo['extension']) ? $aPathInfo['extension'] : 'unknown';
$sFileName = $aPathInfo['filename'].'.'.$sExtension;
/**
* Копируем загруженный файл
*/
$sDirTmp=Config::Get('path.tmp.server').'/media/';
if (!is_dir($sDirTmp)) {
@mkdir($sDirTmp,0777,true);
}
$sFileTmp=$sDirTmp.$sFileName;
if (!move_uploaded_file($aFile['tmp_name'],$sFileTmp)) {
return 'Не удалось загрузить файл';
}
/**
* TODO: проверить на размер файла в байтах
*/
return $this->ProcessingFile($sFileTmp,$sTargetType,$sTargetId,$sTargetTmp);
}
public function UploadUrl($sFileUrl,$sTargetType,$sTargetId,$sTargetTmp=null) {
/**
* Проверяем, является ли файл изображением
* TODO: файл может быть не только изображением, поэтому требуется рефакторинг
*/
if(!$aImageInfo=(@getimagesize($sFileUrl))) {
return 'Файл не является изображением';
}
$aTypeImage=array(1=>'gif',2=>'jpg',3=>'png'); // see http://php.net/manual/en/function.exif-imagetype.php
$sExtension=isset($aTypeImage[$aImageInfo[2]]) ? $aTypeImage[$aImageInfo[2]] : 'jpg';
/**
* Открываем файловый поток и считываем файл поблочно,
* контролируя максимальный размер изображения
*/
$rFile=fopen($sFileUrl,'r');
if(!$rFile) {
return 'Не удалось загрузить файл';
}
$iMaxSizeKb=Config::Get('module.media.image.max_size_url');
$iSizeKb=0;
$sContent='';
while (!feof($rFile) and $iSizeKb<$iMaxSizeKb) {
$sContent.=fread($rFile ,1024*2);
$iSizeKb++;
}
/**
* Если конец файла не достигнут,
* значит файл имеет недопустимый размер
*/
if(!feof($rFile)) {
return 'Превышен максимальный размер файла: '.Config::Get('module.media.image.max_size_url').'Kb';
}
fclose($rFile);
/**
* Копируем загруженный файл
*/
$sDirTmp=Config::Get('path.tmp.server').'/media/';
if (!is_dir($sDirTmp)) {
@mkdir($sDirTmp,0777,true);
}
$sFileTmp=$sDirTmp.func_generator().'.'.$sExtension;
$rFile=fopen($sFileTmp,'w');
fwrite($rFile,$sContent);
fclose($rFile);
return $this->ProcessingFile($sFileTmp,$sTargetType,$sTargetId,$sTargetTmp);
}
public function ProcessingFile($sFileTmp,$sTargetType,$sTargetId,$sTargetTmp=null) {
/**
* Определяем тип файла по расширенияю и запускаем обработку
*/
$aPathInfo=pathinfo($sFileTmp);
$sExtension=@strtolower($aPathInfo['extension']);
if (in_array($sExtension,array('jpg','jpeg','gif','png'))) {
return $this->ProcessingFileImage($sFileTmp,$sTargetType,$sTargetId,$sTargetTmp);
}
return 'Неверный тип файла';
}
public function ProcessingFileImage($sFileTmp,$sTargetType,$sTargetId,$sTargetTmp=null) {
$aPathInfo=pathinfo($sFileTmp);
$aParams=$this->Image_BuildParams('media.'.$sTargetType);
/**
* Если объект изображения не создан, возвращаем ошибку
*/
if(!$oImage=$this->Image_Open($sFileTmp,$aParams)) {
$this->Fs_RemoveFileLocal($sFileTmp);
return $this->Image_GetLastError();
}
$iWidth=$oImage->getWidth();
$iHeight=$oImage->getHeight();
$sPath=$this->GetSaveDir($sTargetType,$sTargetId);
/**
* Уникальное имя файла
*/
$sFileName=func_generator(20);
/**
* Сохраняем оригинальную копию
*/
if (!$sFileResult=$oImage->saveSmart($sPath,$sFileName)) {
$this->Fs_RemoveFileLocal($sFileTmp);
return $this->Image_GetLastError();
}
$aSizes=Config::Get("module.media.type.{$sTargetType}.image_sizes");
if (!$aSizes) {
$aSizes=Config::Get("module.media.image.sizes");
}
/**
* Генерируем варианты с необходимыми размерами
*/
$this->GenerateImageBySizes($sFileTmp,$sPath,$sFileName,$aSizes,$aParams);
/**
* Сохраняем медиа
*/
$oMedia=Engine::GetEntity('ModuleMedia_EntityMedia');
$oMedia->setUserId($this->oUserCurrent ? $this->oUserCurrent->getId() : null);
$oMedia->setType(self::TYPE_IMAGE);
$oMedia->setFilePath($this->Fs_GetPathRelative($sFileResult,true));
$oMedia->setFileName($aPathInfo['filename']);
$oMedia->setFileSize(filesize($sFileTmp));
$oMedia->setWidth($iWidth);
$oMedia->setHeight($iHeight);
$oMedia->setDataOne('image_sizes',$aSizes);
/**
* Теперь можно удалить временный файл
*/
$this->Fs_RemoveFileLocal($sFileTmp);
/**
* Добавляем в БД
*/
if ($oMedia->Add()) {
/**
* Создаем связь с владельцем
*/
$oTarget=Engine::GetEntity('ModuleMedia_EntityTarget');
$oTarget->setMediaId($oMedia->getId());
$oTarget->setTargetType($sTargetType);
$oTarget->setTargetId($sTargetId ? $sTargetId : null);
$oTarget->setTargetTmp($sTargetTmp ? $sTargetTmp : null);
if ($oTarget->Add()) {
return $oMedia;
}
}
return false;
}
/**
* Создает набор отресайзанных изображений
* Варианты наименований результирующих файлов в зависимости от размеров:
* file_100x150 - w=100 h=150 crop=false
* file_100x150crop - w=100 h=150 crop=true
* file_x150 - w=null h=150 crop=false
* file_100x - w=100 h=null crop=false
*
* @param $sFileSource
* @param $sDirDist
* @param $sFileName
* @param $aSizes
* @param null $aParams
*/
public function GenerateImageBySizes($sFileSource,$sDirDist,$sFileName,$aSizes,$aParams=null) {
if (!$aSizes) {
return;
}
/**
* Преобразуем упрощенную запись списка размеров в полную
*/
foreach($aSizes as $k=>$v) {
if (is_numeric($v)) {
$aSizes[$k]=array(
'w' => $v,
'h' => $v,
'crop' => true,
);
}
}
foreach ($aSizes as $aSize) {
/**
* Для каждого указанного в конфиге размера генерируем картинку
*/
$sNewFileName = $sFileName.'_'.$aSize['w'].'x'.$aSize['h'];
if($oImage=$this->Image_Open($sFileSource,$aParams)) {
if ($aSize['crop']) {
$oImage->cropProportion($aSize['w']/$aSize['h'],'center');
$sNewFileName .= 'crop';
}
if (!$oImage->resize($aSize['w'],$aSize['h'],true)->saveSmart($sDirDist,$sNewFileName)) {
// TODO: прерывать и возвращать false?
}
}
}
}
public function RemoveImageBySizes($sPath,$aSizes,$bRemoveOriginal=true) {
if ($aSizes) {
/**
* Преобразуем упрощенную запись списка размеров в полную
*/
foreach($aSizes as $k=>$v) {
if (is_numeric($v)) {
$aSizes[$k]=array(
'w' => $v,
'h' => $v,
'crop' => true,
);
}
}
foreach($aSizes as $aSize) {
$sSize = $aSize['w'].'x'.$aSize['h'];
if ($aSize['crop']) {
$sSize.='crop';
}
$this->Image_RemoveFile($this->GetImagePathBySize($sPath,$sSize));
}
}
/**
* Удаляем оригинал
*/
if ($bRemoveOriginal) {
$this->Image_RemoveFile($sPath);
}
}
/**
* Возвращает каталог для сохранения контента медиа
*
* @param string $sTargetType
* @param string|null $sTargetId Желательно для одного типа при формировании каталога для загрузки выбрать что-то одно - использовать $sTargetId или нет
*
* @return string
*/
public function GetSaveDir($sTargetType,$sTargetId=null) {
return Config::Get('path.uploads.base')."/media/{$sTargetType}/".date('Y/m/d/H/');
}
public function BuildCodeForEditor($oMedia,$aParams) {
$sCode='';
if ($oMedia->getType()==self::TYPE_IMAGE) {
$aSizes=(array)$oMedia->getDataOne('image_sizes');
$sSizeParam=isset($aParams['size']) ? (string)$aParams['size'] : '';
$sSize='original';
$bNeedHref=false;
/**
* Проверяем корректность размера
*/
foreach($aSizes as $aSizeAllow) {
$sSizeKey=$aSizeAllow['w'].'x'.$aSizeAllow['h'].($aSizeAllow['crop'] ? 'crop' : '');
if ($sSizeKey==$sSizeParam) {
$sSize=$sSizeKey;
/**
* Необходимость лайтбокса
*/
if ($aSizeAllow['w']<$oMedia->getWidth()) {
$bNeedHref=true;
}
}
}
$sPath=$oMedia->getFileWebPath($sSize=='original' ? null : $sSize);
$sCode='getDataOne('title');
}
if (!isset($aParams['skip_title']) and $aParams['title']) {
$sCode.=' title="'.htmlspecialchars($aParams['title']).'" ';
$sCode.=' alt="'.htmlspecialchars($aParams['title']).'" ';
}
if (isset($aParams['align']) and in_array($aParams['align'],array('left','right','center'))) {
if ($aParams['align'] == 'center') {
$sCode.=' class="image-center"';
} else {
$sCode.=' align="'.htmlspecialchars($aParams['align']).'" ';
}
}
$sDataParams='';
if (isset($aParams['data']) and is_array($aParams['data'])) {
foreach($aParams['data'] as $sDataName=>$sDataValue) {
if ($sDataValue) {
$sDataParams.=' data-'.$sDataName.'="'.htmlspecialchars($sDataValue).'"';
}
}
}
if ($bNeedHref) {
$sCode.=' />';
$sLbxGroup='';
if (isset($aParams['lbx_group'])) {
$sLbxGroup=' data-rel="'.htmlspecialchars($aParams['lbx_group']).'"';
}
$sCode=''.$sCode.'';
} else {
$sCode.=$sDataParams.' />';
}
}
return $sCode;
}
public function BuildHTML($sPath,$aParams) {
$sText='
';
return $sText;
}
public function GetMediaByTarget($sTargetType,$iTargetId,$iUserId=null) {
return $this->oMapper->GetMediaByTarget($sTargetType,$iTargetId,$iUserId);
}
public function GetMediaByTargetTmp($sTargetTmp,$iUserId=null) {
return $this->oMapper->GetMediaByTargetTmp($sTargetTmp,$iUserId);
}
/**
* Выполняет удаление файлов медиа-объекта
*
* @param $oMedia
*/
public function DeleteFiles($oMedia) {
/**
* Сначала удаляем все файлы
*/
if ($oMedia->getType()==self::TYPE_IMAGE) {
$aSizes=$oMedia->getDataOne('image_sizes');
$this->RemoveImageBySizes($oMedia->getFilePath(),$aSizes);
}
}
/**
* Возвращает список media с учетов прав доступа текущего пользователя
*
* @param array $aId
*
* @return array
*/
public function GetAllowMediaItemsById($aId) {
$aIdItems=array();
foreach((array)$aId as $iId) {
$aIdItems[]=(int)$iId;
}
if (is_array($aIdItems) and count($aIdItems)) {
$iUserId=$this->oUserCurrent ? $this->oUserCurrent->getId() : null;
return $this->Media_GetMediaItemsByFilter(array(
'#where'=>array('id in (?a) AND ( user_id is null OR user_id = ?d )'=>array($aIdItems,$iUserId))
)
);
}
return array();
}
/**
* Обработка тега gallery в тексте
*
** * @param string $sTag Тег на ктором сработал колбэк * @param array $aParams Список параметров тега * @return string */ public function CallbackParserTagGallery($sTag,$aParams) { if (isset($aParams['items'])) { $aItems=explode(',',$aParams['items']); } if (!(isset($aItems) and $aMediaItems=$this->Media_GetAllowMediaItemsById($aItems))) { return ''; } $aParamsMedia=array( 'size'=>'100crop', 'skip_title'=>true ); $sProperties=''; if (isset($aParams['nav']) and in_array($aParams['nav'],array('thumbs'))) { $sProperties.=' data-nav="'.$aParams['nav'].'" '; } $sTextResult='*