Просмотр исходного кода

Ticket #5291 - Improve uploader and storage collaboration. P3.

AntonLV 10 месяцев назад
Родитель
Сommit
8c17994d71

+ 7 - 2
inc/classes/BxDolStorage.php

@@ -246,6 +246,11 @@ abstract class BxDolStorage extends BxDolFactory implements iBxDolFactoryObject
         return ($GLOBALS['bxDolClasses']['BxDolStorage!'.$sObject] = $o);
     }
 
+    public static function getGhostLifetime()
+    {
+        return ($iLifetime = getParam('sys_storage_ghost_lifetime')) !== false ? (int)$iLifetime * 60 : BX_DOL_STORAGE_GHOST_LIFETIME;
+    }
+
     /**
      * Delete old security tokens from database.
      * It is alutomatically called upin cron execution, usually once in a day.
@@ -279,14 +284,14 @@ abstract class BxDolStorage extends BxDolFactory implements iBxDolFactoryObject
 
         return $iDeleted;
     }
-    
+
     /**
      * Delete outdated ghosts
      * It is alutomatically called upon cron execution, usually one time per minute.
      */
     public static function pruneGhosts()
     {
-        $iLifetime = ($iLifetime = getParam('sys_storage_ghost_lifetime')) !== false ? (int)$iLifetime * 60 : BX_DOL_STORAGE_GHOST_LIFETIME;
+        $iLifetime = self::getGhostLifetime();
         if($iLifetime == 0)
             return;
 

+ 1 - 1
inc/classes/BxDolStorageQuery.php

@@ -354,7 +354,7 @@ class BxDolStorageQuery extends BxDolDb
                 $sOnProfile = " AND `g`.`profile_id` = :profile_id ";                
             }
 
-            $sSelect .= ", `g`.`uploader_id`";
+            $sSelect .= ", `g`.`uploader_id`, `g`.`created`";
             $sJoin .= " INNER JOIN `sys_storage_ghosts` AS `g` ON (`f`.`id` = `g`.`id` AND `g`.`object` = :object " . $sOnProfile;
             if ($mixedContent !== false) {
                 if(!is_array($mixedContent)) {

+ 13 - 0
inc/classes/BxDolUploader.php

@@ -107,6 +107,7 @@ abstract class BxDolUploader extends BxDolFactory
     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
@@ -138,6 +139,7 @@ abstract class BxDolUploader extends BxDolFactory
         $this->_iId = (int)$aObject['id'];
         $this->_aObject = $aObject;
         $this->_sStorageObject = $sStorageObject;
+        $this->_bMultiple = true;
 
         $this->_sUniqId = $sUniqId;
 
@@ -189,6 +191,16 @@ abstract class BxDolUploader extends BxDolFactory
         return true;
     }
 
+    public function isMultiple()
+    {
+        return $this->_bMultiple;
+    }
+
+    public function setMultiple($bMultiple)
+    {
+        $this->_bMultiple = (bool)$bMultiple;
+    }
+
     public function getNameJsInstanceUploader()
     {
         return $this->_sUploaderJsInstance;
@@ -414,6 +426,7 @@ abstract class BxDolUploader extends BxDolFactory
                 '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));
         }

+ 1 - 1
inc/js/classes/BxDolUploader.js

@@ -149,7 +149,7 @@ BxDolUploaderBase.prototype.cancelAll = function () {
 }
 
 BxDolUploaderBase.prototype.restoreGhosts = function (bInitReordering, onComplete) {
-    var sUrl = this._getUrlWithStandardParams() + '&img_trans=' + this._sImagesTranscoder + '&a=restore_ghosts&f=json' + '&c=' + this._iContentId + '&l=' + (this._isLatest ? 1 : 0) + '&_t=' + escape(new Date());
+    var sUrl = this._getUrlWithStandardParams() + '&img_trans=' + this._sImagesTranscoder + '&a=restore_ghosts&f=json' + '&c=' + this._iContentId + '&m=' + (this._isMultiple ? 1 : 0) + '&l=' + (this._isLatest ? 1 : 0) + '&_t=' + escape(new Date());
     var $this = this;
 
     bInitReordering = bInitReordering !== undefined ? bInitReordering : this._isReordering;

+ 1 - 1
install/sql/system.sql

@@ -685,7 +685,7 @@ INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `ex
 (@iCategoryId, 'sys_storage_s3_amz_iam_role', '_adm_stg_cpt_option_sys_storage_s3_amz_iam_role', '', 'checkbox', '', '', '', 10),
 (@iCategoryId, 'sys_storage_s3_acl_enable', '_adm_stg_cpt_option_sys_storage_s3_acl_enable', 'on', 'checkbox', '', '', '', 12),
 (@iCategoryId, 'sys_storage_s3_force_auth_urls', '_adm_stg_cpt_option_sys_storage_s3_force_auth_urls', '', 'digit', '', '', '', 14), 
-(@iCategoryId, 'sys_storage_ghost_lifetime', '_adm_stg_cpt_option_sys_storage_ghost_lifetime', '5', 'digit', '', '', '', 20);
+(@iCategoryId, 'sys_storage_ghost_lifetime', '_adm_stg_cpt_option_sys_storage_ghost_lifetime', '60', 'digit', '', '', '', 20);
 
 --
 -- CATEGORY: Account

+ 25 - 13
modules/base/general/classes/BxBaseModGeneralFormEntry.php

@@ -391,7 +391,7 @@ class BxBaseModGeneralFormEntry extends BxTemplFormView
      */
     public function isTrackFieldChanged($sField, $bReturnValues = false)
     {
-        if(!isset($this->_aTrackFieldsChanges[$sField]) || $this->_aTrackFieldsChanges[$sField] === false)
+        if(!isset($this->_aTrackFieldsChanges[$sField]) || $this->_aTrackFieldsChanges[$sField]['result'] === false)
             return false;
 
         return $bReturnValues ? $this->_aTrackFieldsChanges[$sField] : true;
@@ -841,9 +841,7 @@ class BxBaseModGeneralFormEntry extends BxTemplFormView
 
     public function processFiles ($sFieldFile, $iContentId = 0, $isAssociateWithContent = false)
     {
-        $CNF = &$this->_oModule->_oConfig->CNF;
-
-        if (!isset($this->aInputs[$sFieldFile]))
+        if(!isset($this->aInputs[$sFieldFile]))
             return true;
 
         $mixedFileIds = $this->getCleanValue($sFieldFile);
@@ -884,9 +882,20 @@ class BxBaseModGeneralFormEntry extends BxTemplFormView
                 }
         }
 
+        if($iContentId)
+            $this->_processTrackFields($iContentId);
+
         return true;
     }
 
+    public function processFileDeletion ($sFieldFile, $iFileId)
+    {
+        if(!isset($this->aInputs[$sFieldFile], $this->aInputs[$sFieldFile]['storage_object']))
+            return false;
+
+        return $this->_deleteFile($iFileId, $this->aInputs[$sFieldFile]['storage_object']);
+    }
+
     public function processPolls ($sFieldPoll, $iContentId = 0)
     {
         $CNF = &$this->_oModule->_oConfig->CNF;
@@ -919,15 +928,14 @@ class BxBaseModGeneralFormEntry extends BxTemplFormView
     {
         $CNF = &$this->_oModule->_oConfig->CNF;
 
-        if (!$iFileId)
+        if(!$iFileId)
             return true;
 
-		$sStorage = !empty($sStorage) ? $sStorage : $CNF['OBJECT_STORAGE'];
-		$oStorage = BxDolStorage::getObjectInstance($sStorage);
-        if (!$oStorage)
+        $oStorage = BxDolStorage::getObjectInstance($sStorage ?: $CNF['OBJECT_STORAGE']);
+        if(!$oStorage)
             return false;
 
-        if (!$oStorage->getFile($iFileId))
+        if(!$oStorage->getFile($iFileId))
             return true;
 
         $iProfileId = bx_get_logged_profile_id();
@@ -1007,16 +1015,20 @@ class BxBaseModGeneralFormEntry extends BxTemplFormView
             return;
 
         if(!is_array($mixedContent)) {
-            $mixedContent = $this->_oModule->_oDb->getContentInfoById((int)$mixedContent);
+            $mixedContent = $this->_oModule->_oDb->getEntriesBy(['type' => 'id', 'id' => (int)$mixedContent]);
             if(empty($mixedContent) || !is_array($mixedContent))
                 return;
         }
 
         foreach($this->_aTrackFieldsChanges as $sField => $aValues)
-            if($mixedContent[$sField] == $aValues['old'])
-                $this->_aTrackFieldsChanges[$sField] = false;
+            if($mixedContent[$sField] != $aValues['old'])
+                $this->_aTrackFieldsChanges[$sField] = array_merge($this->_aTrackFieldsChanges[$sField], [
+                    'new' => $mixedContent[$sField],
+                    'result' => true
+                ]);
             else
-                $this->_aTrackFieldsChanges[$sField]['new'] = $mixedContent[$sField];
+                $this->_aTrackFieldsChanges[$sField]['result'] = false;
+                
     }
 
     protected function _processMetas(&$aValsToAdd)

+ 17 - 15
modules/base/profile/classes/BxBaseModProfileFormsEntryHelper.php

@@ -132,19 +132,15 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
             $aTrackTextFieldsChanges = array ();
         }
 
-        if(isset($CNF['FIELD_BIRTHDAY']) && isset($aContentInfo[$CNF['FIELD_BIRTHDAY']]))
-            $oForm->addTrackFields($CNF['FIELD_BIRTHDAY'], $aContentInfo);
-        if(isset($CNF['FIELD_TITLE']) && isset($aContentInfo[$CNF['FIELD_TITLE']]))
-            $oForm->addTrackFields($CNF['FIELD_TITLE'], $aContentInfo);
-        if(isset($CNF['FIELD_PICTURE']) && isset($aContentInfo[$CNF['FIELD_PICTURE']]))
-            $oForm->addTrackFields($CNF['FIELD_PICTURE'], $aContentInfo);
+        foreach(['TITLE', 'BIRTHDAY', 'PICTURE', 'COVER', 'BADGE'] as $sIt)
+            if(($sField = 'FIELD_' . $sIt) && isset($CNF[$sField], $aContentInfo[$CNF[$sField]]))
+                $oForm->addTrackFields($CNF[$sField], $aContentInfo);
     }
 
     public function onDataEditAfter ($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)
     {
-        if ($s = parent::onDataEditAfter($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm)){
+        if($s = parent::onDataEditAfter($iContentId, $aContentInfo, $aTrackTextFieldsChanges, $oProfile, $oForm))
             return $s;
-        }
 
         $CNF = &$this->_oModule->_oConfig->CNF;
 
@@ -157,16 +153,20 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
         $oEditedProfile = BxDolProfile::getInstanceMagic($aContentInfo['profile_id']);
         $sStatus = $oEditedProfile->getStatus();
 
-        if (!$this->isAutoApproval(BX_DOL_PROFILE_ACTIVATE_EDIT) && BX_PROFILE_STATUS_ACTIVE == $sStatus && !empty($aTrackTextFieldsChanges['changed_fields']))
+        if(!$this->isAutoApproval(BX_DOL_PROFILE_ACTIVATE_EDIT) && BX_PROFILE_STATUS_ACTIVE == $sStatus && !empty($aTrackTextFieldsChanges['changed_fields']))
             $oEditedProfile->disapprove(BX_PROFILE_ACTION_AUTO, 0, $this->_oModule->serviceActAsProfile());
 
         // process uploaded files
-        if (isset($CNF['FIELD_PICTURE']))
-            $oForm->processFiles($CNF['FIELD_PICTURE'], $iContentId, false);
-        if (isset($CNF['FIELD_COVER']))
-            $oForm->processFiles($CNF['FIELD_COVER'], $iContentId, false);
-        if (isset($CNF['FIELD_BADGE']))
-            $oForm->processFiles($CNF['FIELD_BADGE'], $iContentId, false);
+        foreach(['PICTURE', 'COVER', 'BADGE'] as $sIt) {
+            $sField = 'FIELD_' . $sIt;
+            if(!isset($CNF[$sField]))
+                continue;
+
+            $oForm->processFiles($CNF[$sField], $iContentId, false);
+
+            if(($aChanged = $oForm->isTrackFieldChanged($CNF[$sField], true)) !== false)
+                $oForm->processFileDeletion($CNF[$sField], $aChanged['old']);
+        }
 
         if(isset($CNF['FIELD_ALLOW_POST_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false)
             $oPrivacy->reassociateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]);
@@ -226,6 +226,8 @@ class BxBaseModProfileFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
             $oForm->processFiles($CNF['FIELD_PICTURE'], $iContentId, true);
         if (isset($CNF['FIELD_COVER']))
             $oForm->processFiles($CNF['FIELD_COVER'], $iContentId, true);
+        if (isset($CNF['FIELD_BADGE']))
+            $oForm->processFiles($CNF['FIELD_BADGE'], $iContentId, true);
 
         if(isset($CNF['FIELD_ALLOW_POST_TO']) && !empty($aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]) && ($oPrivacy = BxDolPrivacy::getObjectInstance($CNF['OBJECT_PRIVACY_POST'])) !== false)
             $oPrivacy->associateGroupCustomWithContent($oProfile->id(), $iContentId, (int)$aContentInfo[$CNF['FIELD_ALLOW_POST_TO']]);

+ 41 - 18
modules/base/profile/classes/BxBaseModProfileUploaderCrop.php

@@ -41,8 +41,8 @@ class BxBaseModProfileUploaderCrop extends BxTemplUploaderCrop
 
         $aGhosts = $oStorage->getGhosts($iProfileId, $mixedContent, $iContentId ? true : false);
         foreach ($aGhosts as $aFile) {
-            // for requested image type delete only unassigned ghosts and currently set images
-            if ($aFile['id'] == $aContentInfo[$this->_sImage] || !in_array($aFile['id'], array_map(function($sField) use ($aContentInfo) {
+            // for requested image type delete only unassigned ghosts and currently set image will be deleted while saving
+            if($aFile['id'] != $aContentInfo[$this->_sImage] && !in_array($aFile['id'], array_map(function($sField) use ($aContentInfo) {
                 return $aContentInfo[$sField];
             }, $this->_aOtherImages)))
                 $iCount += $oStorage->deleteFile($aFile['id']);
@@ -54,38 +54,61 @@ class BxBaseModProfileUploaderCrop extends BxTemplUploaderCrop
     public function getGhosts($iProfileId, $sFormat, $sImagesTranscoder = false, $iContentId = false)
     {
         $s = parent::getGhosts($iProfileId, $sFormat, $sImagesTranscoder, $iContentId);
-        if (!$s || !$iContentId) // if we're creating new profile return all ghosts
+        if(!$s || !$iContentId) // if we're creating new profile return all ghosts
             return $s;
 
-        $a = array();
-        if ($sFormat == 'array')
+        $a = [];
+        if($sFormat == 'array')
             $a = $s;
-        else if ($sFormat == 'json')
+        else if($sFormat == 'json')
             $a = json_decode($s, true);
-
-        if (!$a)
+        if(!$a)
             return $s;
 
         // filter out thumbnails
+        $iThreshold = 0;
+        if(($iLifetime = BxDolStorage::getGhostLifetime()) != 0)
+            $iThreshold = time() - $iLifetime;
+
         $aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId);
 
         $aResult = [];
         foreach ($a as $aFile) {
             // for requested image type show only (1) currently set images and (2) unassigned ghosts by uploader
-            if ($aFile['file_id'] == $aContentInfo[$this->_sImage] || (!in_array($aFile['file_id'], array_map(function($sField) use ($aContentInfo) {
+            if(($bCurrent = $aFile['file_id'] == $aContentInfo[$this->_sImage]) || (!in_array($aFile['file_id'], array_map(function($sField) use ($aContentInfo) {
                 return $aContentInfo[$sField];
-            }, $this->_aOtherImages)) && $aFile['uploader_id'] == $this->_iId))
-                $aResult[$aFile['file_id']] = $aFile;
+            }, $this->_aOtherImages)) && $aFile['uploader_id'] == $this->_iId)) {
+                // delete outdated ghost if ghost lifetime is set
+                if(!$bCurrent && $iThreshold && $aFile['file_created'] < $iThreshold) {
+                    if(($oStorage = BxDolStorage::getObjectInstance($this->_sStorageObject)) !== false)
+                        $oStorage->deleteFile($aFile['file_id']);
+
+                    continue;
+                }
+
+                // put only the newest file in results in case of non-multiple mode and all files otherwise
+                if(!$this->isMultiple()) {
+                    if(!$aResult || $aFile['file_created'] > reset($aResult)['file_created'])
+                        $aResult = [
+                            $aFile['file_id'] => $aFile
+                        ];
+                }
+                else
+                    $aResult[$aFile['file_id']] = $aFile;
+            }
         }
 
-        if ('array' == $sFormat) {
-            return $aResult;
-        }
-        else if ('json' == $sFormat) {
-            return json_encode($aResult);
-        } else { // html format is not suported for this data type
-            return false;
+        switch($sFormat) {
+            case 'array':
+               break;
+            case 'json':
+                $aResult = json_encode($aResult);
+                break;
+            default:
+                $aResult = false; // html format is not suported for this data type
         }
+
+        return $aResult;
     }
 
     protected function isAdmin ($iContentId = 0)

+ 3 - 0
storage_uploader.php

@@ -36,6 +36,9 @@ if (!$oUploader) {
     exit;
 }
 
+if(($bM = bx_get('m')) !== false)
+    $oUploader->setMultiple((bool)$bM);
+
 $sAction = bx_process_input(bx_get('a'));
 
 switch ($sAction) {

+ 4 - 1
template/scripts/BxBaseFormView.php

@@ -1746,6 +1746,7 @@ BLAH;
             return '';
 
         $sUniqId = !empty($aInput['uploaders_id']) ? $aInput['uploaders_id'] : genRndPwd (8, false);
+        $bMultiple = $aInput['multiple'] ?? true;
         $bInitGhosts = isset($aInput['init_ghosts']) && !$aInput['init_ghosts'] ? 0 : 1;
         $bInitReordering = empty($aInput['init_reordering']) ? 0 : 1;
 
@@ -1756,6 +1757,8 @@ BLAH;
             if(!$oUploader)
                 continue;
 
+            $oUploader->setMultiple($bMultiple);
+
             //--- Get Button code.
             $aAttrs = !empty($aInput['attrs']) ? $aInput['attrs'] : array();
             if(empty($aInput['attrs']['disabled']))
@@ -1785,7 +1788,7 @@ BLAH;
             if(isset($aInput['images_transcoder']) && $aInput['images_transcoder'])
                 $aParamsJs['images_transcoder'] = bx_js_string($aInput['images_transcoder']);
 
-            $sUploadersJs .= $oUploader->getUploaderJs($sGhostTemplate, isset($aInput['multiple']) ? $aInput['multiple'] : true, $aParamsJs, $this->_bDynamicMode);
+            $sUploadersJs .= $oUploader->getUploaderJs($sGhostTemplate, $bMultiple, $aParamsJs, $this->_bDynamicMode);
         }
 
         if(!$oUploader)