array(
* 'type' => 'files', // this is new form type, which enable upholders automatically
* 'storage_object' => 'sample', // the storage object, where uploaded files are going to be saved
* 'images_transcoder' => 'sample2', // images transcoder object to use for images preview
* 'uploaders' => array ('sys_simple', 'sys_html5'), // the set of uploaders to use to upload files
* 'upload_buttons_titles' => array('Simple' => 'Upload one by one', 'HTML5' => 'Upload several files in bulk'); // change default button titles, array with button names, or string to assign to all bnuttons
* 'multiple' => true, // allow to upload multiple files per one upload
* 'storage_private' => 0, // private or public storage (by default - private), if file is provate generated link will expire, for public storage the link is always persistent
* 'content_id' => 4321, // content id to associate ghost files with
* 'ghost_template' => $mixedGhostTemplate, // template for nested form
* 'name' => 'attachment', // name of file form field, resulted file id is assigned to this field name
* 'caption' => _t('Attachments'), // form field caption
* ),
* @endcode
*
*
* Available uploaders:
* - sys_simple - upload files using standard HTML forms.
* - sys_html5 - upload files using AJAX uploader with multiple files selection support (without flash),
* it works in Firefox and WebKit(Safari, Chrome) browsers only, but has fallback for other browsers (IE, Opera).
*
*
* Uploaded files are showed as "nested" forms.
* You can pass nested form in 'ghost_template' parameter.
* If you don't pass anything in 'ghost_template' parameter, then only file id is passed upon form submission.
* The nested form can be declared using the different ways:
*
*
* 1. Pass template as string - just plain string with HTML, for example:
*
* @code
*
* @endcode
*
*
* 2. Pass form array - regular form array, but with inputs array only, for example:
*
* @code
* array (
* 'inputs' => array (
* 'file_name' => array (
* 'type' => 'text',
* 'name' => 'file_name[]',
* 'value' => '{file_title}',
* 'caption' => _t('Caption'),
* ),
* 'file_desc' => array (
* 'type' => 'textarea',
* 'name' => 'file_desc[]',
* 'caption' => _t('Description'),
* ),
* ),
* );
* @endcode
*
* Array is automatically modified to add necessary form attributes to work as nested form,
* file id field is added automatically as hidden input as well.
*
*
* 3. Pass instance of BxDolFormNestedGhost class - use BxDolFormNestedGhost class or its custom subclass;
* to create instance use the same form array as in the previous variant, for example:
*
* @code
* $oFormNested = new BxDolFormNestedGhost('attachment', $aFormNested, 'do_submit');
* @endcode
*
* - 'attachment' is the name of file form field from main form.
* - $aFormNested is form array from previous example.
* - 'do_submit' is main form submit_name; field name of submit form input to determine if form is submitted or not.
*
*
* All 3 variants can have the following replace markers to substitute with real values:
* - {file_id} - uploaded file id
* - {file_name} - uploaded file name with extension
* - {file_title} - uploaded file name without extension
* - {file_icon} - URL to file icon automatically determined by file extension
* - {file_url} - URL to the original file
* - {js_instance_name} - instance of BxDolUploader javascript class
*
*/
abstract class BxDolUploader extends BxDolFactory
{
protected $_bIsApi;
protected $_oTemplate;
protected $_iId; ///< object ID
protected $_aObject; ///< object properties
protected $_sStorageObject; ///< storage object name
protected $_bMultiple;
protected $_sUniqId; ///< uniq id used to generate UploaderJsInstance, ResultContainerId, UploadInProgressContainerId and PopupContainerId
protected $_sUploaderJsInstance; ///< uplooader js object instance name
protected $_sUploadInProgressContainerId; ///< container id where upload in progress element resides
protected $_sPopupContainerId; ///< popup container id
protected $_sResultContainerId; ///< uploading/uploaded objects container id
protected $_sErrorsContainerId;
protected $_sFormContainerId;
protected $_sUploadErrorMessages; ///< upload error message
protected $_sButtonTemplate; ///< template name for displaying upload button
protected $_sJsTemplate; ///< template name for displaying upload JS
protected $_sUploaderFormTemplate; ///< template name for displaying uploader form
protected $_aJs;
protected $_aCss;
/**
* constructor
*/
protected function __construct($aObject, $sStorageObject, $sUniqId, $oTemplate)
{
parent::__construct();
$this->_bIsApi = bx_is_api();
$this->_oTemplate = $oTemplate ? $oTemplate : BxDolTemplate::getInstance();
$this->_iId = (int)$aObject['id'];
$this->_aObject = $aObject;
$this->_sStorageObject = $sStorageObject;
$this->_bMultiple = true;
$this->_sUniqId = $sUniqId;
$this->_sUploaderJsInstance = 'glUploader_' . $sUniqId . '_' . $this->_aObject['object'];
$this->_sUploadInProgressContainerId = 'bx-form-input-files-' . $sUniqId . '-upload-in-progress-' . $this->_aObject['object'];
$this->_sPopupContainerId = 'bx-form-input-files-' . $sUniqId . '-popup-wrapper-' . $this->_aObject['object'];
$this->_sResultContainerId = 'bx-form-input-files-' . $sUniqId . '-upload-result';
$this->_sErrorsContainerId = 'bx-form-input-files-' . $sUniqId . '-errors';
$this->_sFormContainerId = 'bx-form-input-files-' . $sUniqId . '-form-cont';
$this->_aJs = ['BxDolUploader.js'];
$this->_aCss = ['uploaders.css'];
}
static public function getObjectInstance($sObject, $sStorageObject, $sResultContainerId, $oTemplate = false)
{
$aObject = BxDolUploaderQuery::getUploaderObject($sObject);
if (!$aObject || !is_array($aObject) || !$aObject['active'])
return false;
$sClass = $aObject['override_class_name'];
if (!empty($aObject['override_class_file']))
require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
$o = new $sClass($aObject, $sStorageObject, $sResultContainerId, $oTemplate);
if (!$o->isInstalled() || !$o->isAvailable())
return false;
return $o;
}
/**
* Is uploader available?
* @return boolean
*/
public function isAvailable()
{
return $this->_aObject['active'] ? true : false;
}
/**
* Are required php modules installed for this uploader ?
* @return boolean
*/
public function isInstalled()
{
return true;
}
public function isMultiple()
{
return $this->_bMultiple;
}
public function setMultiple($bMultiple)
{
$this->_bMultiple = (bool)$bMultiple;
}
public function getNameJsInstanceUploader()
{
return $this->_sUploaderJsInstance;
}
public function getIdContainerResult()
{
return $this->_sResultContainerId;
}
public function getIdContainerUploadInProgress()
{
return $this->_sUploadInProgressContainerId;
}
public function getIdContainerPopup()
{
return $this->_sPopupContainerId;
}
public function getIdContainerErrors()
{
return $this->_sErrorsContainerId;
}
/**
* Handle uploads here.
* @param $mixedFiles as usual $_FILES['some_name'] array, but maybe some other params depending on the uploader
* @return nothing, but if some files failed to upload, the actual error message can be determined by calling BxDolUploader::getUploadErrorMessages()
*/
public function handleUploads ($iProfileId, $mixedFiles, $isMultiple = true, $iContentId = false, $bPrivate = true)
{
$oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
if(!$isMultiple)
$this->deleteGhostsForProfile($iProfileId, [$iContentId, $this->_iId]);
if($this->_bIsApi && $mixedFiles) {
if(($iId = $oStorage->storeFileFromForm($mixedFiles, $bPrivate, $iProfileId, $iContentId)))
$oStorage->updateGhostsUploaderId($iId, $this->_iId);
return [
'success' => 1,
'id' => $iId
];
}
if(($aMultipleFiles = $oStorage->convertMultipleFilesArray($mixedFiles)) === false)
$aMultipleFiles = [$mixedFiles];
foreach($aMultipleFiles as $aFile) {
if(($iId = $oStorage->storeFileFromForm($aFile, $bPrivate, $iProfileId, $iContentId)))
$oStorage->updateGhostsUploaderId($iId, $this->_iId);
else
$this->appendUploadErrorMessage(_t('_sys_uploader_err_msg', $aFile['name'], $oStorage->getErrorString()));
if(!$isMultiple)
break;
}
echo '';
}
public function getUploadErrorMessages ($sFormat = 'HTML')
{
if (!$this->_sUploadErrorMessages)
return '';
if ('HTML' == $sFormat)
return nl2br($this->_sUploadErrorMessages);
else
return $this->_sUploadErrorMessages;
}
/**
* Show uploader button.
* @return HTML string
*/
public function getUploaderButton($aParams = array())
{
return $this->_oTemplate->parseHtmlByName($this->_sButtonTemplate, $aParams);
}
public function getUploaderJsParams()
{
return [];
}
/**
* Show uploader JS.
* @return HTML string
*/
public function getUploaderJs($mixedGhostTemplate, $isMultiple = true, $aParams = array(), $bDynamic = false)
{
$sJsValue = '';
if(is_array($mixedGhostTemplate))
$sJsValue = json_encode($mixedGhostTemplate);
else
$sJsValue = "'" . bx_js_string($mixedGhostTemplate, BX_ESCAPE_STR_APOS) . "'";
$sJsObject = $this->getNameJsInstanceUploader();
$sJsCode = $this->_oTemplate->parseHtmlByName($this->_sJsTemplate, array_merge([
'uploader_instance_name' => $sJsObject,
'engine' => $this->_aObject['object'],
'storage_object' => $this->_sStorageObject,
'images_transcoder' => '',
'uniq_id' => $this->_sUniqId,
'template_ghost' => $sJsValue,
'multiple' => $isMultiple ? 1 : 0,
'latest' => 0, //--- Return the latest one uploaded ghost only.
'storage_private' => isset($aParams['storage_private']) ? $aParams['storage_private'] : 1,
'is_init_reordering' => isset($aParams['is_init_reordering']) ? $aParams['is_init_reordering'] : 0,
'bx_if:restore_ghosts' => [
'condition' => isset($aParams['is_init_ghosts']) ? $aParams['is_init_ghosts'] : 1,
'content' => [
'uploader_instance_name' => $sJsObject,
'is_init_reordering' => isset($aParams['is_init_reordering']) ? $aParams['is_init_reordering'] : 0,
]
],
'on_upload_before' => 'false',
'on_upload' => 'false',
'on_restore_ghosts' => 'false'
], $aParams));
if(!$bDynamic) {
$this->_oTemplate->addJs($this->_aJs);
$sJsCode = $this->_oTemplate->addJsCodeOnLoadWrapped($sJsCode);
}
else
$sJsCode = $this->_oTemplate->addJsPreloadedWrapped($this->_aJs, $sJsCode);
return $this->addCssJs($bDynamic) . $sJsCode;
}
/**
* add necessary js, css files and js translations
*/
public function addCssJs($bDynamic = false)
{
$s = '';
$s .= $this->_oTemplate->addCss($this->_aCss, $bDynamic);
$s .= $this->_oTemplate->addJsTranslation([
'_sys_uploader_confirm_leaving_page',
'_sys_uploader_confirm_close_popup',
'_sys_uploader_upload_canceled',
'_sys_uploader_image_reposition_info',
], $bDynamic);
return $bDynamic ? $s : '';
}
public function addJs($mixedFile)
{
if(!is_array($mixedFile))
$mixedFile = [$mixedFile];
foreach($mixedFile as $sFile)
if(!in_array($sFile, $this->_aJs))
$this->_aJs[] = $sFile;
}
public function addCss($mixedFile)
{
if(!is_array($mixedFile))
$mixedFile = [$mixedFile];
foreach($mixedFile as $sFile)
if(!in_array($sFile, $this->_aCss))
$this->_aCss[] = $sFile;
}
/**
* Get uploader button title
*/
public function getUploaderButtonTitle($mixed = false)
{
// it is overrided in child classes
}
/**
* Show uploader form.
* @return HTML string
*/
public function getUploaderForm($isMultiple = true, $iContentId = false, $isPrivate = true)
{
// it is overrided in child classes
}
/**
* Display uploaded, but not saved files - ghosts
* @param $iProfileId - profile id to get orphaned files from
* @param $sFormat - output format, only 'json' output formt is supported
* @param $sImagesTranscoder - transcoder object for files preview for images and videos, false by default - no preview
* @param $mixedContent - int Content ID | array Content ID + Uploader ID | false to not consider content id at all
* @return JSON string
*/
public function getGhosts($iProfileId, $sFormat, $sImagesTranscoder = false, $mixedContent = false)
{
$iContent = (int)(is_array($mixedContent) ? reset($mixedContent) : $mixedContent);
$oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
$oImagesTranscoder = false;
if($sImagesTranscoder)
$oImagesTranscoder = BxDolTranscoderImage::getObjectInstance($sImagesTranscoder);
$a = [];
$aGhosts = $oStorage->getGhosts($this->isAdmin($iContent) && $iContent ? false : $iProfileId, $mixedContent);
foreach($aGhosts as $aFile) {
$sFileIcon = '';
if($this->isUseTranscoderForPreview($oImagesTranscoder, $aFile))
$sFileIcon = $oImagesTranscoder->getFileUrl($aFile['id']);
if(!$sFileIcon)
$sFileIcon = $this->_oTemplate->getIconUrl($oStorage->getIconNameByFileName($aFile['file_name']));
$a[$aFile['id']] = array_merge([
'storage_object' => $this->_sStorageObject,
'uploader_id' => $aFile['uploader_id'] ?? 0,
'file_id' => $aFile['id'],
'file_type' => $aFile['mime_type'],
'file_name' => $aFile['file_name'],
'file_title' => $oStorage->getFileTitle($aFile['file_name']),
'file_icon' => $sFileIcon,
'file_url' => $oStorage->getFileUrlById($aFile['id']),
'file_remote_id' => $aFile['remote_id'],
'file_created' => $aFile['created'],
'js_instance_name' => $this->_sUploaderJsInstance,
], $this->getGhostTemplateVars($aFile, $iProfileId, $iContent, $oStorage, $oImagesTranscoder));
}
if('array' == $sFormat) {
return $a;
}
else if('json' == $sFormat) {
return json_encode($a);
}
else { // html format is not suported for this data type
return false;
}
}
public function getGhostsWithOrder($iProfileId, $sFormat, $sImagesTranscoder = false, $mixedContent = false, $isLatestOnly = false)
{
$a = $this->getGhosts($iProfileId, 'array', $sImagesTranscoder, $mixedContent);
if($isLatestOnly)
$a = array_slice($a, 0, 1, true);
if(!empty($a) && is_array($a))
$a = ['g' => $a, 'o' => array_keys($a)];
if ('json' == $sFormat) {
return json_encode($a);
} else { // html format is not suported for this data type
return $a;
}
}
/**
* Reorder uploaded ghosts.
* @param $iProfileId - profile id to get orphaned files from
* @param $sFormat - output format, only 'json' output formt is supported
* @param $aGhosts - an array of ordered ghosts' IDs.
* @param $iContentId - content id to order orphaned files for, false by default
* @return JSON string
*/
public function reorderGhosts($iProfileId, $sFormat, $aGhosts, $iContentId = false)
{
$bResult = true;
if(($oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject)) !== false)
$bResult = $oStorage->reorderGhosts($this->isAdmin($iContentId) && $iContentId ? false : $iProfileId, $iContentId, $aGhosts);
if($sFormat == 'json')
return json_encode($bResult ? [] : ['msg' => _t('_error occured')]);
else
return $bResult;
}
/**
* Delete file by file id, usually ghost file
* @return 'ok' string on success or error string on error
*/
public function deleteGhost($iFileId, $iProfileId)
{
$oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
$aFile = $oStorage->getGhost ($iFileId);
if (!$aFile)
$aFile = $oStorage->getFile ($iFileId);
if (!$aFile)
return _t('_error occured');
$oProfile = BxDolProfile::getInstance($iProfileId);
$oAccount = $oProfile ? $oProfile->getAccountObject() : null;
$aProfiles = $oAccount ? $oAccount->getProfiles(false) : array();
if (!isset($aProfiles[$aFile['profile_id']]) && !$this->isAdmin($aFile['content_id']))
return _t('_sys_txt_access_denied');
if (!$oStorage->deleteFile($iFileId))
return $oStorage->getErrorString();
return 'ok';
}
/**
* Delete all ghosts files for the specified profile
* @param $iProfileId - profile id to get orphaned files from
* @param $mixedContent - int Content ID | array Content ID + Uploader ID | false
* @return number of delete ghost files
*/
public function deleteGhostsForProfile($iProfileId, $mixedContent = false)
{
$iCount = 0;
$oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
if($oStorage === false)
return $iCount;
$aGhosts = $oStorage->getGhosts($iProfileId, $mixedContent, $mixedContent ? true : false);
foreach ($aGhosts as $aFile)
$iCount += $oStorage->deleteFile($aFile['id']);
return $iCount;
}
protected function cleanUploadErrorMessages ()
{
$this->_sUploadErrorMessages = '';
}
public function appendUploadErrorMessage ($s)
{
$this->_sUploadErrorMessages .= ($this->_sUploadErrorMessages ? "\n" : '') . $s;
}
protected function getRestrictionsText ()
{
$sTextRestrictions = '';
$oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
if (!$oStorage)
return '';
$a = $oStorage->getRestrictionsTextArray(bx_get_logged_profile_id());
foreach ($a as $s)
$sTextRestrictions .= '' . $s . '
';
return $sTextRestrictions;
}
protected function getMaxUploadFileSize ()
{
$oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
if (!$oStorage)
return 0;
return $oStorage->getMaxUploadFileSize(bx_get_logged_profile_id());
}
protected function getAcceptedFilesExtensions ()
{
$oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject);
if (!$oStorage)
return null;
return $oStorage->getAllowedExtensions();
}
protected function getGhostTemplateVars($aFile, $iProfileId, $iContentId, $oStorage, $oImagesTranscoder)
{
return array();
}
protected function isUseTranscoderForPreview($oImagesTranscoder, $aFile)
{
if (!$oImagesTranscoder)
return false;
return $oImagesTranscoder->isMimeTypeSupported($aFile['mime_type']);
}
protected function isAdmin ($iContentId = 0)
{
return isAdmin();
}
}
/** @} */