mirror of
https://github.com/getgrav/grav.git
synced 2025-02-20 19:56:53 +01:00
New bin/gpm direct-install command (#1038)
* initial push of DirectInstall command * Refactored to support direct-install * added info about dependencies, and continue question * Cleanup per @w00fz comments * put Grav destination check back.
This commit is contained in:
parent
7710cba7ad
commit
c57e43ea1d
1
bin/gpm
1
bin/gpm
|
|
@ -67,6 +67,7 @@ $app->addCommands(array(
|
|||
new \Grav\Console\Gpm\UninstallCommand(),
|
||||
new \Grav\Console\Gpm\UpdateCommand(),
|
||||
new \Grav\Console\Gpm\SelfupgradeCommand(),
|
||||
new \Grav\Console\Gpm\DirectInstallCommand(),
|
||||
));
|
||||
|
||||
$app->run();
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ abstract class Folder
|
|||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($recursive) {
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
|
||||
+ \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS;
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
|
|
@ -403,10 +403,7 @@ abstract class Folder
|
|||
|
||||
// If the destination directory does not exist create it
|
||||
if (!is_dir($dest)) {
|
||||
if (!mkdir($dest)) {
|
||||
// If the destination directory could not be created stop processing
|
||||
return false;
|
||||
}
|
||||
Folder::mkdir($dest);
|
||||
}
|
||||
|
||||
// Open the source directory to read in files
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ class Installer
|
|||
const ZIP_OPEN_ERROR = 32;
|
||||
/** @const Error while trying to extract the ZIP package */
|
||||
const ZIP_EXTRACT_ERROR = 64;
|
||||
/** @const Invalid source file */
|
||||
const INVALID_SOURCE = 128;
|
||||
|
||||
/**
|
||||
* Destination folder on which validation checks are applied
|
||||
|
|
@ -62,13 +64,13 @@ class Installer
|
|||
/**
|
||||
* Installs a given package to a given destination.
|
||||
*
|
||||
* @param string $package The local path to the ZIP package
|
||||
* @param string $zip the local path to ZIP package
|
||||
* @param string $destination The local path to the Grav Instance
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
* @param string $extracted The local path to the extacted ZIP package
|
||||
* @return bool True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function install($package, $destination, $options = [])
|
||||
public static function install($zip, $destination, $options = [], $extracted = null)
|
||||
{
|
||||
$destination = rtrim($destination, DS);
|
||||
$options = array_merge(self::$options, $options);
|
||||
|
|
@ -86,34 +88,26 @@ class Installer
|
|||
return false;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($package);
|
||||
// Create a tmp location
|
||||
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
|
||||
$tmp = $tmp_dir . '/Grav-' . uniqid();
|
||||
|
||||
if ($archive !== true) {
|
||||
self::$error = self::ZIP_OPEN_ERROR;
|
||||
if (!$extracted) {
|
||||
$extracted = self::unZip($zip, $tmp);
|
||||
if (!$extracted) {
|
||||
Folder::delete($tmp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($extracted)) {
|
||||
self::$error = self::INVALID_SOURCE;
|
||||
return false;
|
||||
}
|
||||
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$unzip = $zip->extractTo($tmp);
|
||||
|
||||
if (!$unzip) {
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
$zip->close();
|
||||
Folder::delete($tmp);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$package_folder_name = $zip->getNameIndex(0);
|
||||
$installer_file_folder = $tmp . '/' . $package_folder_name;
|
||||
|
||||
$is_install = true;
|
||||
$installer = self::loadInstaller($installer_file_folder, $is_install);
|
||||
$installer = self::loadInstaller($extracted, $is_install);
|
||||
|
||||
if (isset($options['is_update']) && $options['is_update'] === true) {
|
||||
$method = 'preUpdate';
|
||||
|
|
@ -135,16 +129,15 @@ class Installer
|
|||
|
||||
if (!$options['sophisticated']) {
|
||||
if ($options['theme']) {
|
||||
self::copyInstall($zip, $install_path, $tmp);
|
||||
self::copyInstall($extracted, $install_path);
|
||||
} else {
|
||||
self::moveInstall($zip, $install_path, $tmp);
|
||||
self::moveInstall($extracted, $install_path);
|
||||
}
|
||||
} else {
|
||||
self::sophisticatedInstall($zip, $install_path, $tmp);
|
||||
self::sophisticatedInstall($extracted, $install_path);
|
||||
}
|
||||
|
||||
Folder::delete($tmp);
|
||||
$zip->close();
|
||||
|
||||
if (isset($options['is_update']) && $options['is_update'] === true) {
|
||||
$method = 'postUpdate';
|
||||
|
|
@ -163,6 +156,43 @@ class Installer
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzip a file to somewhere
|
||||
*
|
||||
* @param $zip_file
|
||||
* @param $destination
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function unZip($zip_file, $destination)
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($zip_file);
|
||||
|
||||
if ($archive === true) {
|
||||
Folder::mkdir($destination);
|
||||
|
||||
$unzip = $zip->extractTo($destination);
|
||||
|
||||
|
||||
if (!$unzip) {
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
Folder::delete($destination);
|
||||
$zip->close();
|
||||
return false;
|
||||
}
|
||||
|
||||
$package_folder_name = $zip->getNameIndex(0);
|
||||
$zip->close();
|
||||
$extracted_folder = $destination . '/' . $package_folder_name;
|
||||
|
||||
return $extracted_folder;
|
||||
}
|
||||
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates and returns the package installer class
|
||||
*
|
||||
|
|
@ -211,80 +241,67 @@ class Installer
|
|||
}
|
||||
|
||||
/**
|
||||
* @param \ZipArchive $zip
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
* @param $tmp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function moveInstall(\ZipArchive $zip, $install_path, $tmp)
|
||||
public static function moveInstall($source_path, $install_path)
|
||||
{
|
||||
$container = $zip->getNameIndex(0);
|
||||
if (file_exists($install_path)) {
|
||||
Folder::delete($install_path);
|
||||
}
|
||||
|
||||
Folder::move($tmp . DS . $container, $install_path);
|
||||
Folder::move($source_path, $install_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ZipArchive $zip
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
* @param $tmp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function copyInstall(\ZipArchive $zip, $install_path, $tmp)
|
||||
public static function copyInstall($source_path, $install_path)
|
||||
{
|
||||
$firstDir = $zip->getNameIndex(0);
|
||||
if (empty($firstDir)) {
|
||||
throw new \RuntimeException("Directory $firstDir is missing");
|
||||
if (empty($source_path)) {
|
||||
throw new \RuntimeException("Directory $source_path is missing");
|
||||
} else {
|
||||
$tmp = realpath($tmp . DS . $firstDir);
|
||||
Folder::rcopy($tmp, $install_path);
|
||||
Folder::rcopy($source_path, $install_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ZipArchive $zip
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
* @param $tmp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sophisticatedInstall(\ZipArchive $zip, $install_path, $tmp)
|
||||
public static function sophisticatedInstall($source_path, $install_path)
|
||||
{
|
||||
for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
|
||||
$filename = $zip->getNameIndex($i);
|
||||
$fileinfo = pathinfo($filename);
|
||||
$depth = count(explode(DS, rtrim($filename, '/')));
|
||||
foreach (new \DirectoryIterator($source_path) as $file) {
|
||||
|
||||
if ($depth > 2) {
|
||||
if ($file->isLink() || $file->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $install_path . DS . $fileinfo['basename'];
|
||||
$path = $install_path . DS . $file->getBasename();
|
||||
|
||||
if (is_link($path)) {
|
||||
continue;
|
||||
} else {
|
||||
if (is_dir($path)) {
|
||||
Folder::delete($path);
|
||||
Folder::move($tmp . DS . $filename, $path);
|
||||
if ($file->isDir()) {
|
||||
Folder::delete($path);
|
||||
Folder::move($file->getPathname(), $path);
|
||||
|
||||
if ($fileinfo['basename'] == 'bin') {
|
||||
foreach (glob($path . DS . '*') as $file) {
|
||||
@chmod($file, 0755);
|
||||
}
|
||||
if ($file->getBasename() == 'bin') {
|
||||
foreach (glob($path . DS . '*') as $bin_file) {
|
||||
@chmod($bin_file, 0755);
|
||||
}
|
||||
} else {
|
||||
@unlink($path);
|
||||
@copy($tmp . DS . $filename, $path);
|
||||
}
|
||||
} else {
|
||||
@unlink($path);
|
||||
@copy($file->getPathname(), $path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
364
system/src/Grav/Console/Gpm/DirectInstallCommand.php
Normal file
364
system/src/Grav/Console/Gpm/DirectInstallCommand.php
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
<?php
|
||||
/**
|
||||
* @package Grav.Console
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class DirectInstallCommand extends ConsoleCommand
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("direct-install")
|
||||
->setAliases(['directinstall'])
|
||||
->addArgument(
|
||||
'package-file',
|
||||
InputArgument::REQUIRED,
|
||||
'The local location or remote URL to an installable package file'
|
||||
)
|
||||
->setDescription("Installs Grav, plugin, or theme directly from a file or a URL")
|
||||
->setHelp('The <info>direct-install</info> command installs Grav, plugin, or theme directly from a file or a URL');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
$package_file = $this->input->getArgument('package-file');
|
||||
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('Are you sure you want to direct-install <cyan>'.$package_file.'</cyan> [y|N] ', false);
|
||||
|
||||
$answer = $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln("exiting...");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
|
||||
$tmp_zip = $tmp_dir . '/Grav-' . uniqid();
|
||||
|
||||
$this->output->writeln("");
|
||||
$this->output->writeln("Preparing to install <cyan>" . $package_file . "</cyan>");
|
||||
|
||||
|
||||
if ($this->isRemote($package_file)) {
|
||||
$zip = $this->downloadPackage($package_file, $tmp_zip);
|
||||
} else {
|
||||
$zip = $this->copyPackage($package_file, $tmp_zip);
|
||||
}
|
||||
|
||||
if (file_exists($zip)) {
|
||||
$tmp_source = $tmp_dir . '/Grav-' . uniqid();
|
||||
|
||||
$this->output->write(" |- Extracting package... ");
|
||||
$extracted = Installer::unZip($zip, $tmp_source);
|
||||
|
||||
if (!$extracted) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Extracting package... <red>failed</red>");
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Extracting package... <green>ok</green>");
|
||||
|
||||
$type = $this->getPackageType($extracted);
|
||||
|
||||
if (!$type) {
|
||||
$this->output->writeln(" '- <red>ERROR: Not a valid Grav package</red>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$blueprint = $this->getBlueprints($extracted);
|
||||
if ($blueprint) {
|
||||
if (isset($blueprint['dependencies'])) {
|
||||
$depencencies = [];
|
||||
foreach ($blueprint['dependencies'] as $dependency) {
|
||||
if (is_array($dependency) && isset($dependency['name'])) {
|
||||
$depencencies[] = $dependency['name'];
|
||||
} else {
|
||||
$depencencies[] = $dependency;
|
||||
}
|
||||
}
|
||||
$this->output->writeln(" |- Dependencies found... <cyan>[" . implode(',', $depencencies) . "]</cyan>");
|
||||
|
||||
|
||||
|
||||
$question = new ConfirmationQuestion(" | '- Dependencies will not be satisfied. Continue ? [y|N] ", false);
|
||||
$answer = $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln("exiting...");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($type == 'grav') {
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
Installer::isValidDestination(GRAV_ROOT . '/system');
|
||||
if (Installer::IS_LINK === Installer::lastErrorCode()) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
|
||||
$this->output->writeln(" '- <red>ERROR: symlinks found...</red> <yellow>" . GRAV_ROOT."</yellow>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <green>ok</green>");
|
||||
|
||||
$this->output->write(" |- Installing package... ");
|
||||
Installer::install($zip, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
|
||||
} else {
|
||||
$name = $this->getPackageName($extracted);
|
||||
|
||||
if (!$name) {
|
||||
$this->output->writeln("<red>ERROR: Name could not be determined.</red> Please specify with --name|-n");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$install_path = $this->getInstallPath($type, $name);
|
||||
$is_update = file_exists($install_path);
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
|
||||
Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
|
||||
if (Installer::lastErrorCode() == Installer::IS_LINK) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
|
||||
$this->output->writeln(" '- <red>ERROR: symlink found...</red> <yellow>" . GRAV_ROOT . DS . $install_path . '</yellow>');
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
|
||||
} else {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <green>ok</green>");
|
||||
}
|
||||
|
||||
$this->output->write(" |- Installing package... ");
|
||||
|
||||
Installer::install($zip, GRAV_ROOT, ['install_path' => $install_path, 'theme' => (($type == 'theme')), 'is_update' => $is_update], $extracted);
|
||||
}
|
||||
|
||||
Folder::delete($tmp_source);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
|
||||
if(Installer::lastErrorCode()) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->writeln(" |- Installing package... <green>ok</green>");
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->output->writeln(" '- <red>ERROR: ZIP package could not be found</red>");
|
||||
}
|
||||
|
||||
Folder::delete($tmp_zip);
|
||||
|
||||
// clear cache after successful upgrade
|
||||
$this->clearCache();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the install path for a name and a particular type of package
|
||||
*
|
||||
* @param $type
|
||||
* @param $name
|
||||
* @return string
|
||||
*/
|
||||
protected function getInstallPath($type, $name)
|
||||
{
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($type == 'theme') {
|
||||
$install_path = $locator->findResource('themes://', false) . DS . $name;
|
||||
} else {
|
||||
$install_path = $locator->findResource('plugins://', false) . DS . $name;
|
||||
}
|
||||
return $install_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to guess the package name from the source files
|
||||
*
|
||||
* @param $source
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function getPackageName($source)
|
||||
{
|
||||
foreach (glob($source . "*.yaml") as $filename) {
|
||||
$name = strtolower(basename($filename, '.yaml'));
|
||||
if ($name == 'blueprints') {
|
||||
continue;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to guess the package type from the source files
|
||||
*
|
||||
* @param $source
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function getPackageType($source)
|
||||
{
|
||||
if (
|
||||
file_exists($source . 'system/defines.php') &&
|
||||
file_exists($source . 'system/config/system.yaml')
|
||||
) {
|
||||
return 'grav';
|
||||
} else {
|
||||
// must have a blueprint
|
||||
if (!file_exists($source . 'blueprints.yaml')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// either theme or plugin
|
||||
$name = basename($source);
|
||||
if (Utils::contains($name, 'theme')) {
|
||||
return 'theme';
|
||||
} elseif (Utils::contains($name, 'plugin')) {
|
||||
return 'plugin';
|
||||
}
|
||||
foreach (glob($source . "*.php") as $filename) {
|
||||
$contents = file_get_contents($filename);
|
||||
if (Utils::contains($contents, 'Grav\Common\Theme')) {
|
||||
return 'theme';
|
||||
} elseif (Utils::contains($contents, 'Grav\Common\Plugin')) {
|
||||
return 'plugin';
|
||||
}
|
||||
}
|
||||
|
||||
// Assume it's a theme
|
||||
return 'theme';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is a local or a remote file
|
||||
*
|
||||
* @param $file
|
||||
* @return bool
|
||||
*/
|
||||
protected function isRemote($file)
|
||||
{
|
||||
return (bool) filter_var($file, FILTER_VALIDATE_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find/Parse the blueprint file
|
||||
*
|
||||
* @param $source
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function getBlueprints($source)
|
||||
{
|
||||
$blueprint_file = $source . 'blueprints.yaml';
|
||||
if (!file_exists($blueprint_file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the zip package via the URL
|
||||
*
|
||||
* @param $package_file
|
||||
* @param $tmp
|
||||
* @return null|string
|
||||
*/
|
||||
private function downloadPackage($package_file, $tmp)
|
||||
{
|
||||
$this->output->write(" |- Downloading package... 0%");
|
||||
|
||||
$package = parse_url($package_file);
|
||||
|
||||
|
||||
$filename = basename($package['path']);
|
||||
$output = Response::get($package_file, [], [$this, 'progress']);
|
||||
|
||||
if ($output) {
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading package... 100%");
|
||||
$this->output->writeln('');
|
||||
|
||||
file_put_contents($tmp . DS . $filename, $output);
|
||||
|
||||
return $tmp . DS . $filename;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the local zip package to tmp
|
||||
*
|
||||
* @param $package_file
|
||||
* @param $tmp
|
||||
* @return null|string
|
||||
*/
|
||||
private function copyPackage($package_file, $tmp)
|
||||
{
|
||||
$this->output->write(" |- Copying package... 0%");
|
||||
|
||||
$package_file = realpath($package_file);
|
||||
|
||||
if (file_exists($package_file)) {
|
||||
$filename = basename($package_file);
|
||||
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Copying package... 100%");
|
||||
$this->output->writeln('');
|
||||
|
||||
copy(realpath($package_file), $tmp . DS . $filename);
|
||||
|
||||
return $tmp . DS . $filename;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user