Files
YwxAppThink/ywxapp/library/TemplateInstaller.php
T

107 lines
3.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 模板皮肤安装器(后台「安装模板」/ appmall 下载后部署的接入点)。
*
* 模板包 zip 由 scripts/package_template.php 产出,内部两段前缀:
* - templates/<name>/... → 部署到 <root>/templates/<name>/
* - static/... → 部署到 <root>/public/static/templates/<name>/
*
* 模板不携带 DB 迁移,安装即文件落地;与插件(Addon)的 importsql 机制解耦。
*/
namespace ywxapp\library;
class TemplateInstaller
{
/**
* 从 zip 部署模板到项目。
*
* @param string $zipPath 模板包 zip 绝对路径
* @param string $rootDir 项目根目录(含 templates/ 与 public/
* @return array 部署后的清单 ['name','version','target_addon','tpl_dir','static_dir']
* @throws \RuntimeException
*/
public static function install(string $zipPath, string $rootDir): array
{
if (!is_file($zipPath)) {
throw new \RuntimeException("模板包不存在: {$zipPath}");
}
if (!class_exists('ZipArchive')) {
throw new \RuntimeException('ZipArchive 扩展不可用');
}
$zip = new ZipArchive();
if ($zip->open($zipPath) !== true) {
throw new \RuntimeException("无法打开模板包: {$zipPath}");
}
// 先探测模板名(templates/<name>/ 前缀)
$name = '';
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
if (preg_match('#^templates/([^/]+)/#', $entry, $m)) {
$name = $m[1];
break;
}
}
if ($name === '') {
$zip->close();
throw new \RuntimeException('zip 中未找到 templates/<name>/ 结构,可能不是合法模板包');
}
$tplTarget = rtrim($rootDir, '/\\') . '/templates/' . $name . '/';
$staticTarget = rtrim($rootDir, '/\\') . '/public/static/templates/' . $name . '/';
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
if ($entry === '' || $entry === null) {
continue;
}
$prefix = 'templates/' . $name . '/';
if (strpos($entry, $prefix) === 0) {
self::writeEntry($zip, $entry, $tplTarget . substr($entry, strlen($prefix)));
} elseif (strpos($entry, 'static/') === 0) {
$rel = substr($entry, strlen('static/'));
if ($rel === '') {
continue;
}
self::writeEntry($zip, $entry, $staticTarget . $rel);
}
}
$zip->close();
$metaFile = $tplTarget . 'template.json';
$meta = is_file($metaFile) ? (json_decode(file_get_contents($metaFile), true) ?? []) : [];
return [
'name' => $name,
'version' => $meta['version'] ?? '',
'title' => $meta['title'] ?? $name,
'target_addon' => $meta['target_addon'] ?? [],
'tpl_dir' => $tplTarget,
'static_dir' => $staticTarget,
];
}
/**
* 将 zip 内单个条目写出到目标路径(目录建目录,文件写内容)。
*/
protected static function writeEntry(ZipArchive $zip, string $entry, string $target): void
{
if (substr($entry, -1) === '/') {
if (!is_dir($target)) {
@mkdir($target, 0755, true);
}
return;
}
$dir = dirname($target);
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
$content = $zip->getFromName($entry);
if ($content === false) {
return;
}
file_put_contents($target, $content);
}
}