Skip to content
Merged
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
35 changes: 32 additions & 3 deletions lib/Horde/Themes/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ public function __get($name)
/**
* Convert a URI into a Horde_Themes_Element object.
*
* The URI is first matched against the themes, javascript and static
* locations of the registered applications, since these may be served
* outside of the Horde webroot (e.g. /themes/horde in a composer
* installation). Otherwise, the URI is resolved against the Horde
* fileroot.
*
* @param string $uri The URI to convert.
*
* @return Horde_Themes_Element A theme element object.
Expand All @@ -170,12 +176,35 @@ public static function fromUri($uri)
{
global $registry;

$fileroot = $registry->get('fileroot', 'horde');
$webroot = $registry->get('webroot', 'horde');
$uri = (string) $uri;
$fs = null;
$match = 0;

foreach ($registry->listAllApps() as $app) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. Fixed in 82d35a6: CompressTest now stubs listAllApps(). I checked that no other test reaches fromUri() with a registry stub. PHPUnit is still not available in my environment, so the suite itself has not been run.

foreach (['themes', 'js', 'static'] as $type) {
$base_uri = rtrim((string) $registry->get($type . 'uri', $app), '/');
$base_fs = $registry->get($type . 'fs', $app);
if (!strlen($base_uri)
|| !strlen((string) $base_fs)
|| (strlen($base_uri) <= $match)
|| !str_starts_with($uri, $base_uri . '/')) {
continue;
}
$fs = rtrim($base_fs, '/') . substr($uri, strlen($base_uri));
$match = strlen($base_uri);
}
}

if (is_null($fs)) {
$fileroot = $registry->get('fileroot', 'horde');
$webroot = rtrim((string) $registry->get('webroot', 'horde'), '/');
$fs = realpath($fileroot ?? '') . '/'
. ltrim(preg_replace('/^' . preg_quote($webroot, '/') . '(?=\/)/', '', $uri), '/');
}

return new self('', [
'data' => [
'fs' => realpath($fileroot ?? '') . preg_replace('/^' . preg_quote($webroot ?? '', '/') . '/', '', $uri),
'fs' => $fs,
'uri' => $uri,
],
]);
Expand Down
1 change: 1 addition & 0 deletions test/Unit/Themes/Css/CompressTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ protected function setUp(): void
$GLOBALS['browser']->method('hasFeature')->willReturn(false);

$GLOBALS['registry'] = $this->createStub(Horde_Registry::class);
$GLOBALS['registry']->method('listAllApps')->willReturn([]);

// Use stubs for logger and injector
$logger = $this->createStub(LoggerInterface::class);
Expand Down
156 changes: 156 additions & 0 deletions test/Unit/Themes/ElementTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

declare(strict_types=1);

/**
* Tests for Horde_Themes_Element::fromUri().
*
* @category Horde
* @package Core
*/

namespace Horde\Core\Test\Unit\Themes;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Horde_Registry;
use Horde_Themes_Element;

#[CoversClass(Horde_Themes_Element::class)]
class ElementTest extends TestCase
{
private string $fileroot;

protected function setUp(): void
{
$this->fileroot = realpath(sys_get_temp_dir());
}

protected function tearDown(): void
{
unset($GLOBALS['registry']);
}

/**
* Installs a registry stub answering get() from a per-app array.
*/
private function setRegistry(array $applications): void
{
$registry = $this->createStub(Horde_Registry::class);
$registry->method('listAllApps')->willReturn(array_keys($applications));
$registry->method('get')->willReturnCallback(
fn($parameter, $app = null) => $applications[$app][$parameter]
?? $applications['horde'][$parameter]
?? null
);
$GLOBALS['registry'] = $registry;
}

/**
* Composer layout: themes are served outside of the Horde webroot.
*/
private function composerLayout(): array
{
return [
'horde' => [
'fileroot' => $this->fileroot,
'webroot' => '/horde/',
'jsfs' => '/var/www/horde/web/js/horde/',
'jsuri' => '/js/horde',
'staticfs' => '/var/www/horde/web/static/',
'staticuri' => '/static/',
'themesfs' => '/var/www/horde/web/themes/horde/',
'themesuri' => '/themes/horde',
],
'imp' => [
'themesfs' => '/var/www/horde/web/themes/imp/',
'themesuri' => '/themes/imp',
],
];
}

public function testThemesUriOutsideWebroot(): void
{
$this->setRegistry($this->composerLayout());

$ob = Horde_Themes_Element::fromUri('/themes/horde/mytheme/base.css');

$this->assertSame('/var/www/horde/web/themes/horde/mytheme/base.css', $ob->fs);
$this->assertSame('/themes/horde/mytheme/base.css', $ob->uri);
}

public function testApplicationThemesUri(): void
{
$this->setRegistry($this->composerLayout());

$ob = Horde_Themes_Element::fromUri('/themes/imp/mytheme/screen.css');

$this->assertSame('/var/www/horde/web/themes/imp/mytheme/screen.css', $ob->fs);
}

public function testStaticUri(): void
{
$this->setRegistry($this->composerLayout());

$ob = Horde_Themes_Element::fromUri('/static/abc.css');

$this->assertSame('/var/www/horde/web/static/abc.css', $ob->fs);
}

public function testLongestPrefixWins(): void
{
$applications = $this->composerLayout();
$applications['horde']['themesfs'] = '/srv/themes/';
$applications['horde']['themesuri'] = '/themes';
$this->setRegistry($applications);

$ob = Horde_Themes_Element::fromUri('/themes/imp/mytheme/screen.css');

$this->assertSame('/var/www/horde/web/themes/imp/mytheme/screen.css', $ob->fs);
}

public function testPrefixMatchesWholePathSegment(): void
{
$this->setRegistry($this->composerLayout());

$ob = Horde_Themes_Element::fromUri('/themes/horde-extra/base.css');

$this->assertSame($this->fileroot . '/themes/horde-extra/base.css', $ob->fs);
}

public function testFallbackStripsWebrootWithTrailingSlash(): void
{
$this->setRegistry(['horde' => [
'fileroot' => $this->fileroot,
'webroot' => '/horde/',
]]);

$ob = Horde_Themes_Element::fromUri('/horde/themes/default/screen.css');

$this->assertSame($this->fileroot . '/themes/default/screen.css', $ob->fs);
}

public function testFallbackStripsWebrootWithoutTrailingSlash(): void
{
$this->setRegistry(['horde' => [
'fileroot' => $this->fileroot,
'webroot' => '/horde',
]]);

$ob = Horde_Themes_Element::fromUri('/horde/themes/default/screen.css');

$this->assertSame($this->fileroot . '/themes/default/screen.css', $ob->fs);
}

public function testFallbackWithRootWebroot(): void
{
$this->setRegistry(['horde' => [
'fileroot' => $this->fileroot,
'webroot' => '/',
]]);

$ob = Horde_Themes_Element::fromUri('/themes/default/screen.css');

$this->assertSame($this->fileroot . '/themes/default/screen.css', $ob->fs);
}
}
Loading