Plugin Directory

Changeset 3368671


Ignore:
Timestamp:
09/26/2025 07:49:23 PM (6 months ago)
Author:
nuagelab
Message:

Reworked structure

Location:
safe-attachment-names/trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • safe-attachment-names/trunk/readme.txt

    r3151081 r3368671  
    44Tags: admin, attachment, accents, administration, latin, special characters, attachments
    55Requires at least: 5.0
    6 Tested up to: 6.6.2
     6Tested up to: 6.8.2
    77Stable tag: trunk
    88License: GPLv2 or later
  • safe-attachment-names/trunk/safe-attachment-names.php

    r3151081 r3368671  
    1919class safe_attachment_names {
    2020
    21     private static $_instance = null;
    22 
    23     /**
    24      * Bootstrap
    25      *
    26      * @author  Tommy Lacroix <tlacroix@nuagelab.com>
    27      * @access  public
    28      */
    29     public static function boot()
    30     {
    31         if (self::$_instance === null) {
    32             self::$_instance = new safe_attachment_names();
    33             self::$_instance->setup();
    34             return true;
    35         }
    36         return false;
    37     } // boot()
    38 
    39 
    40     /**
    41      * Setup plugin
    42      *
    43      * @author  Tommy Lacroix <tlacroix@nuagelab.com>
    44      * @access  public
    45      */
    46     public function setup()
    47     {
    48         global $current_blog;
    49 
    50         // Add admin menu
    51         add_action('admin_menu', array(&$this, 'add_admin_menu'));
    52 
    53         // Set wp_handle_upload_prefilter
    54         add_filter('wp_handle_upload_prefilter', array($this, 'upload_prefilter'));
    55 
    56         // Load text domain
    57         load_plugin_textdomain('safe-attachment-name', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/');
    58     } // setup()
    59 
    60 
    61     /**
    62      * Add admin menu action; added by setup()
    63      *
    64      * @author  Tommy Lacroix <tlacroix@nuagelab.com>
    65      * @access  public
    66      */
    67     public function add_admin_menu()
    68     {
    69         // This part is not ready yet
    70         //add_management_page(__("Sanitize attachment names",'safe-attachment-name'), __("Sanitize attachment names",'safe-attachment-name'), 'update_core', basename(__FILE__), array(&$this, 'admin_page'));
    71     } // add_admin_menu()
    72 
    73 
    74     /**
    75      * Admin page action; added by add_admin_menu()
    76      *
    77      * @author  Tommy Lacroix <tlacroix@nuagelab.com>
    78      * @access  public
    79      */
    80     public function admin_page()
    81     {
    82         if (isset($_POST['action'])) {
    83             if (wp_verify_nonce($_POST['nonce'],$_POST['action'])) {
    84                 $parts = explode('+',$_POST['action']);
    85                 switch ($parts[0]) {
    86                     case 'sanitize':
    87                         if (!$_POST['accept-terms']) {
    88                             $error_terms = true;
    89                         } else {
    90                             return $this->do_change();
    91                         }
    92                         break;
    93                 }
    94             }
    95         }
    96 
    97         if (!isset($error_terms)) $error_terms = false;
    98 
    99         echo '<div class="wrap">';
    100 
    101         echo '<div id="icon-tools" class="icon32"><br></div>';
    102         echo '<h2>'.__('Sanitize Attachment Names','safe-attachment-name').'</h2>';
    103         echo '<form method="post">';
    104 
    105         $action = 'sanitize+'.uniqid();
    106         wp_nonce_field($action,'nonce');
    107 
    108         echo '<input type="hidden" name="action" value="'.$action.'" />';
    109 
    110         echo '<table class="form-table">';
    111         echo '<tbody>';
    112 
    113         echo '<tr valign="top">';
    114         echo '<td colspan="2"><input type="checkbox" name="accept-terms" id="accept-terms" value="1" /> <label for="accept-terms"'.($error_terms?' style="color:red;font-weight:bold;"':'').'>'.__('I have backed up my database and will assume the responsability of any data loss or corruption.','safe-attachment-name').'</label></td>';
    115         echo '</tr>';
    116 
    117         echo '</tbody></table>';
    118 
    119         echo '<p class="submit"><input type="submit" name="submit" id="submit" class="button-primary" value="'.esc_html(__('Sanitize','safe-attachment-name')).'"></p>';
    120 
    121         echo '</form>';
    122 
    123         echo '</div>';
    124     } // admin_page()
    125 
    126 
    127     /**
    128      * Change domain. This is where the magic happens.
    129      * Called by admin_page() upon form submission.
    130      *
    131      * @author  Tommy Lacroix <tlacroix@nuagelab.com>
    132      * @access  private
    133      */
    134     private function do_change()
    135     {
    136         global $wpdb;
    137 
    138         @set_time_limit(0);
    139 
    140         echo '<div class="wrap">';
    141 
    142         echo '<div id="icon-tools" class="icon32"><br></div>';
    143         echo '<h2>Sanitizing attachment names</h2>';
    144         echo '<pre>';
    145 
    146         mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
    147         mysql_select_db(DB_NAME);
    148         mysql_query('SET NAMES '.DB_CHARSET);
    149         if (function_exists('mysql_set_charset')) mysql_set_charset(DB_CHARSET);
    150 
    151         $upload_dir = wp_upload_dir();
    152        
    153         $ret = mysql_query('SELECT * FROM '.$wpdb->prefix.'posts WHERE post_type="attachment";');
    154         while ($row = mysql_fetch_assoc($ret)) {
    155             $ret2 = mysql_query('SELECT * FROM '.$wpdb->prefix.'postmeta WHERE post_id='.intval($row['ID']).';');
    156             $metas = array();
    157             while ($row2 = mysql_fetch_assoc($ret2)) {
    158                 $metas[$row2['meta_key']] = $row2;
    159             }
    160            
    161             // Check if filename is problematic
    162             $fn1 = $metas['_wp_attached_file']['meta_value'];
    163             $fn2 = htmlentities($fn1, null, 'UTF-8');
    164 
    165             $fns = array($fn1, utf8_encode($fn1), utf8_decode($fn1));
    166 
    167             if ($fn1 != $fn2) {
    168                 $error = false;
    169                 $fn4 = $this->sanitizeFilename($fn1);
    170                
    171                 echo $fn1 . ' -> '.$fn4;
    172 
    173                 $fnx = false;
    174                 foreach ($fns as $x) {
    175                     if (file_exists($upload_dir['basedir'].'/'.$x)) {
    176                         $fnx = $x;
    177                         break;
    178                     }
    179                 }
    180                 if (!$fnx) {
    181                     _e(': error, cannot find source file.');
    182                     echo PHP_EOL;
    183                     $error = true;
    184                 } else {
    185                     _e( ': OK.' );
    186                     echo PHP_EOL;
    187                 }
    188 
    189                 // Rename file
    190                 $renames = array();
    191                 $renames[$upload_dir['basedir'].'/'.$fnx] = $upload_dir['basedir'].'/'.$fn4;
    192                
    193                 // Update _wp_attachment_metadata
    194                 $info = unserialize($metas['_wp_attachment_metadata']['meta_value']);
    195                 $info['file'] = $fn4;
    196 
    197                 if ($info['sizes']) {
    198                     foreach ( $info['sizes'] as &$size ) {
    199                         $sf1 = dirname( $fn1 ) . '/' . $size['file'];
    200                         $sf4 = $this->sanitizeFilename( $sf1 );
    201                         if ( $sf1 != $sf4 ) {
    202                             echo $sf1 . ' -> ' . $sf4;
    203 
    204                             $fns = array( $sf1, utf8_encode( $sf1 ), utf8_decode( $sf1 ) );
    205                             $sfx = false;
    206                             foreach ( $fns as $x ) {
    207                                 if ( file_exists( $upload_dir['basedir'] . '/' . $x ) ) {
    208                                     $sfx = $x;
    209                                     break;
    210                                 }
    211                             }
    212                             if ( ! $sfx ) {
    213                                 _e( ': error, cannot find source file.' );
    214                                 echo PHP_EOL;
    215                                 $error = true;
    216                             } else {
    217                                 _e( ': OK.' );
    218                                 echo PHP_EOL;
    219                             }
    220 
    221                             // Rename file
    222                             $renames[ $upload_dir['basedir'] . '/' . $sfx ] = $upload_dir['basedir'] . '/' . $sf4;
    223 
    224                             $size['file'] = basename( $sf4 );
    225                         }
    226                     }
    227                 }
    228 
    229                 if (!$error) {
    230                     // Rename all
    231                     foreach ( $renames as $f1 => $f2 ) {
    232                         rename( $f1, $f2 );
    233                     }
    234 
    235                     // Update _wp_attachment
    236                     mysql_query( 'UPDATE ' . $wpdb->prefix . 'postmeta SET meta_value="' . mysql_real_escape_string( $fn4 ) . '" WHERE meta_id=' . $metas['_wp_attached_file']['meta_id'] . ';' );
    237 
    238                     mysql_query( 'UPDATE ' . $wpdb->prefix . 'postmeta SET meta_value="' . mysql_real_escape_string( serialize( $info ) ) . '" WHERE meta_id=' . $metas['_wp_attachment_metadata']['meta_id'] . ';' );
    239 
    240                     // Update guid
    241                     mysql_query( 'UPDATE ' . $wpdb->prefix . 'posts SET guid="' . mysql_real_escape_string( $upload_dir['baseurl'] . '/' . $fn4 ) . '" WHERE post_id=' . $row['ID'] . ';' );
    242                 }
    243             } else {
    244                 //echo $fn1 . ' [ok]'.PHP_EOL;
    245             }
    246         }
    247 
    248         echo '</pre>';
    249         echo '<hr>';
    250         echo '<form method="post"><input type="submit" value="'.esc_html(__('Back','safe-attachment-name')).'" />';
    251     } // do_change()
    252 
    253 
    254     /**
    255      * Prefilter uploaded files to remove accents
    256      *
    257      * array    $file
    258      * return   array
    259      */
    260     public function upload_prefilter($file)
    261     {
    262         $file['name'] = $this->sanitizeFilename($file['name']);
    263         return $file;
    264     } // upload_prefilter()
    265 
    266 
    267     /**
    268      * Sanitize a file name
    269      *
    270      * @param   string    $fn1    File name
    271      * @return  string
    272      */
    273     private function sanitizeFilename($fn1) {
    274         $upload_dir = wp_upload_dir();
    275        
    276         if (!file_exists($upload_dir['basedir'].'/'.$fn1)) {
    277             //die('not found: '.$upload_dir['basedir'].'/'.$fn1);
    278         }
    279        
    280         $fn3 = strtolower($this->stripAccents($fn1));
    281         $parts = explode('/', $fn3);
    282         foreach ($parts as &$part) $part = preg_replace('/[^a-zA-Z0-9\-_\.]+/','_',$part);
    283        
    284         $fn4_old = $fn4 = implode('/', $parts);
    285         $i = 1;
    286         while (file_exists($upload_dir['basedir'].'/'.$fn4)) {
    287             $pi = pathinfo($fn4_old);
    288             $fn4 = $pi['dirname'].'/'.$pi['filename'].'-'.$i.'.'.$pi['extension'];
    289             $i++;
    290         }
    291        
    292         return $fn4;
    293     }
    294 
    295     /**
    296      * Strip accents from string and replace by the unaccented letter
    297      *
    298      * @param $stripAccents
    299      * @return string
    300      */
    301     private function stripAccents($stripAccents){
    302         return utf8_encode(strtr(utf8_decode($stripAccents),utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'),'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY'));
    303     } // stripAccents()
     21    private static $_instance = null;
     22
     23    /**
     24     * Bootstrap
     25     *
     26     * @author  Tommy Lacroix <tlacroix@nuagelab.com>
     27     * @access  public
     28     */
     29    public static function boot()
     30    {
     31        if (self::$_instance === null) {
     32            self::$_instance = new safe_attachment_names();
     33            self::$_instance->setup();
     34            return true;
     35        }
     36        return false;
     37    } // boot()
     38
     39
     40    /**
     41     * Setup plugin
     42     *
     43     * @author  Tommy Lacroix <tlacroix@nuagelab.com>
     44     * @access  public
     45     */
     46    public function setup()
     47    {
     48        global $current_blog;
     49
     50        // Add admin menu
     51        add_action('admin_menu', array(&$this, 'add_admin_menu'));
     52
     53        // Set wp_handle_upload_prefilter
     54        add_filter('wp_handle_upload_prefilter', array($this, 'upload_prefilter'));
     55
     56        // Load text domain
     57        load_plugin_textdomain('safe-attachment-name', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/');
     58    } // setup()
     59
     60
     61    /**
     62     * Add admin menu action; added by setup()
     63     *
     64     * @author  Tommy Lacroix <tlacroix@nuagelab.com>
     65     * @access  public
     66     */
     67    public function add_admin_menu()
     68    {
     69        // Check if user has proper capabilities
     70        if (current_user_can('manage_options')) {
     71            //add_management_page(__("Sanitize attachment names",'safe-attachment-name'), __("Sanitize attachment names",'safe-attachment-name'), 'manage_options', basename(__FILE__), array(&$this, 'admin_page'));
     72        }
     73    } // add_admin_menu()
     74
     75
     76    /**
     77     * Admin page action; added by add_admin_menu()
     78     *
     79     * @author  Tommy Lacroix <tlacroix@nuagelab.com>
     80     * @access  public
     81     */
     82    public function admin_page()
     83    {
     84        // Security check: verify user capabilities
     85        if (!current_user_can('manage_options')) {
     86            wp_die(__('You do not have sufficient permissions to access this page.'));
     87        }
     88
     89        if (isset($_POST['action'])) {
     90            // Sanitize and validate input
     91            $action = sanitize_text_field($_POST['action']);
     92            $nonce = sanitize_text_field($_POST['safe_attachment_nonce']);
     93
     94            // Enhanced CSRF protection verification
     95            if (wp_verify_nonce($nonce, $action) && check_admin_referer($action, 'safe_attachment_nonce')) {
     96                $parts = explode('+', $action);
     97                switch ($parts[0]) {
     98                    case 'sanitize':
     99                        $accept_terms = isset($_POST['accept-terms']) ? (bool) $_POST['accept-terms'] : false;
     100                        if (!$accept_terms) {
     101                            $error_terms = true;
     102                        } else {
     103                            return $this->do_change();
     104                        }
     105                        break;
     106                }
     107            }
     108        }
     109
     110        if (!isset($error_terms)) $error_terms = false;
     111
     112        echo '<div class="wrap">';
     113
     114        echo '<div id="icon-tools" class="icon32"><br></div>';
     115        echo '<h2>'.__('Sanitize Attachment Names','safe-attachment-name').'</h2>';
     116        echo '<form method="post">';
     117
     118        // Enhanced CSRF protection with specific action name
     119        $action = 'safe_attachment_sanitize_' . wp_create_nonce('safe_attachment_sanitize');
     120        wp_nonce_field($action, 'safe_attachment_nonce');
     121
     122        echo '<input type="hidden" name="action" value="' . esc_attr($action) . '" />';
     123
     124        echo '<table class="form-table">';
     125        echo '<tbody>';
     126
     127        echo '<tr valign="top">';
     128        echo '<td colspan="2"><input type="checkbox" name="accept-terms" id="accept-terms" value="1" /> <label for="accept-terms"'.($error_terms?' style="color:red;font-weight:bold;"':'').'>'.__('I have backed up my database and will assume the responsability of any data loss or corruption.','safe-attachment-name').'</label></td>';
     129        echo '</tr>';
     130
     131        echo '</tbody></table>';
     132
     133        echo '<p class="submit"><input type="submit" name="submit" id="submit" class="button-primary" value="'.esc_html(__('Sanitize','safe-attachment-name')).'"></p>';
     134
     135        echo '</form>';
     136
     137        echo '</div>';
     138    } // admin_page()
     139
     140
     141    /**
     142     * Change domain. This is where the magic happens.
     143     * Called by admin_page() upon form submission.
     144     *
     145     * @author  Tommy Lacroix <tlacroix@nuagelab.com>
     146     * @access  private
     147     */
     148    private function do_change()
     149    {
     150        global $wpdb;
     151
     152        @set_time_limit(0);
     153
     154        echo '<div class="wrap">';
     155
     156        echo '<div id="icon-tools" class="icon32"><br></div>';
     157        echo '<h2>Sanitizing attachment names</h2>';
     158        echo '<pre>';
     159
     160        $upload_dir = wp_upload_dir();
     161
     162        // Use WordPress $wpdb instead of deprecated mysql functions
     163        $attachments = $wpdb->get_results($wpdb->prepare(
     164            "SELECT * FROM {$wpdb->posts} WHERE post_type = %s",
     165            'attachment'
     166        ));
     167
     168        foreach ($attachments as $row) {
     169            $post_metas = $wpdb->get_results($wpdb->prepare(
     170                "SELECT * FROM {$wpdb->postmeta} WHERE post_id = %d",
     171                $row->ID
     172            ));
     173            $metas = array();
     174            foreach ($post_metas as $row2) {
     175                $metas[$row2->meta_key] = $row2;
     176            }
     177
     178            // Check if filename is problematic
     179            if (!isset($metas['_wp_attached_file'])) {
     180                continue; // Skip if no attached file meta
     181            }
     182            $fn1 = $metas['_wp_attached_file']->meta_value;
     183            $fn2 = htmlentities($fn1, null, 'UTF-8');
     184
     185            $fns = array($fn1, utf8_encode($fn1), utf8_decode($fn1));
     186
     187            if ($fn1 != $fn2) {
     188                $error = false;
     189                $fn4 = $this->sanitizeFilename($fn1);
     190
     191                echo $fn1 . ' -> '.$fn4;
     192
     193                $fnx = false;
     194                foreach ($fns as $x) {
     195                    if (file_exists($upload_dir['basedir'].'/'.$x)) {
     196                        $fnx = $x;
     197                        break;
     198                    }
     199                }
     200                if (!$fnx) {
     201                    _e(': error, cannot find source file.');
     202                    echo PHP_EOL;
     203                    $error = true;
     204                } else {
     205                    _e( ': OK.' );
     206                    echo PHP_EOL;
     207                }
     208
     209                // Rename file
     210                $renames = array();
     211                $renames[$upload_dir['basedir'].'/'.$fnx] = $upload_dir['basedir'].'/'.$fn4;
     212
     213                // Update _wp_attachment_metadata
     214                if (!isset($metas['_wp_attachment_metadata'])) {
     215                    continue; // Skip if no metadata
     216                }
     217                $info = maybe_unserialize($metas['_wp_attachment_metadata']->meta_value);
     218                $info['file'] = $fn4;
     219
     220                if ($info['sizes']) {
     221                    foreach ( $info['sizes'] as &$size ) {
     222                        $sf1 = dirname( $fn1 ) . '/' . $size['file'];
     223                        $sf4 = $this->sanitizeFilename( $sf1 );
     224                        if ( $sf1 != $sf4 ) {
     225                            echo $sf1 . ' -> ' . $sf4;
     226
     227                            $fns = array( $sf1, utf8_encode( $sf1 ), utf8_decode( $sf1 ) );
     228                            $sfx = false;
     229                            foreach ( $fns as $x ) {
     230                                if ( file_exists( $upload_dir['basedir'] . '/' . $x ) ) {
     231                                    $sfx = $x;
     232                                    break;
     233                                }
     234                            }
     235                            if ( ! $sfx ) {
     236                                _e( ': error, cannot find source file.' );
     237                                echo PHP_EOL;
     238                                $error = true;
     239                            } else {
     240                                _e( ': OK.' );
     241                                echo PHP_EOL;
     242                            }
     243
     244                            // Rename file
     245                            $renames[ $upload_dir['basedir'] . '/' . $sfx ] = $upload_dir['basedir'] . '/' . $sf4;
     246
     247                            $size['file'] = basename( $sf4 );
     248                        }
     249                    }
     250                }
     251
     252                if (!$error) {
     253                    // Initialize WordPress filesystem API
     254                    global $wp_filesystem;
     255                    if (empty($wp_filesystem)) {
     256                        require_once(ABSPATH . '/wp-admin/includes/file.php');
     257                        WP_Filesystem();
     258                    }
     259
     260                    // Rename all files using WordPress filesystem API
     261                    foreach ( $renames as $f1 => $f2 ) {
     262                        if ($wp_filesystem->exists($f1)) {
     263                            $wp_filesystem->move($f1, $f2);
     264                        }
     265                    }
     266
     267                    // Update _wp_attachment using WordPress $wpdb
     268                    $wpdb->update(
     269                        $wpdb->postmeta,
     270                        array('meta_value' => $fn4),
     271                        array('meta_id' => $metas['_wp_attached_file']->meta_id),
     272                        array('%s'),
     273                        array('%d')
     274                    );
     275
     276                    $wpdb->update(
     277                        $wpdb->postmeta,
     278                        array('meta_value' => maybe_serialize($info)),
     279                        array('meta_id' => $metas['_wp_attachment_metadata']->meta_id),
     280                        array('%s'),
     281                        array('%d')
     282                    );
     283
     284                    // Update guid
     285                    $wpdb->update(
     286                        $wpdb->posts,
     287                        array('guid' => $upload_dir['baseurl'] . '/' . $fn4),
     288                        array('ID' => $row->ID),
     289                        array('%s'),
     290                        array('%d')
     291                    );
     292                }
     293            } else {
     294                //echo $fn1 . ' [ok]'.PHP_EOL;
     295            }
     296        }
     297
     298        echo '</pre>';
     299        echo '<hr>';
     300        echo '<form method="post"><input type="submit" value="'.esc_html(__('Back','safe-attachment-name')).'" />';
     301    } // do_change()
     302
     303
     304    /**
     305     * Prefilter uploaded files to remove accents
     306     *
     307     * @param array $file The uploaded file array
     308     * @return array Modified file array
     309     */
     310    public function upload_prefilter($file)
     311    {
     312        // Validate file array structure
     313        if (!is_array($file) || !isset($file['name'])) {
     314            return $file;
     315        }
     316
     317        // Sanitize and process filename
     318        $file['name'] = sanitize_file_name($this->sanitizeFilename($file['name']));
     319        return $file;
     320    } // upload_prefilter()
     321
     322
     323    /**
     324     * Sanitize a file name
     325     *
     326     * @param   string    $fn1    File name
     327     * @return  string
     328     */
     329    private function sanitizeFilename($fn1) {
     330        $upload_dir = wp_upload_dir();
     331
     332        // Initialize WordPress filesystem API if needed
     333        global $wp_filesystem;
     334        if (empty($wp_filesystem)) {
     335            require_once(ABSPATH . '/wp-admin/includes/file.php');
     336            WP_Filesystem();
     337        }
     338
     339        $fn3 = strtolower($this->stripAccents($fn1));
     340        $parts = explode('/', $fn3);
     341        foreach ($parts as &$part) {
     342            $part = preg_replace('/[^a-zA-Z0-9\-_\.]+/','_', $part);
     343            $part = str_replace('_.', '.', $part);
     344        }
     345        $fn4_old = $fn4 = implode('/', $parts);
     346        $i = 1;
     347
     348        // Use WordPress filesystem API for file existence check
     349        while ($wp_filesystem->exists($upload_dir['basedir'].'/'.$fn4)) {
     350            $pi = pathinfo($fn4_old);
     351            $fn4 = $pi['dirname'].'/'.$pi['filename'].'-'.$i.'.'.$pi['extension'];
     352            $i++;
     353        }
     354
     355        return $fn4;
     356    }
     357
     358    /**
     359     * Strip accents from string and replace by the unaccented letter
     360     *
     361     * @param $stripAccents
     362     * @return string
     363     */
     364    private function stripAccents($stripAccents){
     365        return utf8_encode(strtr(utf8_decode($stripAccents),utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'),'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY'));
     366    } // stripAccents()
    304367
    305368} // safe_attachment_names class
Note: See TracChangeset for help on using the changeset viewer.