Zip Download ZIP Changes Folder Name of Wordpress

2019-08-13 12:16发布

问题:

I've been trying to solve this but can't seem to find anything on it.

When you "Download ZIP" from github, it doesn't give you the "theme_name.zip" that you'd hope for, but rather "theme_name-master.zip", which when average users install this the messed up folder name throws off child themes.

How can this be remedied, so that the zip download does not change anything?

回答1:

For simplicity, You could host a script somewhere that downloads the script, renames the folder without the -master and then rezip's the project, then send it to the user as a download.

So something like (Requires PHP5 >= 5.2.0, cURL, ZipArchive, safe_mode & open_basedir off):

Fork it from GitHub ;p

<?php
//Example Usage
new GitDL('https://github.com/lcherone/GitDL');

/**
 * GitHub Project/Repository Downloader proxy.
 * This class will handle downloading, removing master folder prefix, 
 * repacking and proxying back the project as a download.
 * 
 * @author Lawrence Cherone
 * @version 0.2
 */
class GitDL{
    // project files working directory - automatically created
    const PWD = "./project_files/";

    /**
     * Class construct.
     *
     * @param string $url
     */
    function __construct($url=null){
        // check construct argument
        if(!$url) die('Class Error: Missing construct argument: $url');

        // fix trailing slash if any
        $url = rtrim($url, '/');

        // assign class properties
        $this->project     = basename($url);
        $this->project_url = $url.'/archive/master.zip';
        $this->tmp_file    = md5($url).'.zip';

        // make project working folder
        if(!file_exists(self::PWD)){
            mkdir(self::PWD.md5($url), 0775, true);
        }

        // get project zip from GitHub
        try{
            $this->get_project();
        }catch(Exception $e){
            die($e->getMessage());
        }

        // extract project zip from git
        $this->extract(self::PWD.$this->tmp_file, self::PWD.md5($url));

        // remove the master part, by renaming
        rename(self::PWD.md5($url).'/'.$this->project.'-master', self::PWD.md5($url).'/'.$this->project);

        // rezip project files
        $this->zipcreate(self::PWD.md5($url), self::PWD.'new_'.$this->tmp_file);

        // send zip to user
        header('Content-Description: File Transfer');
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="'.$this->project.'.zip"');
        header('Content-Transfer-Encoding: binary');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: '.sprintf("%u", filesize(self::PWD.'new_'.$this->tmp_file)));
        readfile(self::PWD.'new_'.$this->tmp_file);

        // cleanup
        $this->destroy_dir(self::PWD.md5($url));
        unlink(self::PWD.$this->tmp_file);
        unlink(self::PWD.'new_'.$this->tmp_file);
    }

    /**
     * cURL GitHub project downloader. 
     * No support for open base dir/safe mode as there is a GitHub redirect to there CDN
     * a HEAD pre-check is done to check project exists,
     * project zip is written directly to the file.
     */
    function get_project(){
        // check curl installed
        if(!function_exists('curl_init')){
            throw new Exception('cURL Error: You must have cURL installed to use this class.');
        }
        // check for unsupported settings
        if (ini_get('open_basedir') != '' || ini_get('safe_mode') == 'On'){
            throw new Exception('cURL Error: safe_mode or an open_basedir is enabled, class not supported.');
        }

        // HEAD request - To verify the project exists
        $ch = curl_init();
        curl_setopt_array($ch, array(
            CURLOPT_URL => $this->project_url,
            CURLOPT_TIMEOUT => 5,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_FAILONERROR => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_BINARYTRANSFER => true,
            CURLOPT_HEADER => false,
            CURLOPT_NOBODY => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSL_VERIFYPEER => false,
        ));

        // lets grab it
        if(curl_exec($ch) !== false){
            $fp = fopen(self::PWD.$this->tmp_file, 'a+b');
            if(flock($fp, LOCK_EX | LOCK_NB)){
                // empty possible contents
                ftruncate($fp, 0);
                rewind($fp);

                // HTTP GET request - write directly to the file
                $ch = curl_init();
                curl_setopt_array($ch, array(
                    CURLOPT_URL => $this->project_url,
                    CURLOPT_TIMEOUT => 5,
                    CURLOPT_CONNECTTIMEOUT => 5,
                    CURLOPT_FAILONERROR => true,
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_BINARYTRANSFER => true,
                    CURLOPT_HEADER => false,
                    CURLOPT_FILE => $fp,
                    CURLOPT_FOLLOWLOCATION => true,
                    CURLOPT_SSL_VERIFYHOST => false,
                    CURLOPT_SSL_VERIFYPEER => false,
                ));

                // transfer failed
                if(curl_exec($ch) === false){
                    ftruncate($fp, 0);
                    throw new Exception('cURL Error: transfer failed.');
                }
                fflush($fp);
                flock($fp, LOCK_UN);
                curl_close($ch);
            }
            fclose($fp);
        }else{
            curl_close($ch);
            throw new Exception('Error: '.htmlentities($this->project).' project not found on GitHub');
        }
    }

    /**
     * Create zip from extracted/fixed project.
     *
     * @uses ZipArchive
     * @uses RecursiveIteratorIterator
     * @param string $source
     * @param string $destination
     * @return bool
     */
    function zipcreate($source, $destination) {
        if (!extension_loaded('zip') || !file_exists($source)) {
            return false;
        }
        $zip = new ZipArchive();
        if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
            return false;
        }
        $source = str_replace('\\', '/', realpath($source));
        if (is_dir($source) === true) {
            $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
            foreach ($files as $file) {
                $file = str_replace('\\', '/', realpath($file));
                if (is_dir($file) === true) {
                    $zip->addEmptyDir(str_replace($source.'/', '', $file.'/'));
                } else if (is_file($file) === true) {
                    $zip->addFromString(str_replace($source.'/', '', $file), file_get_contents($file));
                }
            }
        }
        return $zip->close();
    }

    /**
     * Extract Zip file
     *
     * @uses ZipArchive
     * @param string $source
     * @param string $destination
     * @return bool
     */
    function extract($source, $destination){
        $zip = new ZipArchive;
        if($zip->open($source) === TRUE) {
            $zip->extractTo($destination);
            $zip->close();
            return true;
        } else {
            return false;
        }
    }

    /**
     * Recursive directory remover/deleter
     *
     * @uses RecursiveIteratorIterator
     * @param string $dir
     * @return bool
     */
    function destroy_dir($dir) {
        foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $path) {
            $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname());
        }
        return rmdir($dir);
    }

}
?>


回答2:

That's a bit problematic and I don't think there's a real fix, only workarounds. I faced the same problem with plugins and have only a partial solution: it works only on plugin/theme updates, not when installing.

Never tried, but maybe it's possible to perform the folder rename on plugin (register_activation_hook) or theme (after_switch_theme) activation.

I've checked the core and the filter upgrader_source_selection works for themes and plugins. The repo slug is in GitHub's URL: http://github.com/user/repo-name.

public static $repo_slug = 'repo-name';

add_filter( 'upgrader_source_selection', array( $this, 'rename_github_zip' ), 1, 3 );

/**
 * Removes the prefix "-master" when updating from GitHub zip files
 * 
 * See: https://github.com/YahnisElsts/plugin-update-checker/issues/1
 * 
 * @param string $source
 * @param string $remote_source
 * @param object $thiz
 * @return string
 */
public function rename_github_zip( $source, $remote_source, $thiz )
{
    if(  strpos( $source, self::$repo_slug ) === false )
        return $source;

    $path_parts = pathinfo($source);
    $newsource = trailingslashit($path_parts['dirname']). trailingslashit( self::$repo_slug );
    rename($source, $newsource);
    return $newsource;
}