root / drupal7 / misc / typo3 / phar-stream-wrapper / src / Resolver / PharInvocationResolver.php @ fbb66ca6
1 |
<?php
|
---|---|
2 |
namespace TYPO3\PharStreamWrapper\Resolver; |
3 |
|
4 |
/*
|
5 |
* This file is part of the TYPO3 project.
|
6 |
*
|
7 |
* It is free software; you can redistribute it and/or modify it under the terms
|
8 |
* of the MIT License (MIT). For the full copyright and license information,
|
9 |
* please read the LICENSE file that was distributed with this source code.
|
10 |
*
|
11 |
* The TYPO3 project - inspiring people to share!
|
12 |
*/
|
13 |
|
14 |
use TYPO3\PharStreamWrapper\Helper; |
15 |
use TYPO3\PharStreamWrapper\Manager; |
16 |
use TYPO3\PharStreamWrapper\Phar\Reader; |
17 |
use TYPO3\PharStreamWrapper\Resolvable; |
18 |
|
19 |
class PharInvocationResolver implements Resolvable |
20 |
{ |
21 |
const RESOLVE_REALPATH = 1; |
22 |
const RESOLVE_ALIAS = 2; |
23 |
const ASSERT_INTERNAL_INVOCATION = 32; |
24 |
|
25 |
/**
|
26 |
* @var string[]
|
27 |
*/
|
28 |
private $invocationFunctionNames = array( |
29 |
'include',
|
30 |
'include_once',
|
31 |
'require',
|
32 |
'require_once'
|
33 |
); |
34 |
|
35 |
/**
|
36 |
* Contains resolved base names in order to reduce file IO.
|
37 |
*
|
38 |
* @var string[]
|
39 |
*/
|
40 |
private $baseNames = array(); |
41 |
|
42 |
/**
|
43 |
* Resolves PharInvocation value object (baseName and optional alias).
|
44 |
*
|
45 |
* Phar aliases are intended to be used only inside Phar archives, however
|
46 |
* PharStreamWrapper needs this information exposed outside of Phar as well
|
47 |
* It is possible that same alias is used for different $baseName values.
|
48 |
* That's why PharInvocationCollection behaves like a stack when resolving
|
49 |
* base-name for a given alias. On the other hand it is not possible that
|
50 |
* one $baseName is referring to multiple aliases.
|
51 |
* @see https://secure.php.net/manual/en/phar.setalias.php
|
52 |
* @see https://secure.php.net/manual/en/phar.mapphar.php
|
53 |
*
|
54 |
* @param string $path
|
55 |
* @param int|null $flags
|
56 |
* @return null|PharInvocation
|
57 |
*/
|
58 |
public function resolve($path, $flags = null) |
59 |
{ |
60 |
$hasPharPrefix = Helper::hasPharPrefix($path); |
61 |
if ($flags === null) { |
62 |
$flags = static::RESOLVE_REALPATH | static::RESOLVE_ALIAS | static::ASSERT_INTERNAL_INVOCATION; |
63 |
} |
64 |
|
65 |
if ($hasPharPrefix && $flags & static::RESOLVE_ALIAS) { |
66 |
$invocation = $this->findByAlias($path); |
67 |
if ($invocation !== null) { |
68 |
return $invocation; |
69 |
} |
70 |
} |
71 |
|
72 |
$baseName = $this->resolveBaseName($path, $flags); |
73 |
if ($baseName === null) { |
74 |
return null; |
75 |
} |
76 |
|
77 |
if ($flags & static::RESOLVE_REALPATH) { |
78 |
$baseName = $this->baseNames[$baseName]; |
79 |
} |
80 |
|
81 |
return $this->retrieveInvocation($baseName, $flags); |
82 |
} |
83 |
|
84 |
/**
|
85 |
* Retrieves PharInvocation, either existing in collection or created on demand
|
86 |
* with resolving a potential alias name used in the according Phar archive.
|
87 |
*
|
88 |
* @param string $baseName
|
89 |
* @param int $flags
|
90 |
* @return PharInvocation
|
91 |
*/
|
92 |
private function retrieveInvocation($baseName, $flags) |
93 |
{ |
94 |
$invocation = $this->findByBaseName($baseName); |
95 |
if ($invocation !== null) { |
96 |
return $invocation; |
97 |
} |
98 |
|
99 |
if ($flags & static::RESOLVE_ALIAS) { |
100 |
$reader = new Reader($baseName); |
101 |
$alias = $reader->resolveContainer()->getAlias(); |
102 |
} else {
|
103 |
$alias = ''; |
104 |
} |
105 |
// add unconfirmed(!) new invocation to collection
|
106 |
$invocation = new PharInvocation($baseName, $alias); |
107 |
Manager::instance()->getCollection()->collect($invocation); |
108 |
return $invocation; |
109 |
} |
110 |
|
111 |
/**
|
112 |
* @param string $path
|
113 |
* @param int $flags
|
114 |
* @return null|string
|
115 |
*/
|
116 |
private function resolveBaseName($path, $flags) |
117 |
{ |
118 |
$baseName = $this->findInBaseNames($path); |
119 |
if ($baseName !== null) { |
120 |
return $baseName; |
121 |
} |
122 |
|
123 |
$baseName = Helper::determineBaseFile($path); |
124 |
if ($baseName !== null) { |
125 |
$this->addBaseName($baseName); |
126 |
return $baseName; |
127 |
} |
128 |
|
129 |
$possibleAlias = $this->resolvePossibleAlias($path); |
130 |
if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) { |
131 |
return null; |
132 |
} |
133 |
|
134 |
$trace = debug_backtrace(); |
135 |
foreach ($trace as $item) { |
136 |
if (!isset($item['function']) || !isset($item['args'][0]) |
137 |
|| !in_array($item['function'], $this->invocationFunctionNames, true)) { |
138 |
continue;
|
139 |
} |
140 |
$currentPath = $item['args'][0]; |
141 |
if (Helper::hasPharPrefix($currentPath)) { |
142 |
continue;
|
143 |
} |
144 |
$currentBaseName = Helper::determineBaseFile($currentPath); |
145 |
if ($currentBaseName === null) { |
146 |
continue;
|
147 |
} |
148 |
// ensure the possible alias name (how we have been called initially) matches
|
149 |
// the resolved alias name that was retrieved by the current possible base name
|
150 |
$reader = new Reader($currentBaseName); |
151 |
$currentAlias = $reader->resolveContainer()->getAlias(); |
152 |
if ($currentAlias !== $possibleAlias) { |
153 |
continue;
|
154 |
} |
155 |
$this->addBaseName($currentBaseName); |
156 |
return $currentBaseName; |
157 |
} |
158 |
|
159 |
return null; |
160 |
} |
161 |
|
162 |
/**
|
163 |
* @param string $path
|
164 |
* @return null|string
|
165 |
*/
|
166 |
private function resolvePossibleAlias($path) |
167 |
{ |
168 |
$normalizedPath = Helper::normalizePath($path); |
169 |
return strstr($normalizedPath, '/', true) ?: null; |
170 |
} |
171 |
|
172 |
/**
|
173 |
* @param string $baseName
|
174 |
* @return null|PharInvocation
|
175 |
*/
|
176 |
private function findByBaseName($baseName) |
177 |
{ |
178 |
return Manager::instance()->getCollection()->findByCallback( |
179 |
function (PharInvocation $candidate) use ($baseName) { |
180 |
return $candidate->getBaseName() === $baseName; |
181 |
}, |
182 |
true
|
183 |
); |
184 |
} |
185 |
|
186 |
/**
|
187 |
* @param string $path
|
188 |
* @return null|string
|
189 |
*/
|
190 |
private function findInBaseNames($path) |
191 |
{ |
192 |
// return directly if the resolved base name was submitted
|
193 |
if (in_array($path, $this->baseNames, true)) { |
194 |
return $path; |
195 |
} |
196 |
|
197 |
$parts = explode('/', Helper::normalizePath($path)); |
198 |
|
199 |
while (count($parts)) { |
200 |
$currentPath = implode('/', $parts); |
201 |
if (isset($this->baseNames[$currentPath])) { |
202 |
return $currentPath; |
203 |
} |
204 |
array_pop($parts); |
205 |
} |
206 |
|
207 |
return null; |
208 |
} |
209 |
|
210 |
/**
|
211 |
* @param string $baseName
|
212 |
*/
|
213 |
private function addBaseName($baseName) |
214 |
{ |
215 |
if (isset($this->baseNames[$baseName])) { |
216 |
return;
|
217 |
} |
218 |
$this->baseNames[$baseName] = realpath($baseName); |
219 |
} |
220 |
|
221 |
/**
|
222 |
* Finds confirmed(!) invocations by alias.
|
223 |
*
|
224 |
* @param string $path
|
225 |
* @return null|PharInvocation
|
226 |
* @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
|
227 |
*/
|
228 |
private function findByAlias($path) |
229 |
{ |
230 |
$possibleAlias = $this->resolvePossibleAlias($path); |
231 |
if ($possibleAlias === null) { |
232 |
return null; |
233 |
} |
234 |
return Manager::instance()->getCollection()->findByCallback( |
235 |
function (PharInvocation $candidate) use ($possibleAlias) { |
236 |
return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias; |
237 |
}, |
238 |
true
|
239 |
); |
240 |
} |
241 |
} |