Reputation: 1181
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?
Upvotes: 1
Views: 447
Reputation: 46620
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):
<?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);
}
}
?>
Upvotes: 1
Reputation: 26075
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;
}
Upvotes: 0