@@ -584,7 +584,7 @@ private static function extractArchive(string $filename, string $target): void
584584 'tar ' , 'xz ' , 'txz ' => f_passthru ("tar -xf {$ filename } -C {$ target } --strip-components 1 " ),
585585 'tgz ' , 'gz ' => f_passthru ("tar -xzf {$ filename } -C {$ target } --strip-components 1 " ),
586586 'bz2 ' => f_passthru ("tar -xjf {$ filename } -C {$ target } --strip-components 1 " ),
587- 'zip ' => f_passthru ( " unzip { $ filename} -d { $ target}" ),
587+ 'zip ' => self :: unzipWithStrip ( $ filename, $ target ),
588588 default => throw new FileSystemException ('unknown archive format: ' . $ filename ),
589589 };
590590 } elseif (PHP_OS_FAMILY === 'Windows ' ) {
@@ -599,7 +599,7 @@ private static function extractArchive(string $filename, string $target): void
599599 match (self ::extname ($ filename )) {
600600 'tar ' => f_passthru ("tar -xf {$ filename } -C {$ target } --strip-components 1 " ),
601601 'xz ' , 'txz ' , 'gz ' , 'tgz ' , 'bz2 ' => cmd ()->execWithResult ("\"{$ _7z }\" x -so {$ filename } | tar -f - -x -C \"{$ target }\" --strip-components 1 " ),
602- 'zip ' => f_passthru ( "\"{ $ _7z }\" x { $ filename} -o { $ target} -y " ),
602+ 'zip ' => self :: unzipWithStrip ( $ filename, $ target ),
603603 default => throw new FileSystemException ("unknown archive format: {$ filename }" ),
604604 };
605605 }
@@ -644,4 +644,59 @@ private static function extractWithType(string $source_type, string $filename, s
644644 SPC_SOURCE_LOCAL => symlink (self ::convertPath ($ filename ), $ extract_path ),
645645 };
646646 }
647+
648+ /**
649+ * Unzip file with stripping top-level directory
650+ */
651+ private static function unzipWithStrip (string $ zip_file , string $ extract_path ): void
652+ {
653+ $ temp_dir = self ::convertPath (sys_get_temp_dir () . '/spc_unzip_ ' . bin2hex (random_bytes (16 )));
654+ $ zip_file = self ::convertPath ($ zip_file );
655+ $ extract_path = self ::convertPath ($ extract_path );
656+
657+ // extract to temp dir
658+ self ::createDir ($ temp_dir );
659+
660+ if (PHP_OS_FAMILY === 'Windows ' ) {
661+ $ mute = defined ('DEBUG_MODE ' ) ? '' : ' > NUL ' ;
662+ // use php-sdk-binary-tools/bin/7za.exe
663+ $ _7z = self ::convertPath (getenv ('PHP_SDK_PATH ' ) . '/bin/7za.exe ' );
664+ f_passthru ("\"{$ _7z }\" x {$ zip_file } -o {$ temp_dir } -y {$ mute }" );
665+ } else {
666+ $ mute = defined ('DEBUG_MODE ' ) ? '' : ' > /dev/null ' ;
667+ f_passthru ("unzip \"{$ zip_file }\" -d \"{$ temp_dir }\"{$ mute }" );
668+ }
669+ // scan first level dirs (relative, not recursive, include dirs)
670+ $ contents = self ::scanDirFiles ($ temp_dir , false , true , true );
671+ if ($ contents === false ) {
672+ throw new FileSystemException ('Cannot scan unzip temp dir: ' . $ temp_dir );
673+ }
674+ // if extract path already exists, remove it
675+ if (is_dir ($ extract_path )) {
676+ self ::removeDir ($ extract_path );
677+ }
678+ // if only one dir, move its contents to extract_path using rename
679+ $ subdir = self ::convertPath ("{$ temp_dir }/ {$ contents [0 ]}" );
680+ if (count ($ contents ) === 1 && is_dir ($ subdir )) {
681+ rename ($ subdir , $ extract_path );
682+ } else {
683+ // else, move all contents to extract_path
684+ self ::createDir ($ extract_path );
685+ foreach ($ contents as $ item ) {
686+ $ subdir = self ::convertPath ("{$ temp_dir }/ {$ item }" );
687+ if (is_dir ($ subdir )) {
688+ // move all dir contents to extract_path (strip top-level)
689+ $ sub_contents = self ::scanDirFiles ($ subdir , false , true , true );
690+ if ($ sub_contents === false ) {
691+ throw new FileSystemException ('Cannot scan unzip temp sub-dir: ' . $ subdir );
692+ }
693+ foreach ($ sub_contents as $ sub_item ) {
694+ rename (self ::convertPath ("{$ subdir }/ {$ sub_item }" ), self ::convertPath ("{$ extract_path }/ {$ sub_item }" ));
695+ }
696+ } else {
697+ rename (self ::convertPath ("{$ temp_dir }/ {$ item }" ), self ::convertPath ("{$ extract_path }/ {$ item }" ));
698+ }
699+ }
700+ }
701+ }
647702}
0 commit comments