<?php
namespace think\addons;
use fast\Http;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use think\Db;
use think\Exception;
use ZipArchive;
class Service
{
public static function download($name, $extend = [])
{
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
if (!is_dir($addonTmpDir)) {
@mkdir($addonTmpDir, 0755, true);
}
$tmpFile = $addonTmpDir . $name . ".zip";
$options = [
CURLOPT_CONNECTTIMEOUT => 30,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_HTTPHEADER => [
'X-REQUESTED-WITH: XMLHttpRequest'
]
];
$ret = Http::sendRequest(self::getServerUrl() . '/addon/download', array_merge(['name' => $name], $extend), 'GET', $options);
if ($ret['ret']) {
if (substr($ret['msg'], 0, 1) == '{') {
$json = (array)json_decode($ret['msg'], true);
if ($json['data'] && isset($json['data']['url'])) {
array_pop($options);
$ret = Http::sendRequest($json['data']['url'], [], 'GET', $options);
if (!$ret['ret']) {
throw new AddonException($json['msg'], $json['code'], $json['data']);
}
} else {
throw new AddonException($json['msg'], $json['code'], $json['data']);
}
}
if ($write = fopen($tmpFile, 'w')) {
fwrite($write, $ret['msg']);
fclose($write);
return $tmpFile;
}
throw new Exception("没有权限写入临时文件");
}
throw new Exception("无法下载远程文件");
}
public static function unzip($name)
{
$file = RUNTIME_PATH . 'addons' . DS . $name . '.zip';
$dir = ADDON_PATH . $name . DS;
if (class_exists('ZipArchive')) {
$zip = new ZipArchive;
if ($zip->open($file) !== TRUE) {
throw new Exception('Unable to open the zip file');
}
if (!$zip->extractTo($dir)) {
$zip->close();
throw new Exception('Unable to extract the file');
}
$zip->close();
return $dir;
}
throw new Exception("无法执行解压操作,请确保ZipArchive安装正确");
}
public static function backup($name)
{
$file = RUNTIME_PATH . 'addons' . DS . $name . '-backup-' . date("YmdHis") . '.zip';
$dir = ADDON_PATH . $name . DS;
if (class_exists('ZipArchive')) {
$zip = new ZipArchive;
$zip->open($file, ZipArchive::CREATE);
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
$filePath = $fileinfo->getPathName();
$localName = str_replace($dir, '', $filePath);
if ($fileinfo->isFile()) {
$zip->addFile($filePath, $localName);
} elseif ($fileinfo->isDir()) {
$zip->addEmptyDir($localName);
}
}
$zip->close();
return true;
}
throw new Exception("无法执行压缩操作,请确保ZipArchive安装正确");
}
public static function check($name)
{
if (!$name || !is_dir(ADDON_PATH . $name)) {
throw new Exception('Addon not exists');
}
$addonClass = get_addon_class($name);
if (!$addonClass) {
throw new Exception("插件主启动程序不存在");
}
$addon = new $addonClass();
if (!$addon->checkInfo()) {
throw new Exception("配置文件不完整");
}
return true;
}
public static function noconflict($name)
{
$list = self::getGlobalFiles($name, true);
if ($list) {
throw new AddonException("发现冲突文件", -3, ['conflictlist' => $list]);
}
return true;
}
public static function importsql($name)
{
$sqlFile = ADDON_PATH . $name . DS . 'install.sql';
if (is_file($sqlFile)) {
$lines = file($sqlFile);
$templine = '';
foreach ($lines as $line) {
if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '
public static function refresh()
{
$addons = get_addon_list();
$bootstrapArr = [];
foreach ($addons as $name => $addon) {
$bootstrapFile = ADDON_PATH . $name . DS . 'bootstrap.js';
if ($addon['state'] && is_file($bootstrapFile)) {
$bootstrapArr[] = file_get_contents($bootstrapFile);
}
}
$addonsFile = ROOT_PATH . str_replace("/", DS, "public/assets/js/addons.js");
if ($handle = fopen($addonsFile, 'w')) {
$tpl = <<<EOD
define([], function () {
{__JS__}
});
EOD;
fwrite($handle, str_replace("{__JS__}", implode("\n", $bootstrapArr), $tpl));
fclose($handle);
} else {
throw new Exception("addons.js文件没有写入权限");
}
$file = APP_PATH . 'extra' . DS . 'addons.php';
$config = get_addon_autoload_config(true);
if ($config['autoload'])
return;
if (!is_really_writable($file)) {
throw new Exception("addons.php文件没有写入权限");
}
if ($handle = fopen($file, 'w')) {
fwrite($handle, "<?php\n\n" . "return " . var_export($config, TRUE) . ";");
fclose($handle);
} else {
throw new Exception("文件没有写入权限");
}
return true;
}
public static function install($name, $force = false, $extend = [])
{
if (!$name || (is_dir(ADDON_PATH . $name) && !$force)) {
throw new Exception('Addon already exists');
}
$tmpFile = Service::download($name, $extend);
$addonDir = Service::unzip($name);
@unlink($tmpFile);
try {
Service::check($name);
if (!$force) {
Service::noconflict($name);
}
} catch (AddonException $e) {
@rmdirs($addonDir);
throw new AddonException($e->getMessage(), $e->getCode(), $e->getData());
} catch (Exception $e) {
@rmdirs($addonDir);
throw new Exception($e->getMessage());
}
$sourceAssetsDir = self::getSourceAssetsDir($name);
$destAssetsDir = self::getDestAssetsDir($name);
if (is_dir($sourceAssetsDir)) {
copydirs($sourceAssetsDir, $destAssetsDir);
}
foreach (self::getCheckDirs() as $k => $dir) {
if (is_dir($addonDir . $dir)) {
copydirs($addonDir . $dir, ROOT_PATH . $dir);
}
}
try {
$info = get_addon_info($name);
if (!$info['state']) {
$info['state'] = 1;
set_addon_info($name, $info);
}
$class = get_addon_class($name);
if (class_exists($class)) {
$addon = new $class();
$addon->install();
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
Service::importsql($name);
Service::refresh();
return true;
}
public static function uninstall($name, $force = false)
{
if (!$name || !is_dir(ADDON_PATH . $name)) {
throw new Exception('Addon not exists');
}
if (!$force) {
Service::noconflict($name);
}
$destAssetsDir = self::getDestAssetsDir($name);
if (is_dir($destAssetsDir)) {
rmdirs($destAssetsDir);
}
if ($force) {
$list = Service::getGlobalFiles($name);
foreach ($list as $k => $v) {
@unlink(ROOT_PATH . $v);
}
}
try {
$class = get_addon_class($name);
if (class_exists($class)) {
$addon = new $class();
$addon->uninstall();
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
rmdirs(ADDON_PATH . $name);
Service::refresh();
return true;
}
public static function enable($name, $force = false)
{
if (!$name || !is_dir(ADDON_PATH . $name)) {
throw new Exception('Addon not exists');
}
if (!$force) {
Service::noconflict($name);
}
$addonDir = ADDON_PATH . $name . DS;
$sourceAssetsDir = self::getSourceAssetsDir($name);
$destAssetsDir = self::getDestAssetsDir($name);
if (is_dir($sourceAssetsDir)) {
copydirs($sourceAssetsDir, $destAssetsDir);
}
foreach (self::getCheckDirs() as $k => $dir) {
if (is_dir($addonDir . $dir)) {
copydirs($addonDir . $dir, ROOT_PATH . $dir);
}
}
try {
$class = get_addon_class($name);
if (class_exists($class)) {
$addon = new $class();
if (method_exists($class, "enable")) {
$addon->enable();
}
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$info = get_addon_info($name);
$info['state'] = 1;
unset($info['url']);
set_addon_info($name, $info);
Service::refresh();
return true;
}
public static function disable($name, $force = false)
{
if (!$name || !is_dir(ADDON_PATH . $name)) {
throw new Exception('Addon not exists');
}
if (!$force) {
Service::noconflict($name);
}
$destAssetsDir = self::getDestAssetsDir($name);
if (is_dir($destAssetsDir)) {
rmdirs($destAssetsDir);
}
$list = Service::getGlobalFiles($name);
foreach ($list as $k => $v) {
@unlink(ROOT_PATH . $v);
}
$info = get_addon_info($name);
$info['state'] = 0;
unset($info['url']);
set_addon_info($name, $info);
try {
$class = get_addon_class($name);
if (class_exists($class)) {
$addon = new $class();
if (method_exists($class, "disable")) {
$addon->disable();
}
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
Service::refresh();
return true;
}
public static function upgrade($name, $extend = [])
{
$info = get_addon_info($name);
if ($info['state']) {
throw new Exception(__('Please disable addon first'));
}
$config = get_addon_config($name);
if ($config) {
}
Service::backup($name);
$tmpFile = Service::download($name, $extend);
$addonDir = Service::unzip($name);
@unlink($tmpFile);
if ($config) {
set_addon_config($name, $config);
}
Service::importsql($name);
try {
$class = get_addon_class($name);
if (class_exists($class)) {
$addon = new $class();
if (method_exists($class, "upgrade")) {
$addon->upgrade();
}
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
Service::refresh();
return true;
}
public static function getGlobalFiles($name, $onlyconflict = false)
{
$list = [];
$addonDir = ADDON_PATH . $name . DS;
foreach (self::getCheckDirs() as $k => $dir) {
$checkDir = ROOT_PATH . DS . $dir . DS;
if (!is_dir($checkDir))
continue;
if (is_dir($addonDir . $dir)) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($addonDir . $dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
if ($fileinfo->isFile()) {
$filePath = $fileinfo->getPathName();
$path = str_replace($addonDir, '', $filePath);
if ($onlyconflict) {
$destPath = ROOT_PATH . $path;
if (is_file($destPath)) {
if (filesize($filePath) != filesize($destPath) || md5_file($filePath) != md5_file($destPath)) {
$list[] = $path;
}
}
} else {
$list[] = $path;
}
}
}
}
}
return $list;
}
protected static function getSourceAssetsDir($name)
{
return ADDON_PATH . $name . DS . 'assets' . DS;
}
protected static function getDestAssetsDir($name)
{
$assetsDir = ROOT_PATH . str_replace("/", DS, "public/assets/addons/{$name}/");
if (!is_dir($assetsDir)) {
mkdir($assetsDir, 0755, true);
}
return $assetsDir;
}
protected static function getServerUrl()
{
return config('fastadmin.api_url');
}
protected static function getCheckDirs()
{
return [
'application',
'public'
];
}
}