Completed
Pull Request — master (#4514)
by Axel
06:46 queued 56s
created

Asset   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 114
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 43
c 0
b 0
f 0
dl 0
loc 114
rs 10
wmc 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A getBundleAssetPath() 0 15 4
A resolve() 0 46 5
A __construct() 0 12 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\ThemeModule\Engine;
15
16
use InvalidArgumentException;
17
use Symfony\Component\Asset\Packages;
18
use Symfony\Component\Filesystem\Filesystem;
19
use Symfony\Component\HttpKernel\Bundle\Bundle;
20
use Symfony\Component\Routing\RouterInterface;
21
use function Symfony\Component\String\s;
22
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaHttpKernelInterface;
23
use Zikula\ExtensionsModule\AbstractExtension;
24
use Zikula\ThemeModule\Engine\Exception\AssetNotFoundException;
25
26
/**
27
 * Class Asset
28
 *
29
 * This class locates assets accounting for possible overrides in public/overrides/$bundleName or in the
30
 * active theme. It is foremost used by the zasset() Twig template plugin, but can be utilized as a standalone
31
 * service as well. All asset types (js, css, images) will work.
32
 *
33
 * Asset paths must begin with `@` in order to be processed (and possibly overridden) by this class.
34
 * Assets that do not contain `@` are passed through to the standard symfony asset management.
35
 *
36
 * Overrides are in this order:
37
 *  1) public/overrides/$bundleName/*
38
 *  2) public/themes/$theme/$bundleName/*
39
 *  3) public/modules/$bundleName/*
40
 */
41
class Asset
42
{
43
    /**
44
     * @var ZikulaHttpKernelInterface
45
     */
46
    private $kernel;
47
48
    /**
49
     * @var Packages
50
     */
51
    private $assetPackages;
52
53
    /**
54
     * @var RouterInterface
55
     */
56
    private $router;
57
58
    /**
59
     * @var Filesystem
60
     */
61
    private $fileSystem;
62
63
    /**
64
     * @var Engine
65
     */
66
    private $themeEngine;
67
68
    public function __construct(
69
        ZikulaHttpKernelInterface $kernel,
70
        Packages $assetPackages,
71
        RouterInterface $router,
72
        Filesystem $fileSystem,
73
        Engine $themeEngine
74
    ) {
75
        $this->kernel = $kernel;
76
        $this->assetPackages = $assetPackages;
77
        $this->router = $router;
78
        $this->fileSystem = $fileSystem;
79
        $this->themeEngine = $themeEngine;
80
    }
81
82
    /**
83
     * Returns path for asset.
84
     * Confirms actual file existence before returning path
85
     */
86
    public function resolve(string $path): string
87
    {
88
        $publicDir = str_replace('\\', '/', $this->kernel->getProjectDir()) . '/public';
89
        $basePath = $this->router->getContext()->getBaseUrl();
90
        $httpRootDir = str_replace($basePath, '', $publicDir);
91
92
        // return immediately for straight asset paths
93
        $path = s($path);
94
        if (!$path->startsWith('@')) {
95
            $path = $path->trimStart('/');
96
            $publicPath = $this->assetPackages->getUrl($path->__toString());
97
            if (false !== realpath($httpRootDir . $publicPath)) {
98
                return $publicPath;
99
            }
100
            throw new AssetNotFoundException(sprintf('Could not find asset "%s"', $httpRootDir . $publicPath));
101
        }
102
103
        [$bundleName, $relativeAssetPath] = explode(':', $path->__toString());
104
105
        $bundleNameForAssetPath = s($bundleName)->trimStart('@')->lower()->__toString();
106
        $bundleAssetPath = $this->getBundleAssetPath($bundleName);
107
        $themeName = $this->themeEngine->getTheme()->getName();
108
109
        $foldersToCheck = [
110
            // public override path (e.g. public/overrides/zikulacontentmodule)
111
            'overrides/' . $bundleNameForAssetPath,
112
            // public theme path (e.g. public/themes/zikuladefaulttheme/zikulacontentmodule)
113
            'themes/' . mb_strtolower($themeName) . '/' . $bundleNameForAssetPath,
114
            // public bundle directory (e.g. public/modules/zikulacontent)
115
            $bundleAssetPath
116
        ];
117
118
        foreach ($foldersToCheck as $folder) {
119
            $fullPath = $publicDir . '/' . $folder . '/' . $relativeAssetPath;
120
            if (false !== realpath($fullPath)) {
121
                return str_replace($httpRootDir, '', $fullPath);
122
            }
123
        }
124
125
        // asset not found in public/.
126
        // copy the asset from the bundle directory to /public
127
        // and then locate it in the bundle's normal public directory
128
        $fullPath = $this->kernel->locateResource($bundleName . '/Resources/public/' . $relativeAssetPath);
129
        $this->fileSystem->copy($fullPath, $publicDir . '/' . $bundleAssetPath . '/' . $relativeAssetPath);
130
131
        return $this->assetPackages->getUrl($bundleAssetPath . '/' . $relativeAssetPath);
132
    }
133
134
    /**
135
     * Maps and returns zasset base path.
136
     * e.g. "@AcmeNewsModule" to `modules/acmenews`
137
     * e.g. "@AcmeCustomTheme" to `themes/acmecustom`
138
     * e.g. "@SomeBundle" to `bundles/some`
139
     */
140
    private function getBundleAssetPath(?string $bundleName): string
141
    {
142
        if (!isset($bundleName)) {
143
            throw new InvalidArgumentException('No bundle name resolved, must be like "@AcmeBundle"');
144
        }
145
        $bundle = $this->kernel->getBundle(s($bundleName)->trimStart('@')->__toString());
146
        if (!$bundle instanceof Bundle) {
147
            throw new InvalidArgumentException('Bundle ' . $bundleName . ' not found.');
148
        }
149
150
        if ($bundle instanceof AbstractExtension) {
151
            return $bundle->getRelativeAssetPath();
152
        }
153
154
        return 'bundles/' . s($bundle->getName())->lower()->trimEnd('bundle');
155
    }
156
}
157