Kaynağa Gözat

Ticket #2754 - Reorder storage ghosts.

AntonLV 5 yıl önce
ebeveyn
işleme
f621811edf

+ 1 - 1
inc/classes/BxDolSearch.php

@@ -665,7 +665,7 @@ class BxDolSearchResult implements iBxDolReplaceable
                 if (isset($aValue['groupTable']))
                     $aSql['groupBy'] =  "GROUP BY `{$aValue['groupTable']}`.`{$aValue['groupField']}`, ";
                 $sOn = isset($aValue['mainTable']) ? $aValue['mainTable'] : $this->aCurrent['table'];
-                $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON `{$sAlias}`.`{$aValue['onField']}`=`$sOn`.`{$aValue['mainField']}`";
+                $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON " . (!empty($aValue['on_sql']) ? $aValue['on_sql'] : "`{$sAlias}`.`{$aValue['onField']}`=`$sOn`.`{$aValue['mainField']}`");
                 $aSql['ownFields'] .= $this->setFieldUnit($aValue['mainField'], $sOn, '', $bRenameMode);
             }
             $aSql['joinFields'] = trim($aSql['joinFields'], ', ');

+ 18 - 0
inc/classes/BxDolStorage.php

@@ -784,6 +784,24 @@ abstract class BxDolStorage extends BxDolFactory implements iBxDolFactoryObject
         
         return $this->_oDb->getGhosts($iProfileId, $iContentId, $isAdmin);
     }
+    
+    /**
+     * Reorder ghost/orphaned files for particular content/user.
+     * @param $iProfileId profile id
+     * @param $iContentId content id, or false to not consider content id at all
+     * @param $aGhosts an ordered list of ghost/orphaned files' IDs.
+     * @return boolean result of operation
+     */
+    public function reorderGhosts($iProfileId, $iContentId, $aGhosts)
+    {
+        $bResult = true;
+
+        $iGhosts = count($aGhosts);
+        for($i = 0; $i < $iGhosts; $i++)
+            $bResult &= $this->_oDb->updateGhostOrder($iProfileId, $iContentId, (int)$aGhosts[$i], $i);
+
+        return $bResult;
+    }
 
     /**
      * Update ghosts' content id.

+ 21 - 1
inc/classes/BxDolStorageQuery.php

@@ -235,6 +235,26 @@ class BxDolStorageQuery extends BxDolDb
         return $iCount;
     }
 
+    public function updateGhostOrder($iProfileId, $iContentId, $iFileId, $iOrder)
+    {
+        $aBindings = array(
+            'object' => $this->_aObject['object'],
+            'content_id' => $iContentId,
+            'id' => $iFileId,
+            'order' => $iOrder,
+        );
+
+        $sWhereClause = " AND `object`=:object AND `content_id`=:content_id AND `id`=:id";
+
+        if(!empty($iProfileId)) {
+            $aBindings['profile_id'] = $iProfileId;
+            
+            $sWhereClause .= " AND `profile_id`=:profile_id";
+        }
+
+        return $this->query("UPDATE `sys_storage_ghosts` SET `order`=:order WHERE 1" . $sWhereClause, $aBindings) !== false;
+    }
+
     public function updateGhostsContentId($mixedFileIds, $iProfileId, $iContentId, $aProfiles = array(), $isAdmin = false)
     {
         $aBindings = array(
@@ -325,7 +345,7 @@ class BxDolStorageQuery extends BxDolDb
             }
         }
 
-        $sQuery = "SELECT `f`.* FROM " . $this->_sTableFiles . " AS `f` " . $sJoin . $sWhere;
+        $sQuery = "SELECT `f`.* FROM " . $this->_sTableFiles . " AS `f` " . $sJoin . $sWhere . " ORDER BY `g`.`order` ASC";
         return $this->getAll($sQuery, $aBindings);
     }
 

+ 36 - 0
inc/classes/BxDolUploader.php

@@ -341,12 +341,48 @@ abstract class BxDolUploader extends BxDolFactory
             $a[$aFile['id']] = array_merge($aVars, $this->getGhostTemplateVars($aFile, $iProfileId, $iContentId, $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, $iContentId = false)
+    {
+        $a = $this->getGhosts($iProfileId, 'array', $sImagesTranscoder, $iContentId);
+        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 false;
         }
     }
+    
+    /**
+     * 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

+ 54 - 17
inc/js/classes/BxDolUploader.js

@@ -132,7 +132,7 @@ BxDolUploaderSimple.prototype.cancelAll = function () {
     this.onUploadCompleted(_t('_sys_uploader_upload_canceled'));
 }
 
-BxDolUploaderSimple.prototype.restoreGhosts = function () {
+BxDolUploaderSimple.prototype.restoreGhosts = function (bInitReordering, onComplete) {
     var sUrl = this._getUrlWithStandardParams() + '&img_trans=' + this._sImagesTranscoder + '&a=restore_ghosts&f=json' + '&c=' + this._iContentId + '&_t=' + escape(new Date());
     var $this = this;
 
@@ -146,28 +146,65 @@ BxDolUploaderSimple.prototype.restoreGhosts = function () {
             $('#' + $this._sResultContainerId + ' .bx-uploader-ghost').remove();
 
         if ('object' === typeof(aData)) {
-            $.each(aData, function(iFileId, oVars) {
-            	var oFileContainer = $('#' + $this._getFileContainerId(iFileId));
-                if (oFileContainer.length > 0)
-                    return;
-                var sHTML;
-                if (typeof $this._sTemplateGhost == 'object')
-                    sHTML = $this._sTemplateGhost[iFileId];
-                else
-                    sHTML = $this._sTemplateGhost;
-                for (var i in oVars)
-                    sHTML = sHTML.replace (new RegExp('{'+i+'}', 'g'), oVars[i]);
-                
-                $('#' + $this._sResultContainerId).prepend(sHTML);
-
-                oFileContainer.find('.bx-uploader-ghost-preview img').hide().fadeIn(1000);
-            });
+            if('object' === typeof(aData.g) && 'object' === typeof(aData.o)) 
+                for(var i in aData.o) {
+                    var iFileId = aData.o[i];
+                    $this.showGhost(iFileId, aData.g[iFileId]);
+                }
+            else
+                $.each(aData, function(iFileId, oVars) {
+                    $this.showGhost(iFileId, oVars);
+                });
 
             $('#' + $this._sResultContainerId).bx_show_more_check_overflow();
+
+            if(bInitReordering) {
+                $('#' + $this._sResultContainerId).sortable({
+                    items: '.bx-uploader-ghost', 
+                    start: function(oEvent, oUi) {
+                        oUi.item.addClass('bx-uploader-ghost-dragging');
+                    },
+                    stop: function(oEvent, oUi) {
+                        oUi.item.removeClass('bx-uploader-ghost-dragging');
+
+                        $this.reorderGhosts(oUi.item);
+                    }
+                });
+            }
+
+            if(typeof onComplete === 'function')
+                return onComplete(aData);
         }
     });
 };
 
+BxDolUploaderSimple.prototype.reorderGhosts = function(oDraggable) {
+    var sUrl = this._getUrlWithStandardParams() + '&a=reorder_ghosts&f=json' + '&c=' + this._iContentId + '&' + $('#' + this._sResultContainerId).sortable('serialize', {key: 'ghosts[]'}) + '&_t=' + escape(new Date());
+
+    $.getJSON(sUrl, function (aData) {
+        processJsonData(aData);
+    });
+};
+
+BxDolUploaderSimple.prototype.showGhost = function(iId, oVars) {
+    var oFileContainer = $('#' + this._getFileContainerId(iId));
+    if(oFileContainer.length > 0)
+        return;
+
+    var sHTML;
+    if (typeof this._sTemplateGhost == 'object')
+        sHTML = this._sTemplateGhost[iId];
+    else
+        sHTML = this._sTemplateGhost;
+
+    for(var i in oVars)
+        sHTML = sHTML.replace (new RegExp('{' + i + '}', 'g'), oVars[i]);
+
+    $('#' + this._sResultContainerId).append(sHTML);
+
+    oFileContainer.find('.bx-uploader-ghost-preview img').hide().fadeIn(1000);
+};
+
 BxDolUploaderSimple.prototype.deleteGhost = function (iFileId) {
     var sUrl = this._getUrlWithStandardParams() + '&a=delete&id=' + iFileId;
     var $this = this;

+ 1 - 0
install/sql/system.sql

@@ -2095,6 +2095,7 @@ CREATE TABLE IF NOT EXISTS `sys_storage_ghosts` (
   `object` varchar(64) NOT NULL,
   `content_id` int(11) NOT NULL,
   `created` int(10) unsigned NOT NULL,
+  `order` int(11) NOT NULL default '0',
   UNIQUE KEY `id` (`id`,`object`),
   KEY `created` (`created`),
   KEY `profile_object_content` (`profile_id`,`object`,`content_id`)

+ 3 - 0
modules/base/general/template/css/forms.css

@@ -1,5 +1,8 @@
 
 /* uploader */
+.bx-form-input-files-result.ui-sortable .bx-uploader-ghost {
+    cursor: move;
+}
 
 .bx-uploader-ghost .bx-base-general-icon-wrapper {
     float:left;

+ 1 - 0
modules/boonex/albums/classes/BxAlbumsConfig.php

@@ -83,6 +83,7 @@ class BxAlbumsConfig extends BxBaseModTextConfig
             'PARAM_NUM_RSS' => 'bx_albums_rss_num',
             'PARAM_SEARCHABLE_FIELDS' => 'bx_albums_searchable_fields',
             'PARAM_PER_PAGE_FOR_FAVORITES_LISTS' => 'bx_albums_per_page_for_favorites_lists',
+            'PARAM_ORDER_BY_GHOSTS' => true,
 
             // objects
             'OBJECT_STORAGE' => 'bx_albums_files',

+ 11 - 1
modules/boonex/albums/classes/BxAlbumsDb.php

@@ -57,7 +57,17 @@ class BxAlbumsDb extends BxBaseModTextDb
 
     public function getMediaInfoById($iMediaId)
     {
-        $sQuery = $this->prepare ("SELECT `f2e`.*, `f`.`added` FROM `" . $this->_oConfig->CNF['TABLE_FILES2ENTRIES'] . "` AS `f2e` INNER JOIN `" . $this->_oConfig->CNF['TABLE_FILES'] . "` AS `f` ON (`f`.`id` = `f2e`.`file_id`) INNER JOIN `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `e` ON (`e`.`id` = `f2e`.`content_id`) WHERE `f2e`.`id` = ?", $iMediaId);
+        $CNF = &$this->_oConfig->CNF;
+
+        $sSelectClause = "`f2e`.*, `f`.`added`";
+        $sJoinClause = "INNER JOIN `" . $CNF['TABLE_FILES'] . "` AS `f` ON (`f`.`id` = `f2e`.`file_id`) INNER JOIN `" . $CNF['TABLE_ENTRIES'] . "` AS `e` ON (`e`.`id` = `f2e`.`content_id`)";
+
+        if($CNF['PARAM_ORDER_BY_GHOSTS']) {
+            $sSelectClause .= ", `g`.`order` as `gorder`";
+            $sJoinClause .= $this->prepareAsString("INNER JOIN `sys_storage_ghosts` AS `g` ON `g`.`id`=`f2e`.`file_id` AND `g`.`content_id`=`f2e`.`content_id` AND `g`.`object`=?", $CNF['OBJECT_STORAGE']);
+        }
+
+        $sQuery = $this->prepare("SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_FILES2ENTRIES'] . "` AS `f2e` " . $sJoinClause . " WHERE `f2e`.`id` = ?", $iMediaId);
         return $this->getRow($sQuery);
     }
 

+ 5 - 0
modules/boonex/albums/classes/BxAlbumsFormEntry.php

@@ -18,6 +18,11 @@ class BxAlbumsFormEntry extends BxBaseModTextFormEntry
     {
         $this->MODULE = 'bx_albums';
         parent::__construct($aInfo, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (isset($CNF['FIELD_PHOTO']) && isset($this->aInputs[$CNF['FIELD_PHOTO']]))
+            $this->aInputs[$CNF['FIELD_PHOTO']]['init_reordering'] = true;
     }
 
     public function processFiles ($sFieldFile, $iContentId = 0, $isAssociateWithContent = false)

+ 37 - 8
modules/boonex/albums/classes/BxAlbumsSearchResultMedia.php

@@ -15,10 +15,9 @@ class BxAlbumsSearchResultMedia extends BxBaseModTextSearchResult
 
     function __construct($sMode = '', $aParams = array())
     {
-       
         $this->sUnitTemplateLiveSearch = 'unit_media_live_search.html';
-		
-		$aParams['unit_views'] = array('gallery' => 'unit_media.html', 'showcase' => 'unit_showcase.html');
+
+        $aParams['unit_views'] = array('gallery' => 'unit_media.html', 'showcase' => 'unit_showcase.html');
         parent::__construct($sMode, $aParams);
 
         $this->aCurrent = array(
@@ -33,7 +32,7 @@ class BxAlbumsSearchResultMedia extends BxBaseModTextSearchResult
             'restriction' => array(
                 'author' => array('value' => '', 'field' => 'author', 'operator' => '='),
                 'album' => array('value' => '', 'field' => 'content_id', 'operator' => '='),
-        		'featured' => array('value' => '', 'field' => 'featured', 'operator' => '<>'),
+                'featured' => array('value' => '', 'field' => 'featured', 'operator' => '<>'),
             ),
             'join' => array(
                 'albums' => array(
@@ -79,13 +78,17 @@ class BxAlbumsSearchResultMedia extends BxBaseModTextSearchResult
 
         switch ($sMode) {
             case 'album':
-                $this->aCurrent['restriction']['album']['value'] = (int)$aParams['album_id'];
-                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . (int)$aParams['album_id']);
+                $iAlbumId = (int)$aParams['album_id'];
+                $this->aCurrent['restriction']['album']['value'] = $iAlbumId;
+                $this->sBrowseUrl = BxDolPermalinks::getInstance()->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iAlbumId);
                 $this->aCurrent['title'] = _t('_bx_albums_page_title_browse_media_in_album');
-                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss_media/' . $sMode . '/' . (int)$aParams['album_id'];
+                $this->aCurrent['rss']['link'] = 'modules/?r=albums/rss_media/' . $sMode . '/' . $iAlbumId;
                 $this->aCurrent['sorting'] = 'order';
                 $this->sOrderDirection = 'ASC';
                 $this->setProcessPrivateContent(true);
+
+                if($CNF['PARAM_ORDER_BY_GHOSTS'])
+                    $this->_updateCurrentForOrderByGhosts();
                 break;
 
             case 'favorite':
@@ -159,6 +162,10 @@ class BxAlbumsSearchResultMedia extends BxBaseModTextSearchResult
                 $aSql['order'] = " ORDER BY `" . $sTable . "`.`order` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . " ";
                 break;
 
+            case 'order_by_ghosts':
+                $aSql['order'] = " ORDER BY `g`.`order` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . " ";
+                break;
+
             case 'last':
                 $aSql['order'] = " ORDER BY `f`.`added` " . $sWay . ", `" . $sTable . "`.`id` " . $sWay . " ";
                 break;
@@ -212,13 +219,18 @@ class BxAlbumsSearchResultMedia extends BxBaseModTextSearchResult
         foreach ($aRestrictions as $sKey) 
             if (0 === strpos($sKey, 'privacy_'))
                 unset($this->aCurrent['restriction'][$sKey]);
-        
+
         switch ($this->aCurrent['sorting']) {
             case 'order':
                 $this->sOrderDirection = $isNext ? 'ASC' : 'DESC';
                 $sOper = $isNext ? '>' : '<';
                 $this->aCurrent['restriction_sql'] = " AND (`{$this->aCurrent['table']}`.`order` {$sOper} {$aMediaInfo['order']} OR (`{$this->aCurrent['table']}`.`order` = {$aMediaInfo['order']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
                 break;
+            case 'order_by_ghosts':
+                $this->sOrderDirection = $isNext ? 'ASC' : 'DESC';
+                $sOper = $isNext ? '>' : '<';
+                $this->aCurrent['restriction_sql'] = " AND (`g`.`order` {$sOper} {$aMediaInfo['gorder']} OR (`g`.`order` = {$aMediaInfo['gorder']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
+                break;
             case 'last':
                 $this->aCurrent['restriction_sql'] = " AND (`f`.`added` {$sOper} {$aMediaInfo['added']} OR (`f`.`added` = {$aMediaInfo['added']} AND `{$this->aCurrent['table']}`.`id` {$sOper} {$aMediaInfo['id']})) ";
                 break;
@@ -241,6 +253,23 @@ class BxAlbumsSearchResultMedia extends BxBaseModTextSearchResult
     {
         return BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $this->oModule->_oConfig->CNF['URI_VIEW_MEDIA'] . '&id=' . $a['id']);
     }
+
+    protected function _updateCurrentForOrderByGhosts()
+    {
+        $CNF = &$this->oModule->_oConfig->CNF;
+
+        $this->aCurrent['join']['ghosts'] = [
+            'type' => 'INNER',
+            'table' => 'sys_storage_ghosts',
+            'table_alias' => 'g',
+            'mainField' => 'file_id',
+            'on_sql' => $this->oModule->_oDb->prepareAsString(" `g`.`id`=`bx_albums_files2albums`.`file_id` AND `g`.`content_id`=`bx_albums_files2albums`.`content_id` AND `g`.`object`=? ", $CNF['OBJECT_STORAGE']),
+            'joinFields' => array('order'),
+        ];
+        $this->aCurrent['sorting'] = 'order_by_ghosts';
+        $this->sOrderDirection = 'ASC';
+    }
+
 /*
     function displaySearchUnit ($aData)
     {

+ 7 - 1
storage_uploader.php

@@ -52,7 +52,13 @@ switch ($sAction) {
     case 'restore_ghosts':
         header('Content-Type: application/json; charset=utf-8');
         $sImagesTranscoder = bx_process_input(bx_get('img_trans'));
-        echo $oUploader->getGhosts((int)bx_get_logged_profile_id(), $sFormat, $sImagesTranscoder, $iContentId);
+        echo $oUploader->getGhostsWithOrder((int)bx_get_logged_profile_id(), $sFormat, $sImagesTranscoder, $iContentId);
+        break;
+    
+    case 'reorder_ghosts':
+        header('Content-Type: application/json; charset=utf-8');
+        $aGhosts = bx_process_input(bx_get('ghosts'));
+        echo $oUploader->reorderGhosts((int)bx_get_logged_profile_id(), $sFormat, $aGhosts, $iContentId);
         break;
 
     case 'delete':

+ 3 - 5
template/form_field_uploader.html

@@ -1,4 +1,3 @@
-
 <div class="bx-form-input-files-uploaders bx-def-margin-sec-top">
     __uploaders_buttons__
     <div class="bx-clear"></div>
@@ -8,9 +7,8 @@ __error__
 <div id="__id_container_errors__" class="bx-form-input-files-errors"></div>
 <div id="__id_container_result__" class="bx-form-input-files-result bx-clearfix"></div>
 <script>
-    $(document).ready(function(){
+    $(document).ready(function() {
         if (__is_init_ghosts__)
-            __uploader_instance_name__.restoreGhosts();
+            __uploader_instance_name__.restoreGhosts(__is_init_reordering__);
     });
-</script>
-
+</script>

+ 10 - 0
template/scripts/BxBaseFormView.php

@@ -1414,6 +1414,15 @@ BLAH;
             $sUploaders .= $oUploader->getUploaderButton($sGhostTemplate, isset($aInput['multiple']) ? $aInput['multiple'] : true, $aParams, $this->_bDynamicMode);
         }
 
+        $bInitReordering = $oUploader && !empty($aInput['init_reordering']);
+        if($bInitReordering)
+            $this->oTemplate->addJs(array(
+                'jquery-ui/jquery.ui.core.min.js',
+                'jquery-ui/jquery.ui.widget.min.js',
+                'jquery-ui/jquery.ui.mouse.min.js',
+                'jquery-ui/jquery.ui.sortable.min.js',
+            ));
+
         return $this->oTemplate->parseHtmlByName('form_field_uploader.html', array(
             'uploaders_buttons' => $sUploaders,
             'info' => $sInfo,
@@ -1422,6 +1431,7 @@ BLAH;
             'id_container_result' => $oUploader ? $oUploader->getIdContainerResult() : '',
             'uploader_instance_name' => $oUploader ? $oUploader->getNameJsInstanceUploader() : '',
             'is_init_ghosts' => isset($aInput['init_ghosts']) && !$aInput['init_ghosts'] ? 0 : 1,
+            'is_init_reordering' => !$bInitReordering ? 0 : 1
         ));
     }