Projet

Général

Profil

Paste
Télécharger (7,44 ko) Statistiques
| Branche: | Révision:

root / drupal7 / misc / typo3 / phar-stream-wrapper / src / Resolver / PharInvocationResolver.php @ 6b24a280

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\Phar\ReaderException;
18
use TYPO3\PharStreamWrapper\Resolvable;
19

    
20
class PharInvocationResolver implements Resolvable
21
{
22
    const RESOLVE_REALPATH = 1;
23
    const RESOLVE_ALIAS = 2;
24
    const ASSERT_INTERNAL_INVOCATION = 32;
25

    
26
    /**
27
     * @var string[]
28
     */
29
    private $invocationFunctionNames = array(
30
        'include',
31
        'include_once',
32
        'require',
33
        'require_once'
34
    );
35

    
36
    /**
37
     * Contains resolved base names in order to reduce file IO.
38
     *
39
     * @var string[]
40
     */
41
    private $baseNames = array();
42

    
43
    /**
44
     * Resolves PharInvocation value object (baseName and optional alias).
45
     *
46
     * Phar aliases are intended to be used only inside Phar archives, however
47
     * PharStreamWrapper needs this information exposed outside of Phar as well
48
     * It is possible that same alias is used for different $baseName values.
49
     * That's why PharInvocationCollection behaves like a stack when resolving
50
     * base-name for a given alias. On the other hand it is not possible that
51
     * one $baseName is referring to multiple aliases.
52
     * @see https://secure.php.net/manual/en/phar.setalias.php
53
     * @see https://secure.php.net/manual/en/phar.mapphar.php
54
     *
55
     * @param string $path
56
     * @param int|null $flags
57
     * @return null|PharInvocation
58
     */
59
    public function resolve($path, $flags = null)
60
    {
61
        $hasPharPrefix = Helper::hasPharPrefix($path);
62
        if ($flags === null) {
63
            $flags = static::RESOLVE_REALPATH | static::RESOLVE_ALIAS;
64
        }
65

    
66
        if ($hasPharPrefix && $flags & static::RESOLVE_ALIAS) {
67
            $invocation = $this->findByAlias($path);
68
            if ($invocation !== null) {
69
                return $invocation;
70
            }
71
        }
72

    
73
        $baseName = $this->resolveBaseName($path, $flags);
74
        if ($baseName === null) {
75
            return null;
76
        }
77

    
78
        if ($flags & static::RESOLVE_REALPATH) {
79
            $baseName = $this->baseNames[$baseName];
80
        }
81

    
82
        return $this->retrieveInvocation($baseName, $flags);
83
    }
84

    
85
    /**
86
     * Retrieves PharInvocation, either existing in collection or created on demand
87
     * with resolving a potential alias name used in the according Phar archive.
88
     *
89
     * @param string $baseName
90
     * @param int $flags
91
     * @return PharInvocation
92
     */
93
    private function retrieveInvocation($baseName, $flags)
94
    {
95
        $invocation = $this->findByBaseName($baseName);
96
        if ($invocation !== null) {
97
            return $invocation;
98
        }
99

    
100
        if ($flags & static::RESOLVE_ALIAS) {
101
            $reader = new Reader($baseName);
102
            $alias = $reader->resolveContainer()->getAlias();
103
        } else {
104
            $alias = '';
105
        }
106
        // add unconfirmed(!) new invocation to collection
107
        $invocation = new PharInvocation($baseName, $alias);
108
        Manager::instance()->getCollection()->collect($invocation);
109
        return $invocation;
110
    }
111

    
112
    /**
113
     * @param string $path
114
     * @param int $flags
115
     * @return null|string
116
     */
117
    private function resolveBaseName($path, $flags)
118
    {
119
        $baseName = $this->findInBaseNames($path);
120
        if ($baseName !== null) {
121
            return $baseName;
122
        }
123

    
124
        $baseName = Helper::determineBaseFile($path);
125
        if ($baseName !== null) {
126
            $this->addBaseName($baseName);
127
            return $baseName;
128
        }
129

    
130
        $possibleAlias = $this->resolvePossibleAlias($path);
131
        if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) {
132
            return null;
133
        }
134

    
135
        $trace = debug_backtrace();
136
        foreach ($trace as $item) {
137
            if (!isset($item['function']) || !isset($item['args'][0])
138
                || !in_array($item['function'], $this->invocationFunctionNames, true)) {
139
                continue;
140
            }
141
            $currentPath = $item['args'][0];
142
            if (Helper::hasPharPrefix($currentPath)) {
143
                continue;
144
            }
145
            $currentBaseName = Helper::determineBaseFile($currentPath);
146
            if ($currentBaseName === null) {
147
                continue;
148
            }
149
            // ensure the possible alias name (how we have been called initially) matches
150
            // the resolved alias name that was retrieved by the current possible base name
151
            try {
152
                $reader = new Reader($currentBaseName);
153
                $currentAlias = $reader->resolveContainer()->getAlias();
154
            } catch (ReaderException $exception) {
155
                // most probably that was not a Phar file
156
                continue;
157
            }
158
            if (empty($currentAlias) || $currentAlias !== $possibleAlias) {
159
                continue;
160
            }
161
            $this->addBaseName($currentBaseName);
162
            return $currentBaseName;
163
        }
164

    
165
        return null;
166
    }
167

    
168
    /**
169
     * @param string $path
170
     * @return null|string
171
     */
172
    private function resolvePossibleAlias($path)
173
    {
174
        $normalizedPath = Helper::normalizePath($path);
175
        return strstr($normalizedPath, '/', true) ?: null;
176
    }
177

    
178
    /**
179
     * @param string $baseName
180
     * @return null|PharInvocation
181
     */
182
    private function findByBaseName($baseName)
183
    {
184
        return Manager::instance()->getCollection()->findByCallback(
185
            function (PharInvocation $candidate) use ($baseName) {
186
                return $candidate->getBaseName() === $baseName;
187
            },
188
            true
189
        );
190
    }
191

    
192
    /**
193
     * @param string $path
194
     * @return null|string
195
     */
196
    private function findInBaseNames($path)
197
    {
198
        // return directly if the resolved base name was submitted
199
        if (in_array($path, $this->baseNames, true)) {
200
            return $path;
201
        }
202

    
203
        $parts = explode('/', Helper::normalizePath($path));
204

    
205
        while (count($parts)) {
206
            $currentPath = implode('/', $parts);
207
            if (isset($this->baseNames[$currentPath])) {
208
                return $currentPath;
209
            }
210
            array_pop($parts);
211
        }
212

    
213
        return null;
214
    }
215

    
216
    /**
217
     * @param string $baseName
218
     */
219
    private function addBaseName($baseName)
220
    {
221
        if (isset($this->baseNames[$baseName])) {
222
            return;
223
        }
224
        $this->baseNames[$baseName] = Helper::normalizeWindowsPath(
225
            realpath($baseName)
226
        );
227
    }
228

    
229
    /**
230
     * Finds confirmed(!) invocations by alias.
231
     *
232
     * @param string $path
233
     * @return null|PharInvocation
234
     * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
235
     */
236
    private function findByAlias($path)
237
    {
238
        $possibleAlias = $this->resolvePossibleAlias($path);
239
        if ($possibleAlias === null) {
240
            return null;
241
        }
242
        return Manager::instance()->getCollection()->findByCallback(
243
            function (PharInvocation $candidate) use ($possibleAlias) {
244
                return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias;
245
            },
246
            true
247
        );
248
    }
249
}