Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 60 additions & 3 deletions classes/core/blade/BladeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
*
* @class BladeCompiler
*
* @brief This overrides the default BladeCompiler to use the overridden ComponentTagCompiler
* @brief Extended BladeCompiler that supports Smarty template includes.
*/

namespace PKP\core\blade;

use PKP\core\blade\ComponentTagCompiler;
use Illuminate\View\Compilers\BladeCompiler as IlluminateBladeCompiler;
use PKP\template\PKPTemplateResource;

class BladeCompiler extends IlluminateBladeCompiler
{
Expand All @@ -26,7 +26,7 @@ class BladeCompiler extends IlluminateBladeCompiler
*/
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
if (!$this->compilesComponentTags) {
return $value;
}

Expand All @@ -38,4 +38,61 @@ protected function compileComponentTags($value)
)
)->compile($value);
}

/**
* Override compileInclude to support Smarty template includes from Blade.
*
* Uses PKPTemplateResource::getFilePath() to resolve the template.
* If it resolves to a .tpl file, generates code to render via Smarty.
* If it resolves to a .blade file, uses View::file() for direct rendering.
*
* @see \Illuminate\View\Compilers\Concerns\CompilesIncludes::compileInclude()
*
* @param string $expression The @include expression
*
* @return string Compiled PHP code
*/
protected function compileInclude($expression)
{
$expression = $this->stripParentheses($expression);

// Extract the view name from the expression
$viewName = $this->extractViewName($expression);

if ($viewName) {
$filePath = PKPTemplateResource::getFilePath($viewName);

if ($filePath) {
// If it's a Smarty template, render via TemplateManager
if (!PKPTemplateResource::isBladeTemplate($filePath)) {
$normalizedName = PKPTemplateResource::normalizeTemplateName($viewName);
return "<?php echo \\APP\\template\\TemplateManager::getManager(\\APP\\core\\Application::get()->getRequest())->fetch('{$normalizedName}.tpl'); ?>";
}

// For Blade templates, use View::file() with the resolved path
$escapedPath = addslashes($filePath);
return "<?php echo \\Illuminate\\Support\\Facades\\View::file('{$escapedPath}', \\Illuminate\\Support\\Arr::except(get_defined_vars(), ['__data', '__path']))->render(); ?>";
}
}

// Default behavior: use Laravel's view system
return parent::compileInclude($expression);
}

/**
* Extract the view name from the @include expression.
*
* @param string $expression The expression (e.g., "'frontend.objects.article_details'" or "'view', ['data']")
*
* @return string|null The view name without quotes, or null if cannot extract
*/
protected function extractViewName(string $expression): ?string
{
// Match single or double quoted string at the start
if (preg_match('/^["\']([^"\']+)["\']/', $expression, $matches)) {
return $matches[1];
}

return null;
}
}
86 changes: 31 additions & 55 deletions classes/plugins/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
use APP\core\Application;
use APP\template\TemplateManager;
use Exception;
use Illuminate\Database\Migrations\Migration;
use PKP\config\Config;
use PKP\core\JSONMessage;
use PKP\core\PKPApplication;
Expand Down Expand Up @@ -233,7 +232,7 @@ final public function getInstallSchemaFile()
/**
* Get the installation migration for this plugin.
*
* @return ?Migration
* @return \Illuminate\Database\Migrations\Migration|\PKP\migration\Migration|null
*/
public function getInstallMigration()
{
Expand Down Expand Up @@ -431,15 +430,13 @@ public function getComponentClassNamespace(): string
*/
public function resolveBladeViewPath(string $templatePath): string
{
// This is to accommodate the case if template files path beign set as similar
// to the smarty templates e.g. `some-path/some-template.blade.php`
// as blade view needed to in just `some-path.some-template`
// Convert template path to Blade view notation
// e.g. `some-path/some-template.blade` → `some-path.some-template`
$bladeTemplatePath = str_replace(
['/', '.blade.php', '.blade', '.tpl'],
['.', '', '', ''],
['/', '.blade', '.tpl'],
['.', '', ''],
$templatePath
);

return "{$this->getTemplateViewNamespace()}::{$bladeTemplatePath}";
}

Expand Down Expand Up @@ -514,68 +511,47 @@ protected function _registerViewComponentNamespace(string $componentNamespacePat
public function _overridePluginTemplates($hookName, $args)
{
$filePath = &$args[0];
$template = $args[1];

// Get template paths to search (themes override this to include parent themes)
$templatePaths = $this->getTemplatePaths();
if (empty($templatePaths)) {
return false;
}

$checkFilePath = $filePath;

// If there's a templates/ prefix on the template, clean up the test path.
if (strpos($filePath, 'plugins/') === 0) {
$checkFilePath = 'templates/' . $checkFilePath;
// If there's a lib/pkp/ prefix, strip it
if (str_starts_with($checkFilePath, 'lib/pkp/')) {
$checkFilePath = substr($checkFilePath, strlen('lib/pkp/'));
}

// If there's a lib/pkp/ prefix on the template, test without it.
$libPkpPrefix = 'lib/pkp/';
if (strpos($checkFilePath, $libPkpPrefix) === 0) {
$checkFilePath = substr($filePath, strlen($libPkpPrefix));
// If path starts with templates/, strip it since getTemplatePaths()
// already returns paths ending with /templates
if (str_starts_with($checkFilePath, 'templates/')) {
$checkFilePath = substr($checkFilePath, strlen('templates/'));
}

// Check if an overriding plugin exists in the plugin path.
if ($overriddenFilePath = $this->_findOverriddenTemplate($checkFilePath)) {
$filePath = $overriddenFilePath;
// Normalize and search for override
$baseName = preg_replace('/\.(tpl|blade)$/', '', $checkFilePath);
$override = \PKP\template\PKPTemplateResource::findInPaths($baseName, $templatePaths);

if ($override) {
$filePath = $override;
}

return false;
}

/**
* Recursive check for existing templates
*
* @param string $path
* Get template paths for this plugin to search for overrides.
* Themes override this to include parent theme paths.
*
* @return string|null
* @return array Array of template directory paths
*/
private function _findOverriddenTemplate($path)
protected function getTemplatePaths(): array
{
$fullPath = sprintf('%s/%s', $this->getPluginPath(), $path);

// If smarty template exists, return the full path
if (file_exists($fullPath)) {
return $fullPath;
}

// Fallback to blade view if exists for theme plugins and if the parent template is a blade view
if ($this instanceof \PKP\plugins\ThemePlugin && $this->isRenderingViaBladeView) {
$bladePath = $this->resolveBladeViewPath(str_replace('templates/', '', $path));
if (view()->exists($bladePath)) {
return $bladePath;
}
}

// Backward compatibility for OJS prior to 3.1.2; changed path to templates for plugins.
if (($fullPath = preg_replace("/templates\/(?!.*templates\/)/", '', $fullPath)) && file_exists($fullPath)) {
if (Config::getVar('debug', 'deprecation_warnings')) {
trigger_error('Deprecated: The template at ' . $fullPath . ' has moved and will not be found in the future.');
}
return $fullPath;
}

// Recursive check for templates in ancestors of a current theme plugin
if ($this instanceof ThemePlugin
&& $this->parent
&& $fullPath = $this->parent->_findOverriddenTemplate($path)) {
return $fullPath;
}

return null;
$path = $this->getPluginPath() . '/templates';
return is_dir($path) ? [$path] : [];
}

/**
Expand Down
65 changes: 26 additions & 39 deletions classes/plugins/ThemePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ abstract class ThemePlugin extends LazyLoadPlugin
*/
public bool $isVueRuntimeRequired = false;

/**
* Track whether rendering via blade view
*/
public bool $isRenderingViaBladeView = false;

/**
* @copydoc Plugin::register
*
Expand All @@ -122,30 +117,11 @@ public function register($category, $path, $mainContextId = null)
Hook::add('PluginRegistry::categoryLoaded::themes', $this->initAfter(...));

// Allow themes to override plugin template files
Hook::add('TemplateManager::display', $this->loadBladeView(...));
Hook::add('TemplateResource::getFilename', $this->_overridePluginTemplates(...));

return true;
}

/**
* Register the blade view path by replacing the smarty template path in the TemplateManager
* only if the blade view exists
*/
public function loadBladeView(string $hookName, array $params): bool
{
$templateManager =& $params[0]; /** @var TemplateManager $templateManager */
$templatePath =& $params[1]; /** @var string $templatePath */

$bladeViewPath = $this->resolveBladeViewPath($templatePath);
if (view()->exists($bladeViewPath)) {
$this->isRenderingViaBladeView = true;
$templatePath = $bladeViewPath;
}

return Hook::CONTINUE;
}

/**
* Fire the init() method when a theme is registered
*
Expand Down Expand Up @@ -784,19 +760,32 @@ public function setParent($parent)

/**
* Register directories to search for template files
*
*/
private function _registerTemplates()
{
// Register parent theme template directory
if (isset($this->parent) && $this->parent instanceof self) {
$this->parent->_registerTemplates();
// Template paths are now computed on demand in getTemplatePaths()
}

/**
* Get template paths for this theme including parent themes.
* Overrides Plugin::getTemplatePaths() to include theme hierarchy.
*
* @return array Array of template directory paths (child to parent order)
*/
protected function getTemplatePaths(): array
{
$paths = [];
$theme = $this;

while ($theme) {
$themePath = $theme->getPluginPath() . '/templates';
if (is_dir($themePath)) {
$paths[] = $themePath;
}
$theme = $theme->parent ?? null;
}

// Register this theme's template directory
$request = Application::get()->getRequest();
$templateManager = TemplateManager::getManager($request);
$templateManager->addTemplateDir($this->_getBaseDir('templates'));
return $paths;
}

/**
Expand Down Expand Up @@ -1065,13 +1054,11 @@ protected function getUsageStatsDisplayColor(int $num): string
*/
protected function getSubmissionViewContext(): string
{
if (Application::get()->getName() == 'ojs2') {
return 'frontend-article-view';
} elseif (Application::get()->getName() == 'omp') {
return 'frontend-catalog-book';
} elseif (Application::get()->getName() == 'ops') {
return 'frontend-preprint-view';
}
return match (Application::get()->getName()) {
'ojs2' => 'frontend-article-view',
'omp' => 'frontend-catalog-book',
'ops' => 'frontend-preprint-view',
};
}

/**
Expand Down
15 changes: 14 additions & 1 deletion classes/template/PKPTemplateManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,22 @@ public function initialize(PKPRequest $request)
$activeTheme = null;
$contextOrSite = $currentContext ? $currentContext : $request->getSite();
$allThemes = PluginRegistry::getPlugins('themes');
foreach ($allThemes as $theme) {
foreach ($allThemes as $theme) { /** @var \PKP\plugins\Plugin|\PKP\plugins\ThemePlugin $theme */
if ($contextOrSite->getData('themePluginPath') === $theme->getDirName()) {
$activeTheme = $theme;

$bladeFileViewFinder = app()->get('view.finder'); /** @var \Illuminate\View\FileViewFinder $bladeFileViewFinder */
// Along with current active theme,
// we should also register the path of any parent theme
while($theme) {
if ($theme->getTemplatePath()) {
$bladeFileViewFinder->prependLocation(
app()->basePath($theme->getTemplatePath())
);
}
$theme = $theme->parent;
}

break;
}
}
Expand Down
Loading