OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
456
app/Core/UI/ViewsServiceProvider.php
Normal file
456
app/Core/UI/ViewsServiceProvider.php
Normal file
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\UI;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\Compilers\BladeCompiler;
|
||||
use Illuminate\View\DynamicComponent;
|
||||
use Illuminate\View\Engines\CompilerEngine;
|
||||
use Illuminate\View\Engines\EngineResolver;
|
||||
use Illuminate\View\Engines\FileEngine;
|
||||
use Illuminate\View\Engines\PhpEngine;
|
||||
use Illuminate\View\Factory;
|
||||
use Illuminate\View\FileViewFinder;
|
||||
use Illuminate\View\ViewServiceProvider as LaravelViewServiceProvider;
|
||||
use Leantime\Core\Sessions\PathManifestRepository;
|
||||
|
||||
class ViewsServiceProvider extends LaravelViewServiceProvider
|
||||
{
|
||||
protected $viewPaths;
|
||||
|
||||
protected $pathRepo;
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
|
||||
$this->app['config']->set('view.compiled', storage_path('framework/views'));
|
||||
$this->app['config']->set('view.cache', true);
|
||||
$this->app['config']->set('view.compiled_extension', 'php');
|
||||
|
||||
$this->registerFactory();
|
||||
$this->registerViewFinder();
|
||||
$this->registerBladeCompiler();
|
||||
$this->registerEngineResolver();
|
||||
|
||||
$this->app->terminating(static function () {
|
||||
\Illuminate\View\Component::flushCache();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the view environment.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerFactory()
|
||||
{
|
||||
$this->app->singleton('view', function ($app) {
|
||||
// Next we need to grab the engine resolver instance that will be used by the
|
||||
// environment. The resolver will be used by an environment to get each of
|
||||
// the various engine implementations such as plain PHP or Blade engine.
|
||||
$resolver = $app['view.engine.resolver'];
|
||||
|
||||
$finder = $app['view.finder'];
|
||||
|
||||
$factory = $this->createFactory($resolver, $finder, $app['events']);
|
||||
|
||||
// Legacy template extensions — still needed for plugins with .tpl.php/.sub.php/.inc.php
|
||||
array_map(fn ($ext) => $factory->addExtension($ext, 'php'), ['inc.php', 'sub.php', 'tpl.php']);
|
||||
|
||||
// Use blade engine for all things blade (registered last to take priority)
|
||||
$factory->addExtension('blade.php', 'blade');
|
||||
|
||||
// We will also set the container instance on this view environment since the
|
||||
// view composers may be classes registered in the container, which allows
|
||||
// for great testable, flexible composers for the application developer.
|
||||
$factory->setContainer(app());
|
||||
|
||||
$factory->share('app', $app);
|
||||
|
||||
// Find and set composers
|
||||
$composers = $this->getComposerPaths();
|
||||
foreach ($composers as $key => $composerClass) {
|
||||
if (
|
||||
(is_subclass_of($composerClass, \Leantime\Core\UI\Composer::class)
|
||||
|| is_subclass_of($composerClass, \Leantime\Core\Controller\Composer::class))
|
||||
&&
|
||||
|
||||
! (new \ReflectionClass($composerClass))->isAbstract()
|
||||
) {
|
||||
$factory->composer($composerClass::$views, $composerClass);
|
||||
}
|
||||
}
|
||||
|
||||
$app->terminating(static function () {
|
||||
\Illuminate\View\Component::forgetFactory();
|
||||
});
|
||||
|
||||
return $factory;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Factory Instance.
|
||||
*
|
||||
* @param \Illuminate\View\Engines\EngineResolver $resolver
|
||||
* @param \Illuminate\View\ViewFinderInterface $finder
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
* @return \Illuminate\View\Factory
|
||||
*/
|
||||
protected function createFactory($resolver, $finder, $events)
|
||||
{
|
||||
return new Factory($resolver, $finder, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the view finder implementation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerViewFinder()
|
||||
{
|
||||
$this->app->bind('view.finder', function ($app) {
|
||||
|
||||
$fileViewFinder = new FileViewFinder($app['files'], []);
|
||||
|
||||
$this->viewPaths = $this->getViewPaths();
|
||||
|
||||
array_map([$fileViewFinder, 'addNamespace'], array_keys($this->viewPaths), array_values($this->viewPaths));
|
||||
|
||||
return $fileViewFinder;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Blade compiler implementation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerBladeCompiler()
|
||||
{
|
||||
$this->app->singleton('blade.compiler', function ($app) {
|
||||
$compiler = new BladeCompiler(
|
||||
$app['files'],
|
||||
$app['config']['view.compiled'],
|
||||
$app->basePath(),
|
||||
$app['config']->get('view.cache', true),
|
||||
$app['config']->get('view.compiled_extension', 'php'),
|
||||
);
|
||||
|
||||
if (! $this->viewPaths) {
|
||||
$this->viewPaths = $this->getViewPaths();
|
||||
}
|
||||
|
||||
$namespaces = array_keys($this->viewPaths);
|
||||
array_map(
|
||||
[$compiler, 'anonymousComponentNamespace'],
|
||||
array_map(fn ($namespace) => "$namespace::components", $namespaces),
|
||||
$namespaces
|
||||
);
|
||||
|
||||
// Class-based components (Type 3): map <x-{ns}::name> to a PHP component class when one
|
||||
// exists, transparently falling back to the anonymous blade file otherwise. Registration
|
||||
// is lazy, so a namespace whose class root does not exist (or whose plugin folder casing
|
||||
// differs) is harmless — the anonymous component is still used.
|
||||
foreach ($namespaces as $namespace) {
|
||||
if ($classNamespace = $this->componentClassNamespace($namespace)) {
|
||||
$compiler->componentNamespace($classNamespace, $namespace);
|
||||
}
|
||||
}
|
||||
|
||||
return tap($compiler, function ($blade) {
|
||||
$blade->component('dynamic-component', DynamicComponent::class);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the PHP class namespace that backs class-based (Type 3) Blade components for a given
|
||||
* view namespace, or null when the namespace has no class root.
|
||||
*
|
||||
* Maps: global/globals → Leantime\Views\Components; a domain → Leantime\Domain\{Studly}\View\Components;
|
||||
* a plugin → Leantime\Plugins\{Studly}\View\Components.
|
||||
*/
|
||||
private function componentClassNamespace(string $namespace): ?string
|
||||
{
|
||||
if (in_array($namespace, ['global', 'globals', '__components'], true)) {
|
||||
return 'Leantime\\Views\\Components';
|
||||
}
|
||||
|
||||
$studly = Str::studly($namespace);
|
||||
|
||||
if (is_dir(APP_ROOT.'/app/Domain/'.$studly)) {
|
||||
return 'Leantime\\Domain\\'.$studly.'\\View\\Components';
|
||||
}
|
||||
|
||||
if (is_dir(APP_ROOT.'/app/Plugins/'.$studly)) {
|
||||
return 'Leantime\\Plugins\\'.$studly.'\\View\\Components';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the engine resolver instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerEngineResolver()
|
||||
{
|
||||
$this->app->singleton('view.engine.resolver', function () {
|
||||
$resolver = new EngineResolver;
|
||||
|
||||
// Next, we will register the various view engines with the resolver so that the
|
||||
// environment will resolve the engines needed for various views based on the
|
||||
// extension of view file. We call a method for each of the view's engines.
|
||||
foreach (['file', 'php', 'blade'] as $engine) {
|
||||
$this->{'register'.ucfirst($engine).'Engine'}($resolver);
|
||||
}
|
||||
|
||||
return $resolver;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the file engine implementation.
|
||||
*
|
||||
* @param \Illuminate\View\Engines\EngineResolver $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function registerFileEngine($resolver)
|
||||
{
|
||||
$resolver->register('file', function () {
|
||||
return new FileEngine(Container::getInstance()->make('files'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the PHP engine implementation.
|
||||
*
|
||||
* @param \Illuminate\View\Engines\EngineResolver $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function registerPhpEngine($resolver)
|
||||
{
|
||||
$resolver->register('php', function () {
|
||||
return new PhpEngine(Container::getInstance()->make('files'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Blade engine implementation.
|
||||
*
|
||||
* @param \Illuminate\View\Engines\EngineResolver $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function registerBladeEngine($resolver)
|
||||
{
|
||||
$resolver->register('blade', function () {
|
||||
$app = Container::getInstance();
|
||||
|
||||
$compiler = new CompilerEngine(
|
||||
$app->make('blade.compiler'),
|
||||
$app->make('files'),
|
||||
);
|
||||
|
||||
$app->terminating(static function () use ($compiler) {
|
||||
$compiler->forgetCompiledOrNotExpired();
|
||||
});
|
||||
|
||||
return $compiler;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function getComposerPaths()
|
||||
{
|
||||
$pathRepo = app()->make(PathManifestRepository::class);
|
||||
|
||||
if ($viewPaths = $pathRepo->loadManifest('composerPaths')) {
|
||||
return $viewPaths;
|
||||
}
|
||||
|
||||
$storePaths = $this->discoverComposerPaths();
|
||||
|
||||
$viewPaths = $pathRepo->writeManifest('composerPaths', $storePaths);
|
||||
|
||||
return $viewPaths;
|
||||
}
|
||||
|
||||
private function discoverComposerPaths()
|
||||
{
|
||||
$appComposerClasses = collect(glob(APP_ROOT.'/app/Views/Composers/*.php'))
|
||||
->concat(glob(APP_ROOT.'/app/Domain/*/Composers/*.php'));
|
||||
|
||||
try {
|
||||
$pluginService = $this->app->make(\Leantime\Core\Plugins\Plugins::class);
|
||||
$enabledPluginPaths = $pluginService->getEnabledPluginPaths();
|
||||
|
||||
$pluginComposerClasses = collect($enabledPluginPaths)
|
||||
->map(function ($pluginInfo) {
|
||||
|
||||
$composersPath = $pluginInfo['path'].'/Composers/';
|
||||
|
||||
if ($pluginInfo['format'] === 'phar') {
|
||||
|
||||
if (! file_exists($pluginInfo['path'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$composers = [];
|
||||
$p = new \Phar($composersPath, 0);
|
||||
$paths = collect(new \RecursiveIteratorIterator($p));
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$something = $path;
|
||||
$composers[] = 'Plugins/'.$pluginInfo['foldername'].'/Composers/'.$path->getFileName();
|
||||
}
|
||||
|
||||
return $composers;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Use glob which works for both folder and phar paths
|
||||
return glob($composersPath.'*.php') ?: [];
|
||||
})
|
||||
->flatten();
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
$pluginComposerClasses = collect();
|
||||
}
|
||||
|
||||
$composerList = $appComposerClasses
|
||||
->concat($pluginComposerClasses)
|
||||
->map(fn ($filepath) => Str::of($filepath)
|
||||
->replace([APP_ROOT.'/app/', '.php'], ['', '', ''])
|
||||
->replace('/', '\\')
|
||||
->start($this->app->getNamespace())
|
||||
->toString())
|
||||
->all();
|
||||
|
||||
return $composerList;
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
|
||||
app('blade.compiler')->directive(
|
||||
'dispatchEvent',
|
||||
fn ($args) => "<?php \$tpl->dispatchTplEvent($args); ?>",
|
||||
);
|
||||
|
||||
app('blade.compiler')->directive(
|
||||
'dispatchFilter',
|
||||
fn ($args) => "<?php echo \$tpl->dispatchTplFilter($args); ?>",
|
||||
);
|
||||
|
||||
app('blade.compiler')->directive(
|
||||
'spaceless',
|
||||
fn ($args) => '<?php ob_start(); ?>',
|
||||
);
|
||||
|
||||
app('blade.compiler')->directive(
|
||||
'endspaceless',
|
||||
fn ($args) => "<?php echo preg_replace('/>\\s+</', '><', ob_get_clean()); ?>",
|
||||
);
|
||||
|
||||
app('blade.compiler')->directive(
|
||||
'formatDate',
|
||||
fn ($args) => "<?php echo format($args)->date(); ?>",
|
||||
);
|
||||
|
||||
app('blade.compiler')->directive(
|
||||
'formatTime',
|
||||
fn ($args) => "<?php echo format($args)->time(); ?>",
|
||||
);
|
||||
|
||||
app('blade.compiler')->directive(
|
||||
'displayNotification',
|
||||
fn ($args) => '<?php echo $tpl->displayNotification(); ?>',
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function getViewPaths()
|
||||
{
|
||||
$pathRepo = app()->make(PathManifestRepository::class);
|
||||
|
||||
// Check if we're in a test environment
|
||||
if (app()->environment('testing')) {
|
||||
// Force regeneration of view paths in test environment
|
||||
return $this->discoverViewPaths();
|
||||
}
|
||||
|
||||
if ($viewPaths = $pathRepo->loadManifest('viewPaths')) {
|
||||
return $viewPaths;
|
||||
}
|
||||
|
||||
$storePaths = $this->discoverViewPaths();
|
||||
|
||||
$viewPaths = $pathRepo->writeManifest('viewPaths', $storePaths);
|
||||
|
||||
return $viewPaths;
|
||||
}
|
||||
|
||||
private function discoverViewPaths()
|
||||
{
|
||||
$domainPaths = collect(glob($this->app->basePath().'/app/Domain/*'))
|
||||
->mapWithKeys(function ($path) {
|
||||
if (is_dir($path.'/Templates')) {
|
||||
return [
|
||||
$basename = strtolower(basename($path)) => [
|
||||
"$path/Templates",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
// Get all plugin paths (both enabled and disabled) since views are discovered at service provider registration
|
||||
// For view paths we need to include all plugins, not just enabled ones, to avoid database calls during registration
|
||||
$pluginPaths = collect(glob($this->app->basePath().'/app/Plugins/*'))
|
||||
->mapWithKeys(function ($path) {
|
||||
|
||||
$pluginFolder = basename($path);
|
||||
|
||||
// Check phar templates first
|
||||
$pharTemplatesPath = 'phar://'.APP_ROOT.'/app/Plugins/'.$pluginFolder.'/'.$pluginFolder.'.phar/Templates';
|
||||
if (is_dir($pharTemplatesPath)) {
|
||||
return [strtolower($pluginFolder) => [$pharTemplatesPath]];
|
||||
}
|
||||
|
||||
// Check folder templates
|
||||
$folderTemplatesPath = APP_ROOT.'/app/Plugins/'.$pluginFolder.'/Templates';
|
||||
if (is_dir($folderTemplatesPath)) {
|
||||
return [strtolower($pluginFolder) => [$folderTemplatesPath]];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
$storePaths = $domainPaths
|
||||
->merge($pluginPaths)
|
||||
->merge(['globals' => APP_ROOT.'/app/Views/Templates'])
|
||||
->merge(['global' => APP_ROOT.'/app/Views/Templates'])
|
||||
->merge(['__components' => $this->app['config']->get('view.compiled')])
|
||||
->all();
|
||||
|
||||
return $storePaths;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user