🔞 ADULT: Changeset/ - Full Gallery 2025

Changeset 3353021


Ignore:
Timestamp:
08/30/2025 08:22:13 AM (4 months ago)
Author:
devloper00
Message:

Update:1.4.0

Location:
media-library-downloader/trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • media-library-downloader/trunk/assets/js/admin.js

    r3281245 r3353021  
    11document.addEventListener('DOMContentLoaded', function () {
    2     let body = document.body;
    3     if (!body.classList.contains('upload-php')) { return; }
     2    const body = document.body;
     3    if (!body.classList.contains('upload-php')) {
     4        return;
     5    }
    46
    57    /**
    6      * Get current view
     8     * Download Manager Class
    79     */
    8     let viewList = document.querySelector('.table-view-list');
    9     if (viewList !== null) {
    10         let bulkActionTop = document.querySelector('#bulk-action-selector-top');
    11         let bulkActionBottom = document.querySelector('#bulk-action-selector-bottom');
    12 
    13         /**
    14          * Add option on Top
    15          */
    16         const option1 = document.createElement("option");
    17         option1.value = "mld-download-files"
    18         option1.innerText = mld_i18n.download_files;
    19         bulkActionTop.appendChild(option1);
    20 
    21         /**
    22          * Add option on Bottom
    23          */
    24         const option2 = document.createElement("option");
    25         option2.value = "mld-download-files"
    26         option2.innerText = mld_i18n.download_files;
    27         bulkActionBottom.appendChild(option2);
    28 
    29         // Ajax Call
    30         let doAction = document.querySelector('#doaction');
    31         doAction.addEventListener('click', function (e) {
    32             if ((bulkActionTop.value || bulkActionBottom.value) !== 'mld-download-files') {
     10    class MLDDownloadManager {
     11        constructor() {
     12            this.isDownloading = false;
     13            this.init();
     14        }
     15
     16        init() {
     17            const viewList = document.querySelector('.table-view-list');
     18            if (viewList !== null) {
     19                this.initListView();
     20            } else {
     21                this.initGridView();
     22            }
     23        }
     24
     25        /**
     26         * Initialize List View
     27         */
     28        initListView() {
     29            this.addBulkActions();
     30            this.handleBulkDownload();
     31            this.addIndividualDownloadButtons();
     32        }
     33
     34        /**
     35         * Add bulk action options
     36         */
     37        addBulkActions() {
     38            const bulkActionTop = document.querySelector('#bulk-action-selector-top');
     39            const bulkActionBottom = document.querySelector('#bulk-action-selector-bottom');
     40
     41            if (!bulkActionTop || !bulkActionBottom) return;
     42
     43            // Add option to top selector
     44            const option1 = document.createElement("option");
     45            option1.value = "mld-download-files";
     46            option1.innerText = mld_i18n.download_files;
     47            bulkActionTop.appendChild(option1);
     48
     49            // Add option to bottom selector
     50            const option2 = document.createElement("option");
     51            option2.value = "mld-download-files";
     52            option2.innerText = mld_i18n.download_files;
     53            bulkActionBottom.appendChild(option2);
     54        }
     55
     56        /**
     57         * Handle bulk download action
     58         */
     59        handleBulkDownload() {
     60            const doAction = document.querySelector('#doaction');
     61            const doAction2 = document.querySelector('#doaction2');
     62           
     63            if (doAction) {
     64                doAction.addEventListener('click', (e) => this.processBulkAction(e, 'top'));
     65            }
     66            if (doAction2) {
     67                doAction2.addEventListener('click', (e) => this.processBulkAction(e, 'bottom'));
     68            }
     69        }
     70
     71        /**
     72         * Process bulk action
     73         */
     74        processBulkAction(e, position) {
     75            const selector = position === 'top' ?
     76                document.querySelector('#bulk-action-selector-top') :
     77                document.querySelector('#bulk-action-selector-bottom');
     78
     79            if (!selector || selector.value !== 'mld-download-files') {
    3380                return;
    3481            }
     
    3683            e.preventDefault();
    3784
    38             let checkedFiles = document.querySelectorAll('#the-list input:checked');
     85            const checkedFiles = document.querySelectorAll('#the-list input:checked');
    3986            if (!checkedFiles || checkedFiles.length === 0) {
    40                 alert(mld_i18n.no_files_selected);
     87                this.showMessage(mld_i18n.no_files_selected, 'error');
    4188                return;
    4289            }
    4390
    44             let selection = new Array();
    45             for (let index = 0; index < checkedFiles.length; index++) {
    46                 const element = checkedFiles[index];
     91            const selection = [];
     92            for (const element of checkedFiles) {
    4793                if (element && element.value) {
    4894                    selection.push(element.value);
     
    5197
    5298            if (selection.length === 0) {
    53                 alert(mld_i18n.no_files_selected);
     99                this.showMessage(mld_i18n.no_files_selected, 'error');
    54100                return;
    55101            }
     102
     103            this.downloadFiles(selection);
     104        }
     105
     106        /**
     107         * Add individual download buttons to list view
     108         */
     109        addIndividualDownloadButtons() {
     110            const rows = document.querySelectorAll('#the-list tr');
     111           
     112            rows.forEach(row => {
     113                const titleCell = row.querySelector('.title');
     114                if (!titleCell) return;
     115
     116                const attachmentId = this.getAttachmentId(row);
     117                if (!attachmentId) return;
     118
     119                // Create download button
     120                const downloadBtn = document.createElement('button');
     121                downloadBtn.className = 'button button-small mld-single-download';
     122                downloadBtn.type = 'button';
     123                downloadBtn.innerHTML = '📥 ' + mld_i18n.download_single;
     124                downloadBtn.dataset.attachmentId = attachmentId;
     125                downloadBtn.style.marginLeft = '10px';
     126                downloadBtn.setAttribute('aria-label', mld_i18n.download_single + ' ' + (titleCell.querySelector('.title')?.textContent || 'file'));
     127                downloadBtn.setAttribute('title', mld_i18n.download_single);
     128
     129                // Add click handler
     130                downloadBtn.addEventListener('click', (e) => {
     131                    e.preventDefault();
     132                    this.downloadFiles([attachmentId]);
     133                });
     134
     135                // Add keyboard support
     136                downloadBtn.addEventListener('keydown', (e) => {
     137                    if (e.key === 'Enter' || e.key === ' ') {
     138                        e.preventDefault();
     139                        this.downloadFiles([attachmentId]);
     140                    }
     141                });
     142
     143                // Insert button
     144                const rowActions = titleCell.querySelector('.row-actions');
     145                if (rowActions) {
     146                    const downloadAction = document.createElement('span');
     147                    downloadAction.className = 'mld-download';
     148                    downloadAction.appendChild(downloadBtn);
     149                    rowActions.appendChild(downloadAction);
     150                }
     151            });
     152        }
     153
     154        /**
     155         * Get attachment ID from row
     156         */
     157        getAttachmentId(row) {
     158            const checkbox = row.querySelector('input[type="checkbox"]');
     159            return checkbox ? checkbox.value : null;
     160        }
     161
     162        /**
     163         * Initialize Grid View
     164         */
     165        initGridView() {
     166            this.watchForGridChanges();
     167            this.addGridDownloadButtons();
     168        }
     169
     170        /**
     171         * Watch for grid view changes
     172         */
     173        watchForGridChanges() {
     174            const observer = new MutationObserver(() => {
     175                this.addGridDownloadButtons();
     176            });
     177
     178            const attachmentsWrapper = document.querySelector('.attachments-wrapper');
     179            if (attachmentsWrapper) {
     180                observer.observe(attachmentsWrapper, {
     181                    childList: true,
     182                    subtree: true
     183                });
     184            }
     185
     186            // Also watch for select mode toggle
     187            setTimeout(() => {
     188                const bulkSelect = document.querySelector('.media-toolbar .select-mode-toggle-button');
     189                if (bulkSelect) {
     190                    bulkSelect.addEventListener('click', () => {
     191                        setTimeout(() => this.addBulkDownloadButton(), 100);
     192                    });
     193                }
     194            }, 200);
     195        }
     196
     197        /**
     198         * Add bulk download button for grid view
     199         */
     200        addBulkDownloadButton() {
     201            // Remove existing button
     202            const existingBtn = document.querySelector('#mld-download');
     203            if (existingBtn) {
     204                existingBtn.remove();
     205            }
     206
     207            const toolbar = document.querySelector('.media-toolbar-secondary');
     208            const deleteBtn = document.querySelector('.delete-selected-button');
     209           
     210            if (!toolbar || !deleteBtn) return;
     211
     212            const downloadButton = document.createElement('button');
     213            downloadButton.id = 'mld-download';
     214            downloadButton.type = 'button';
     215            downloadButton.className = 'button media-button button-primary button-large';
     216            downloadButton.innerHTML = mld_i18n.download_files;
     217            downloadButton.setAttribute('aria-label', mld_i18n.download_files);
     218            downloadButton.setAttribute('title', mld_i18n.download_files);
     219           
     220            downloadButton.addEventListener('click', () => {
     221                const selectedAttachments = document.querySelectorAll('.attachments-wrapper .attachments .attachment[aria-checked="true"]');
     222               
     223                if (selectedAttachments.length === 0) {
     224                    this.showMessage(mld_i18n.no_files_selected, 'error');
     225                    return;
     226                }
     227
     228                const filesToDownload = [];
     229                selectedAttachments.forEach(element => {
     230                    const dataID = element.dataset.id;
     231                    if (dataID) {
     232                        filesToDownload.push(dataID);
     233                    }
     234                });
     235
     236                if (filesToDownload.length > 0) {
     237                    this.downloadFiles(filesToDownload);
     238                }
     239            });
     240
     241            toolbar.insertBefore(downloadButton, deleteBtn);
     242        }
     243
     244        /**
     245         * Add individual download buttons to grid view
     246         */
     247        addGridDownloadButtons() {
     248            const attachments = document.querySelectorAll('.attachment:not(.mld-processed)');
     249           
     250            attachments.forEach(attachment => {
     251                attachment.classList.add('mld-processed');
     252               
     253                const attachmentId = attachment.dataset.id;
     254                if (!attachmentId) return;
     255
     256                // Create download overlay
     257                const downloadOverlay = document.createElement('div');
     258                downloadOverlay.className = 'mld-download-overlay';
     259                downloadOverlay.style.cssText = `
     260                    position: absolute;
     261                    top: 5px;
     262                    right: 5px;
     263                    background: rgba(0,0,0,0.7);
     264                    border-radius: 3px;
     265                    padding: 2px;
     266                    opacity: 0;
     267                    transition: opacity 0.2s;
     268                    z-index: 10;
     269                `;
     270
     271                const downloadBtn = document.createElement('button');
     272                downloadBtn.className = 'button button-small mld-grid-download';
     273                downloadBtn.type = 'button';
     274                downloadBtn.innerHTML = '📥';
     275                downloadBtn.title = mld_i18n.download_single;
     276                downloadBtn.style.cssText = `
     277                    background: #0073aa;
     278                    color: white;
     279                    border: none;
     280                    padding: 4px 6px;
     281                    font-size: 12px;
     282                    cursor: pointer;
     283                `;
     284
     285                downloadBtn.addEventListener('click', (e) => {
     286                    e.preventDefault();
     287                    e.stopPropagation();
     288                    this.downloadFiles([attachmentId]);
     289                });
     290
     291                downloadOverlay.appendChild(downloadBtn);
     292                attachment.style.position = 'relative';
     293                attachment.appendChild(downloadOverlay);
     294
     295                // Show/hide on hover
     296                attachment.addEventListener('mouseenter', () => {
     297                    downloadOverlay.style.opacity = '1';
     298                });
     299                attachment.addEventListener('mouseleave', () => {
     300                    downloadOverlay.style.opacity = '0';
     301                });
     302            });
     303        }
     304
     305        /**
     306         * Download files via AJAX
     307         */
     308        downloadFiles(attachmentIds) {
     309            if (this.isDownloading) {
     310                this.showMessage(mld_i18n.preparing_download, 'info');
     311                return;
     312            }
     313
     314            this.isDownloading = true;
     315            this.showProgress(true);
    56316
    57317            jQuery.post({
     
    59319                data: {
    60320                    action: "download_files",
    61                     ids: selection,
     321                    ids: attachmentIds,
     322                    nonce: admin.nonce
    62323                },
    63                 success: function (response) {
    64                     if (response.success) {
    65                         window.location = response.data;
    66                     } else {
    67                         alert(response.data);
    68                     }
     324                success: (response) => {
     325                    this.handleDownloadResponse(response);
    69326                },
    70                 error: function() {
    71                     alert(mld_i18n.download_error);
     327                error: (xhr, status, error) => {
     328                    this.handleDownloadError(xhr, status, error);
     329                },
     330                complete: () => {
     331                    this.isDownloading = false;
     332                    this.showProgress(false);
    72333                }
    73334            });
    74         })
    75     } else {
    76 
    77         setTimeout(() => {
    78             let bulkSelect = document.querySelector('.media-toolbar .select-mode-toggle-button');
    79            
    80             if (bulkSelect !== null) {
    81                 bulkSelect.setAttribute('aria-selected', 'false');
    82                 bulkSelect.addEventListener('click', function (e) {
    83                     let existingDownloadButton = document.querySelector('#mld-download');
    84 
    85                     // Remove old button
    86                     if (e.target.classList.contains('large-button')){
    87                         existingDownloadButton.remove();
    88                     }
    89                     if (existingDownloadButton === null) {
    90                         let downloadButton = document.createElement('button');
    91                         downloadButton.id = 'mld-download'
    92                         downloadButton.type = 'button';
    93                         downloadButton.classList = 'button media-button button-primary button-large delete-selected-button';
    94                         downloadButton.innerText = 'Télécharger';
    95                         document.querySelector('.media-toolbar-secondary').insertBefore(downloadButton, document.querySelector('.delete-selected-button'))
    96                     }
    97                 })
    98             }
    99         }, 200);
    100 
    101         // Ajax Call
    102         window.addEventListener('click', function(e){
    103             let clickedElementID = e.target.id;
    104             if (clickedElementID !== 'mld-download'){
    105                 return;
    106             }
    107             let filesToDownload = new Array();
    108             let imagesSelected = document.querySelectorAll('.attachments-wrapper .attachments .attachment[aria-checked="true"]');
    109             if (imagesSelected.length > 0){
    110                 for (let index = 0; index < imagesSelected.length; index++) {
    111                     const element = imagesSelected[index];
    112                     let dataID = element.dataset.id;
    113                     filesToDownload.push(dataID);
    114                 }
    115                 jQuery.post({
    116                     url: admin.ajax_url,
    117                     data: {
    118                         action: "download_files",
    119                         ids: filesToDownload,
    120                     },
    121                     success: function (response) {
    122                         window.location = response.data;
    123                     },
    124                 });
    125             }
    126         })
     335        }
     336
     337        /**
     338         * Handle successful download response
     339         */
     340        handleDownloadResponse(response) {
     341            if (response.success && response.data) {
     342                const data = response.data;
     343               
     344                if (data.single) {
     345                    // Direct download for single files
     346                    window.open(data.url, '_blank');
     347                    this.showMessage(`Downloaded: ${data.filename}`, 'success');
     348                } else {
     349                    // ZIP download for multiple files
     350                    window.location = data.url;
     351                    this.showMessage(`Prepared ZIP with ${data.file_count} files`, 'success');
     352                }
     353            } else {
     354                this.showMessage(response.data || mld_i18n.download_error, 'error');
     355            }
     356        }
     357
     358        /**
     359         * Handle download error
     360         */
     361        handleDownloadError(xhr, status, error) {
     362            let errorMessage = mld_i18n.download_error;
     363           
     364            if (xhr.responseJSON && xhr.responseJSON.data) {
     365                errorMessage = xhr.responseJSON.data;
     366            } else if (error) {
     367                errorMessage = error;
     368            }
     369           
     370            this.showMessage(errorMessage, 'error');
     371        }
     372
     373        /**
     374         * Show progress indicator
     375         */
     376        showProgress(show) {
     377            let progressEl = document.querySelector('#mld-progress');
     378           
     379            if (show) {
     380                if (!progressEl) {
     381                    progressEl = document.createElement('div');
     382                    progressEl.id = 'mld-progress';
     383                    progressEl.style.cssText = `
     384                        position: fixed;
     385                        top: 32px;
     386                        right: 20px;
     387                        background: #0073aa;
     388                        color: white;
     389                        padding: 10px 15px;
     390                        border-radius: 4px;
     391                        z-index: 100000;
     392                        font-size: 14px;
     393                    `;
     394                    progressEl.innerHTML = '📥 ' + mld_i18n.preparing_download;
     395                    document.body.appendChild(progressEl);
     396                }
     397            } else {
     398                if (progressEl) {
     399                    progressEl.remove();
     400                }
     401            }
     402        }
     403
     404        /**
     405         * Show message to user
     406         */
     407        showMessage(message, type = 'info') {
     408            // Remove existing message
     409            const existingMessage = document.querySelector('#mld-message');
     410            if (existingMessage) {
     411                existingMessage.remove();
     412            }
     413
     414            const messageEl = document.createElement('div');
     415            messageEl.id = 'mld-message';
     416           
     417            let bgColor = '#0073aa';
     418            if (type === 'error') bgColor = '#dc3232';
     419            if (type === 'success') bgColor = '#46b450';
     420           
     421            messageEl.style.cssText = `
     422                position: fixed;
     423                top: 32px;
     424                right: 20px;
     425                background: ${bgColor};
     426                color: white;
     427                padding: 10px 15px;
     428                border-radius: 4px;
     429                z-index: 100000;
     430                font-size: 14px;
     431                max-width: 300px;
     432            `;
     433            messageEl.textContent = message;
     434            document.body.appendChild(messageEl);
     435
     436            // Auto remove after 5 seconds
     437            setTimeout(() => {
     438                if (messageEl && messageEl.parentNode) {
     439                    messageEl.remove();
     440                }
     441            }, 5000);
     442        }
    127443    }
    128 })
     444
     445    // Initialize the download manager
     446    new MLDDownloadManager();
     447});
  • media-library-downloader/trunk/includes/class-main.php

    r3281245 r3353021  
    1010            add_action( 'admin_enqueue_scripts', array( $this, 'mld_enqueue_back' ) );
    1111            add_action( 'wp_ajax_download_files', array( $this, 'mld_download_files' ) );
     12            add_action( 'init', array( $this, 'schedule_cleanup' ) );
    1213        }
    1314
     
    1718        public function mld_empty_temp_folder() {
    1819            $current_screen_obj = get_current_screen();
    19             if ( !$current_screen_obj && $current_screen_obj->base !== 'upload' ) :
    20                 return;
    21             endif;
     20            if ( ! $current_screen_obj || $current_screen_obj->base !== 'upload' ) {
     21                return;
     22            }
     23           
     24            if ( ! is_dir( MLD_TEMP_PATH ) ) {
     25                return;
     26            }
     27           
    2228            $files = glob( MLD_TEMP_PATH . '/*' );
    23             foreach ( $files as $file ) :
    24                 if ( is_file( $file ) ) :
     29            if ( ! $files ) {
     30                return;
     31            }
     32           
     33            foreach ( $files as $file ) {
     34                // Security check: ensure file is within temp directory
     35                if ( strpos( realpath( $file ), realpath( MLD_TEMP_PATH ) ) !== 0 ) {
     36                    continue;
     37                }
     38               
     39                if ( is_file( $file ) ) {
    2540                    unlink( $file );
    26                 endif;
    27                 if ( is_dir( $file ) ) :
    28                     rmdir( $file );
    29                 endif;
    30             endforeach;
     41                } elseif ( is_dir( $file ) ) {
     42                    $this->mld_remove_directory( $file );
     43                }
     44            }
     45        }
     46       
     47        /**
     48         * Recursively remove directory
     49         */
     50        private function mld_remove_directory( $dir ) {
     51            if ( ! is_dir( $dir ) ) {
     52                return false;
     53            }
     54           
     55            $files = array_diff( scandir( $dir ), array( '.', '..' ) );
     56            foreach ( $files as $file ) {
     57                $path = $dir . '/' . $file;
     58                if ( is_dir( $path ) ) {
     59                    $this->mld_remove_directory( $path );
     60                } else {
     61                    unlink( $path );
     62                }
     63            }
     64            return rmdir( $dir );
    3165        }
    3266
     
    3569         */
    3670        public function mld_enqueue_back() {
    37             wp_enqueue_script( 'mld-admin-script', MLD_ASSETS_JS . 'admin.js', array( 'jquery' ), 1.0, true );
    38             wp_localize_script( 'mld-admin-script', 'admin', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
     71            $current_screen = get_current_screen();
     72            if ( ! $current_screen || $current_screen->base !== 'upload' ) {
     73                return;
     74            }
     75           
     76            wp_enqueue_script( 'mld-admin-script', MLD_ASSETS_JS . 'admin.js', array( 'jquery' ), '1.3.4', true );
     77            wp_localize_script( 'mld-admin-script', 'admin', array(
     78                'ajax_url' => admin_url( 'admin-ajax.php' ),
     79                'nonce' => wp_create_nonce( 'mld_download_nonce' )
     80            ) );
    3981            wp_localize_script( 'mld-admin-script', 'mld_i18n', array(
    40                 'download_files' => __('Download selected files', 'media-library-downloader'),
    41                 'no_files_selected' => __('No files selected', 'media-library-downloader'),
    42                 'download_error' => __('An error occurred while downloading files', 'media-library-downloader')
     82                'download_files' => __( 'Download selected files', 'media-library-downloader' ),
     83                'download_single' => __( 'Download', 'media-library-downloader' ),
     84                'no_files_selected' => __( 'No files selected', 'media-library-downloader' ),
     85                'download_error' => __( 'An error occurred while downloading files', 'media-library-downloader' ),
     86                'preparing_download' => __( 'Preparing download...', 'media-library-downloader' ),
     87                'invalid_file' => __( 'Invalid file selected', 'media-library-downloader' ),
     88                'unauthorized' => __( 'Unauthorized access', 'media-library-downloader' )
    4389            ));
    4490        }
     
    4894         */
    4995        public function mld_download_files() {
    50 
    51             // Prevent non authorized user to make action
    52             if ( !current_user_can('upload_files') ) :
    53                 wp_send_json_error('Unauthorized access', 403);
    54                 wp_die();
    55             endif;
    56 
    57             // Check $_POST data
    58             if ( isset( $_POST['ids'] ) && !empty( $_POST['ids'] ) ) :
    59                 $attachment_ids = filter_input( INPUT_POST, 'ids', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
    60             endif;
    61 
    62             /**
    63              * Create temp folder
    64              */
    65             $timestamp     = gmdate( 'U' );
    66             $folder_name   = 'media-library-downloader-' . $timestamp;
    67             $folder_path   = MLD_TEMP_PATH . $folder_name;
    68             $root          = MLD_TEMP_URL;
    69             $create_folder = mkdir( $folder_path );
    70 
    71             if ( $create_folder && $attachment_ids ) :
    72                 $zip = new ZipArchive();
    73                 if ( $zip->open( $folder_path . '.zip', ZipArchive::CREATE ) ) :
    74                     foreach ( $attachment_ids as $attachment_id ) :
    75                         $attachment_name = basename( get_attached_file( $attachment_id ) );
    76                         $attachment_url  = get_attached_file($attachment_id );
    77                         if ( ini_get( 'allow_url_fopen' ) ) {
    78                             $file_content = file_get_contents( $attachment_url ); //phpcs:disable
    79                         } else {
    80                             $ch = curl_init();
    81                             curl_setopt( $ch, CURLOPT_URL, $attachment_url );
    82                             curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
    83                             $file_content = curl_exec( $ch );
    84                             curl_close( $ch );
    85                         }
    86                         $zip->addFromString( $attachment_name, $file_content );
    87                     endforeach;
    88                     $zip->close();
    89                     header( 'Content-disposition: attachment; filename=' . $folder_name . '.zip' );
    90                     header( 'Content-type: application/zip' );
    91                     wp_send_json_success( $root . $folder_name . '.zip', null, 0 );
    92                     wp_die();
    93                 endif;
    94             endif;
     96            // Verify nonce for security
     97            if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'mld_download_nonce' ) ) {
     98                wp_send_json_error( __( 'Security check failed', 'media-library-downloader' ), 403 );
     99            }
     100
     101            // Check user permissions
     102            if ( ! current_user_can( 'upload_files' ) ) {
     103                wp_send_json_error( __( 'Unauthorized access', 'media-library-downloader' ), 403 );
     104            }
     105
     106            // Validate and sanitize input
     107            $attachment_ids = $this->mld_validate_attachment_ids();
     108            if ( empty( $attachment_ids ) ) {
     109                wp_send_json_error( __( 'No valid files selected', 'media-library-downloader' ), 400 );
     110            }
     111
     112            // Check if user can access these attachments
     113            $valid_ids = $this->mld_filter_accessible_attachments( $attachment_ids );
     114            if ( empty( $valid_ids ) ) {
     115                wp_send_json_error( __( 'No accessible files found', 'media-library-downloader' ), 403 );
     116            }
     117
     118            // Handle single file download
     119            if ( count( $valid_ids ) === 1 ) {
     120                $this->mld_download_single_file( $valid_ids[0] );
     121                return;
     122            }
     123
     124            // Handle multiple files download
     125            $this->mld_download_multiple_files( $valid_ids );
     126        }
     127
     128        /**
     129         * Validate and sanitize attachment IDs
     130         */
     131        private function mld_validate_attachment_ids() {
     132            if ( ! isset( $_POST['ids'] ) || ! is_array( $_POST['ids'] ) ) {
     133                return array();
     134            }
     135
     136            $attachment_ids = array();
     137            foreach ( $_POST['ids'] as $id ) {
     138                $clean_id = absint( $id );
     139                if ( $clean_id > 0 && get_post_type( $clean_id ) === 'attachment' ) {
     140                    $attachment_ids[] = $clean_id;
     141                }
     142            }
     143
     144            return array_unique( $attachment_ids );
     145        }
     146
     147        /**
     148         * Filter attachments that user can access
     149         */
     150        private function mld_filter_accessible_attachments( $attachment_ids ) {
     151            $valid_ids = array();
     152           
     153            foreach ( $attachment_ids as $attachment_id ) {
     154                // Check if attachment exists and user can edit it
     155                if ( current_user_can( 'edit_post', $attachment_id ) ) {
     156                    $file_path = get_attached_file( $attachment_id );
     157                    if ( $file_path && file_exists( $file_path ) ) {
     158                        $valid_ids[] = $attachment_id;
     159                    }
     160                }
     161            }
     162           
     163            return $valid_ids;
     164        }
     165
     166        /**
     167         * Download single file
     168         */
     169        private function mld_download_single_file( $attachment_id ) {
     170            $file_path = get_attached_file( $attachment_id );
     171            $file_name = basename( $file_path );
     172           
     173            if ( ! $file_path || ! file_exists( $file_path ) ) {
     174                wp_send_json_error( __( 'File not found', 'media-library-downloader' ), 404 );
     175            }
     176
     177            // Log the download
     178            $this->log_download( array( $attachment_id ), 'single' );
     179
     180            // For single files, return the direct URL
     181            $file_url = wp_get_attachment_url( $attachment_id );
     182            wp_send_json_success( array(
     183                'url' => $file_url,
     184                'filename' => $file_name,
     185                'single' => true
     186            ) );
     187        }
     188
     189        /**
     190         * Download multiple files as ZIP
     191         */
     192        private function mld_download_multiple_files( $attachment_ids ) {
     193            if ( ! class_exists( 'ZipArchive' ) ) {
     194                wp_send_json_error( __( 'ZIP functionality not available', 'media-library-downloader' ), 500 );
     195            }
     196
     197            // Create temp directory if it doesn't exist
     198            if ( ! is_dir( MLD_TEMP_PATH ) ) {
     199                if ( ! wp_mkdir_p( MLD_TEMP_PATH ) ) {
     200                    wp_send_json_error( __( 'Cannot create temporary directory', 'media-library-downloader' ), 500 );
     201                }
     202            }
     203
     204            $timestamp = time();
     205            $folder_name = $this->generate_zip_filename( $timestamp );
     206            $zip_path = MLD_TEMP_PATH . $folder_name . '.zip';
     207            $zip_url = MLD_TEMP_URL . $folder_name . '.zip';
     208
     209            $zip = new ZipArchive();
     210            if ( $zip->open( $zip_path, ZipArchive::CREATE ) !== TRUE ) {
     211                wp_send_json_error( __( 'Cannot create ZIP file', 'media-library-downloader' ), 500 );
     212            }
     213
     214            $file_count = 0;
     215            $total_size = 0;
     216            $max_size = $this->mld_get_max_download_size();
     217
     218            foreach ( $attachment_ids as $attachment_id ) {
     219                $file_path = get_attached_file( $attachment_id );
     220                $file_name = basename( $file_path );
     221               
     222                if ( ! $file_path || ! file_exists( $file_path ) ) {
     223                    continue;
     224                }
     225
     226                $file_size = filesize( $file_path );
     227                if ( $total_size + $file_size > $max_size ) {
     228                    break; // Stop if we exceed size limit
     229                }
     230
     231                // Handle duplicate filenames
     232                $unique_name = $this->mld_get_unique_filename( $zip, $file_name );
     233               
     234                if ( $zip->addFile( $file_path, $unique_name ) ) {
     235                    $file_count++;
     236                    $total_size += $file_size;
     237                }
     238            }
     239
     240            $zip->close();
     241
     242            if ( $file_count === 0 ) {
     243                unlink( $zip_path );
     244                wp_send_json_error( __( 'No files could be added to ZIP', 'media-library-downloader' ), 500 );
     245            }
     246
     247            // Log the download
     248            $this->log_download( $attachment_ids, 'zip' );
     249
     250            wp_send_json_success( array(
     251                'url' => $zip_url,
     252                'filename' => $folder_name . '.zip',
     253                'file_count' => $file_count,
     254                'single' => false
     255            ) );
     256        }
     257
     258        /**
     259         * Get maximum download size in bytes
     260         */
     261        private function mld_get_max_download_size() {
     262            $settings = get_option( 'mld_settings', array() );
     263            $max_size_mb = $settings['max_download_size'] ?? 100;
     264            $max_size = $max_size_mb * 1024 * 1024; // Convert to bytes
     265           
     266            $max_size = apply_filters( 'mld_max_download_size', $max_size );
     267            return min( $max_size, wp_max_upload_size() * 2 ); // Don't exceed 2x upload limit
     268        }
     269
     270        /**
     271         * Get unique filename for ZIP to handle duplicates
     272         */
     273        private function mld_get_unique_filename( $zip, $filename ) {
     274            $name = pathinfo( $filename, PATHINFO_FILENAME );
     275            $ext = pathinfo( $filename, PATHINFO_EXTENSION );
     276            $counter = 1;
     277            $unique_name = $filename;
     278
     279            while ( $zip->locateName( $unique_name ) !== false ) {
     280                $unique_name = $name . '_' . $counter . ( $ext ? '.' . $ext : '' );
     281                $counter++;
     282            }
     283
     284            return $unique_name;
     285        }
     286
     287        /**
     288         * Generate ZIP filename based on pattern
     289         */
     290        private function generate_zip_filename( $timestamp ) {
     291            $settings = get_option( 'mld_settings', array() );
     292            $pattern = $settings['zip_filename_pattern'] ?? 'media-library-download-{timestamp}';
     293           
     294            $current_user = wp_get_current_user();
     295            $replacements = array(
     296                '{timestamp}' => $timestamp,
     297                '{date}' => date( 'Y-m-d', $timestamp ),
     298                '{user}' => $current_user->user_login,
     299                '{userid}' => $current_user->ID,
     300            );
     301           
     302            $filename = str_replace( array_keys( $replacements ), array_values( $replacements ), $pattern );
     303            return sanitize_file_name( $filename );
     304        }
     305
     306        /**
     307         * Log download activity
     308         */
     309        private function log_download( $attachment_ids, $type ) {
     310            $settings = get_option( 'mld_settings', array() );
     311            if ( empty( $settings['enable_logging'] ) ) {
     312                return;
     313            }
     314
     315            $current_user = wp_get_current_user();
     316            $log_entry = array(
     317                'timestamp' => time(),
     318                'user' => $current_user->user_login,
     319                'user_id' => $current_user->ID,
     320                'file_count' => count( $attachment_ids ),
     321                'attachment_ids' => $attachment_ids,
     322                'type' => $type,
     323                'ip' => $this->get_user_ip(),
     324            );
     325
     326            $logs = get_option( 'mld_download_logs', array() );
     327            $logs[] = $log_entry;
     328
     329            // Keep only last 1000 entries
     330            if ( count( $logs ) > 1000 ) {
     331                $logs = array_slice( $logs, -1000 );
     332            }
     333
     334            update_option( 'mld_download_logs', $logs );
     335
     336            // Hook for developers
     337            do_action( 'mld_download_logged', $log_entry );
     338        }
     339
     340        /**
     341         * Get user IP address
     342         */
     343        private function get_user_ip() {
     344            if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
     345                return sanitize_text_field( wp_unslash( $_SERVER['HTTP_CLIENT_IP'] ) );
     346            } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
     347                return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
     348            } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
     349                return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
     350            }
     351            return 'unknown';
     352        }
     353
     354        /**
     355         * Schedule automatic cleanup
     356         */
     357        public function schedule_cleanup() {
     358            if ( ! wp_next_scheduled( 'mld_cleanup_temp_files' ) ) {
     359                $settings = get_option( 'mld_settings', array() );
     360                $interval = $settings['cleanup_interval'] ?? 24;
     361               
     362                wp_schedule_event( time(), 'hourly', 'mld_cleanup_temp_files' );
     363            }
     364           
     365            add_action( 'mld_cleanup_temp_files', array( $this, 'automatic_cleanup' ) );
     366        }
     367
     368        /**
     369         * Automatic cleanup via cron
     370         */
     371        public function automatic_cleanup() {
     372            $settings = get_option( 'mld_settings', array() );
     373            $cleanup_interval = $settings['cleanup_interval'] ?? 24;
     374            $cutoff_time = time() - ( $cleanup_interval * HOUR_IN_SECONDS );
     375
     376            if ( ! is_dir( MLD_TEMP_PATH ) ) {
     377                return;
     378            }
     379
     380            $files = glob( MLD_TEMP_PATH . '*.zip' );
     381            if ( ! $files ) {
     382                return;
     383            }
     384
     385            foreach ( $files as $file ) {
     386                if ( is_file( $file ) && filemtime( $file ) < $cutoff_time ) {
     387                    unlink( $file );
     388                }
     389            }
     390
     391            // Log cleanup if logging is enabled
     392            if ( ! empty( $settings['enable_logging'] ) ) {
     393                $log_entry = array(
     394                    'timestamp' => time(),
     395                    'user' => 'system',
     396                    'user_id' => 0,
     397                    'file_count' => 0,
     398                    'attachment_ids' => array(),
     399                    'type' => 'cleanup',
     400                    'ip' => 'system',
     401                );
     402
     403                $logs = get_option( 'mld_download_logs', array() );
     404                $logs[] = $log_entry;
     405                update_option( 'mld_download_logs', $logs );
     406            }
    95407        }
    96408    }
  • media-library-downloader/trunk/media-library-downloader.php

    r3281245 r3353021  
    44 * Plugin URI:        https://wordpress.org/plugins/media-library-downloader/
    55 * Description:       Download multiple media library files in one click !
    6  * Version:           1.3.3
     6 * Version:           1.4.0
    77 * Tags:              library, media, files, download, downloader, WordPress
    88 * Requires at least: 5.0 or higher
    99 * Requires PHP:      5.6
    10  * Tested up to:      6.6.1
    11  * Stable tag:        1.3.3
    12  * Author:            M . Code
     10 * Tested up to:      6.8.2
     11 * Stable tag:        1.4.0
     12 * Author:            Michael Revellin-Clerc
    1313 * License:           GPL v2 or later
    1414 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    15  * Contributors:      M . Code
     15 * Contributors:      Michael Revellin-Clerc
    1616 * Donate link:       https://ko-fi.com/devloper
    1717 */
     
    3434         */
    3535        public function __construct() {
     36            $this->define_constants();
     37            $this->setup_actions();
     38            $this->include_files();
     39        }
     40
     41        /**
     42         * Define plugin constants
     43         */
     44        private function define_constants() {
     45            define( 'MLD_VERSION', '1.4.0' );
    3646            define( 'MLD_PATH', plugin_dir_path( __FILE__ ) );
     47            define( 'MLD_URL', plugin_dir_url( __FILE__ ) );
    3748            define( 'MLD_BASENAME', plugin_basename( __FILE__ ) );
    3849            define( 'MLD_TEMP_PATH', plugin_dir_path( __FILE__ ) . 'temp/' );
    3950            define( 'MLD_TEMP_URL', plugin_dir_url( __FILE__ ) . 'temp/' );
    40             define( 'MLD_ASSETS_JS', plugin_dir_url(__FILE__ ) . '/assets/js/' );
    41             define( 'MLD_INCLUDES', plugin_dir_path(__FILE__ ) . 'includes/' );
    42             $this->setup_actions();
    43             $this->include_files();
     51            define( 'MLD_ASSETS_JS', plugin_dir_url( __FILE__ ) . 'assets/js/' );
     52            define( 'MLD_INCLUDES', plugin_dir_path( __FILE__ ) . 'includes/' );
    4453        }
    4554
     
    4958        public function setup_actions() {
    5059            register_activation_hook( __FILE__, array( $this, 'mld_check_requirements' ) );
     60            add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
     61            add_action( 'init', array( $this, 'mld_init' ) );
     62        }
     63
     64        /**
     65         * Load plugin textdomain
     66         */
     67        public function load_textdomain() {
     68            load_plugin_textdomain( 'media-library-downloader', false, dirname( MLD_BASENAME ) . '/languages' );
     69        }
     70
     71        /**
     72         * Initialize plugin
     73         */
     74        public function mld_init() {
     75            // Create temp directory if it doesn't exist
     76            if ( ! is_dir( MLD_TEMP_PATH ) ) {
     77                wp_mkdir_p( MLD_TEMP_PATH );
     78            }
    5179        }
    5280
     
    5886                array(
    5987                    'type' => 'module',
    60                     'link' => 'https://www.php.net/manual/fr/zip.installation.php',
     88                    'link' => 'https://www.php.net/manual/en/zip.installation.php',
    6189                    'name' => 'zip',
     90                    'required' => true,
    6291                ),
    6392                array(
    64                     'type' => 'module',
    65                     'link' => 'https://www.php.net/manual/fr/curl.installation.php',
     93                    'type' => 'module', 
     94                    'link' => 'https://www.php.net/manual/en/curl.installation.php',
    6695                    'name' => 'curl',
    67                 ),
    68                 array(
    69                     'type' => 'value',
    70                     'name' => 'allow_url_fopen',
     96                    'required' => false, // Not strictly required, fallback exists
    7197                ),
    7298            );
    7399
    74             if ( $requirements ) :
    75                 $loaded_php_extensions = get_loaded_extensions();
    76                 if ( $loaded_php_extensions ) :
    77                     foreach ( $requirements as $requirement ) :
    78                         $requirement_type = $requirement['type'];
    79                         $requirement_url  = $requirement['link'];
    80                         $requirement_name = $requirement['name'];
    81                         switch ( $requirement_type ) :
    82                             case 'module':
    83                                 if ( !in_array( $requirement_name, $loaded_php_extensions, true ) ) :
    84                                     echo '<div class="notice notice-error is-dismissible"><p>';
    85                                     printf( "Le module PHP <a href=' . $requirement_url . '><strong>$requirement_name</strong></a> n'est pas installé. <br> L'extension ne fonctionnera pas correctement. <br> Veuillez l'installer et réactiver le plugin." );
    86                                     echo '</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">Ignorer cette notification.</span></button></div>';
    87                                 endif;
    88                                 break;
    89                             case 'value':
    90                                 if ( !ini_get( $requirement_name ) ) :
    91                                     echo '<div class="notice notice-error is-dismissible"><p>';
    92                                     printf( '<strong>' . $requirement_name . '</strong> is not enable in the php.ini file, activate it and retry.' );
    93                                     echo '</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">Ignorer cette notification.</span></button></div>';
    94                                 endif;
    95                                 break;
    96                         endswitch;
    97                     endforeach;
    98                 endif;
    99             endif;
     100            $errors = array();
     101            $loaded_php_extensions = get_loaded_extensions();
     102
     103            foreach ( $requirements as $requirement ) {
     104                $requirement_type = $requirement['type'];
     105                $requirement_url = $requirement['link'];
     106                $requirement_name = $requirement['name'];
     107                $is_required = $requirement['required'];
     108
     109                switch ( $requirement_type ) {
     110                    case 'module':
     111                        if ( ! in_array( $requirement_name, $loaded_php_extensions, true ) ) {
     112                            if ( $is_required ) {
     113                                $errors[] = sprintf(
     114                                    /* translators: %1$s: requirement URL, %2$s: requirement name */
     115                                    __( 'The PHP module <a href="%1$s"><strong>%2$s</strong></a> is not installed. The plugin will not work correctly. Please install it and reactivate the plugin.', 'media-library-downloader' ),
     116                                    esc_url( $requirement_url ),
     117                                    esc_html( $requirement_name )
     118                                );
     119                            }
     120                        }
     121                        break;
     122                   
     123                    case 'value':
     124                        if ( ! ini_get( $requirement_name ) ) {
     125                            $errors[] = sprintf(
     126                                /* translators: %s: PHP setting name */
     127                                __( '<strong>%s</strong> is not enabled in the php.ini file. Please enable it and try again.', 'media-library-downloader' ),
     128                                esc_html( $requirement_name )
     129                            );
     130                        }
     131                        break;
     132                }
     133            }
     134
     135            // Display errors if any
     136            if ( ! empty( $errors ) ) {
     137                add_action( 'admin_notices', function() use ( $errors ) {
     138                    foreach ( $errors as $error ) {
     139                        echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post( $error ) . '</p></div>';
     140                    }
     141                });
     142            }
     143
     144            return empty( $errors );
    100145        }
    101146
     
    105150        public function include_files() {
    106151            require MLD_INCLUDES . 'class-main.php';
     152           
     153            // Include admin class only in admin area
     154            if ( is_admin() ) {
     155                require MLD_INCLUDES . 'class-admin.php';
     156            }
    107157        }
    108158    }
  • media-library-downloader/trunk/readme.txt

    r3281245 r3353021  
    22Contributors: devloper00
    33Donate link: https://ko-fi.com/devloper
    4 Tags: library, media, files, download, downloader, WordPress
     4Tags: library, media, files, download, downloader
    55Requires at least: 5.0 or higher
    6 Tested up to: 6.6.1
     6Tested up to: 6.8.2
    77Requires PHP: 5.6
    8 Stable tag: 1.3.3
     8Stable tag: 1.4.0
    99License: GPL v2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 Download multiples files / media in one click, from the WordPress media library
     12Professional media download solution with bulk operations, smart management, and enterprise-grade security for WordPress
    1313
    1414== Description ==
    1515
    16 Natively WordPress doesn't offer the possibility to download files directly from the WordPress media library. With that simple extension you can download any files you need!
     16Transform your WordPress media library into a powerful download center! While WordPress doesn't provide native file download capabilities, Media Library Downloader bridges this gap with enterprise-grade functionality and professional user experience.
    1717
    18 = Main features: =
     18Whether you need to download a single image or backup hundreds of media files, this plugin makes it effortless with intuitive one-click downloads, smart bulk operations, and advanced management features.
    1919
    20 * Download single / multiple files
    21 * Compatible with List / Grid view
    22 * AJAX Method (No reload)
     20= Core Features: =
     21
     22* **Single & Bulk Downloads** - Download individual files instantly or create ZIP archives from multiple selections
     23* **Universal Compatibility** - Works seamlessly with both List and Grid view layouts
     24* **Smart Download Management** - Automatic file organization with customizable naming patterns
     25* **Progress Indicators** - Real-time feedback during download preparation
     26* **AJAX Technology** - Lightning-fast downloads with no page refreshes
     27
     28= Advanced Features: =
     29
     30* **Admin Settings Dashboard** - Complete control panel for configuration and monitoring
     31* **Download Activity Logging** - Track usage patterns and generate statistics
     32* **Automatic Cleanup** - Scheduled maintenance to keep your server optimized
     33* **Security & Permissions** - Enterprise-grade access control and CSRF protection
     34* **Accessibility Compliant** - Full keyboard navigation and screen reader support
     35* **Developer Friendly** - Extensive hooks and filters for customization
     36
     37= Perfect For: =
     38
     39* **Content Creators** - Quickly download media for offline editing
     40* **Site Administrators** - Bulk backup and migration of media files 
     41* **Agencies** - Client asset delivery and portfolio management
     42* **Developers** - Media file management during development
     43* **Anyone** - Who needs efficient file access from WordPress media library
     44
     45Experience the difference of professional-grade media management with intuitive design and powerful functionality!
    2346
    2447== Installation ==
     
    27502. Activate the plugin through the 'Plugins' menu in WordPress
    2851
     52== How to Use ==
     53
     54Once the plugin is installed and activated, you can start downloading files from your media library immediately.
     55
     56= Downloading Single Files =
     57
     581. Go to your WordPress admin area
     592. Navigate to **Media > Library**
     603. You can use either List view or Grid view
     614. Locate the file you want to download
     625. Click the **Download** button that appears next to each media file
     636. The file will be downloaded to your computer
     64
     65= Downloading Multiple Files =
     66
     671. Go to **Media > Library**
     682. Switch to List view for easier bulk selection
     693. Use the checkboxes to select multiple files you want to download
     704. Click the **Bulk Download** button
     715. All selected files will be packaged and downloaded as a ZIP file
     72
     73= View Options =
     74
     75The plugin works seamlessly with both WordPress media library view options:
     76
     77* **List View**: Shows files in a table format with download buttons in each row
     78* **Grid View**: Displays files as thumbnails with download options accessible via hover or click
     79
     80= Plugin Settings =
     81
     821. Go to **Settings > Media Downloader** in your WordPress admin
     832. Configure maximum download size limits (default: 100MB)
     843. Set automatic cleanup intervals for temporary files (default: 24 hours)
     854. Enable download logging to track activity (optional)
     865. Customize ZIP filename patterns with placeholders like {timestamp}, {date}, {user}
     876. Use manual cleanup to remove temporary files immediately
     88
     89= Important Notes =
     90
     91* Downloads are processed using AJAX, so there's no page reload
     92* Large files or multiple file downloads may take a few moments to process
     93* Ensure your browser allows downloads from your WordPress site
     94* The plugin respects WordPress user permissions - only users with appropriate media access can download files
     95* Temporary ZIP files are automatically cleaned up based on your settings
     96* Download activity can be logged and viewed in the admin dashboard
     97
    2998== Changelog ==
     99
     100= 1.4.0 =
     101* **MAJOR UPDATE**: Complete security and feature overhaul
     102* Added CSRF protection with nonces for all AJAX requests
     103* Improved input validation and sanitization
     104* Added file access permission checks
     105* Enhanced security for temporary file handling
     106* **NEW**: Single file download functionality
     107* **NEW**: Download progress indicators and better user feedback
     108* **NEW**: Individual download buttons in both List and Grid views
     109* **NEW**: Admin settings page in Settings > Media Downloader
     110* **NEW**: Configurable maximum download size limits
     111* **NEW**: Automatic cleanup scheduling with configurable intervals 
     112* **NEW**: Optional download activity logging and statistics
     113* **NEW**: Custom ZIP filename patterns with placeholders
     114* **NEW**: Enhanced accessibility with ARIA labels and keyboard navigation
     115* **NEW**: Developer hooks and filters for extensibility
     116* Added manual cleanup option in admin settings
     117* Added download statistics dashboard
     118* Fixed critical logic bug in temp folder cleanup
     119* Optimized memory usage for large file downloads
     120* Added file size limits and duplicate filename handling
     121* Improved error handling with detailed messages
     122* Better internationalization support
     123* Modern JavaScript with ES6 class structure
     124* Added comprehensive code documentation
     125* Improved WordPress cron integration for automatic maintenance
     126* Enhanced user IP tracking for security logs
     127* Better error handling in admin interface
     128* Performance optimizations and WordPress coding standards compliance
    30129
    31130= 1.3.3 =
Note: See TracChangeset for help on using the changeset viewer.