Completed
Push — master ( c681fc...9457d4 )
by Craig
05:07
created

Asset::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 1
eloc 5
c 2
b 1
f 0
nc 1
nop 5
dl 0
loc 12
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula Foundation - 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 Zikula\Bundle\CoreBundle\AbstractBundle;
22
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaHttpKernelInterface;
23
24
/**
25
 * Class Asset
26
 *
27
 * This class locates assets accounting for possible overrides in public/overrides/$bundleName or in the
28
 * active theme. It is foremost used by the zasset() Twig template plugin, but can be utilized as a standalone
29
 * service as well. All asset types (js, css, images) will work.
30
 *
31
 * Asset paths must begin with `@` in order to be processed (and possibly overridden) by this class.
32
 * Assets that do not contain `@` are passed through to the standard symfony asset management.
33
 *
34
 * Overrides are in this order:
35
 *  1) public/overrides/$bundleName/*
36
 *  2) public/themes/$theme/$bundleName/*
37
 *  3) public/modules/$bundleName/*
38
 */
39
class Asset
40
{
41
    /**
42
     * @var ZikulaHttpKernelInterface
43
     */
44
    private $kernel;
45
46
    /**
47
     * @var Packages
48
     */
49
    private $assetPackages;
50
51
    /**
52
     * @var RouterInterface
53
     */
54
    private $router;
55
56
    /**
57
     * @var Filesystem
58
     */
59
    private $fileSystem;
60
61
    /**
62
     * @var Engine
63
     */
64
    private $themeEngine;
65
66
    public function __construct(
67
        ZikulaHttpKernelInterface $kernel,
68
        Packages $assetPackages,
69
        RouterInterface $router,
70
        Filesystem $fileSystem,
71
        Engine $themeEngine
72
    ) {
73
        $this->kernel = $kernel;
74
        $this->assetPackages = $assetPackages;
75
        $this->router = $router;
76
        $this->fileSystem = $fileSystem;
77
        $this->themeEngine = $themeEngine;
78
    }
79
80
    /**
81
     * Returns path for asset.
82
     * Confirms actual file existence before returning path
83
     */
84
    public function resolve(string $path): string
85
    {
86
        $projectDir = $this->kernel->getProjectDir();
87
        $publicDir = $projectDir . '/public';
88
        $basePath = $this->router->getContext()->getBaseUrl();
89
        $httpRootDir = str_replace($basePath, '', $publicDir);
90
91
        // return immediately for straight asset paths
92
        if ('@' !== $path[0]) {
93
            if (0 === mb_strpos($path, '/')) {
94
                $path = mb_substr($path, 1);
95
            }
96
            $publicPath = $this->assetPackages->getUrl($path);
97
            if (false !== realpath($httpRootDir . $publicPath)) {
98
                return $publicPath;
99
            }
100
        }
101
102
        [$bundleName, $originalPath] = explode(':', $path);
103
104
        // try to locate asset in public override path
105
        $overridePath = $publicDir . '/overrides/' . mb_substr($bundleName, 1) . '/' . $originalPath;
106
        if (false !== $fullPath = realpath($overridePath)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $fullPath is dead and can be removed.
Loading history...
107
            $publicPath = $this->assetPackages->getUrl($overridePath);
108
            if (false !== realpath($httpRootDir . $publicPath)) {
109
                return $publicPath;
110
            }
111
        }
112
113
        // try to locate asset in public theme path
114
        $themeName = $this->themeEngine->getTheme()->getName();
115
        $overridePath = $publicDir . '/themes/' . strtolower($themeName) . '/' . mb_substr($bundleName, 1) . '/' . $originalPath;
116
        if (false !== $fullPath = realpath($overridePath)) {
117
            $publicPath = $this->assetPackages->getUrl($overridePath);
118
            if (false !== realpath($httpRootDir . $publicPath)) {
119
                return $publicPath;
120
            }
121
        }
122
123
        // try to locate asset in it's normal public directory
124
        $path = $this->mapZikulaAssetPath($bundleName, $originalPath);
125
        if (false !== $fullPath = realpath($publicDir . '/' . $path)) {
126
            $publicPath = $this->assetPackages->getUrl($path);
127
            if (false !== realpath($httpRootDir . $publicPath)) {
128
                return $publicPath;
129
            }
130
        }
131
132
        // Asset not found in public.
133
        // copy the asset from the Bundle directory to /public and then call this method again
134
        $fullPath = $this->kernel->locateResource($bundleName . '/Resources/public/' . $originalPath);
135
        $this->fileSystem->copy($fullPath, $publicDir . '/' . $path);
136
137
        return $this->resolve($bundleName . ':' . $originalPath);
138
    }
139
140
    /**
141
     * Maps zasset path argument
142
     * e.g. "@AcmeBundle:css/foo.css" to `AcmeBundle/Resources/public/css/foo.css`
143
     */
144
    private function mapZikulaAssetPath(?string $bundleName, ?string $path): string
145
    {
146
        if (!isset($bundleName) || !isset($path)) {
147
            throw new InvalidArgumentException('No bundle name resolved, must be like "@AcmeBundle:css/foo.css"');
148
        }
149
        $bundle = $this->kernel->getBundle(mb_substr($bundleName, 1));
150
        if ($bundle instanceof Bundle) {
151
            $path = '/' . $path;
152
            if ($bundle instanceof AbstractBundle) {
153
                $path = $bundle->getRelativeAssetPath() . $path;
154
            } else {
155
                $path = mb_strtolower('Bundles/' . mb_substr($bundle->getName(), 0, -mb_strlen('Bundle'))) . $path;
156
            }
157
        }
158
159
        return $path;
160
    }
161
}
162