Projet

Général

Profil

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

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
}