1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Stdlib;
11
12/**
13 * Wrapper for glob with fallback if GLOB_BRACE is not available.
14 */
15abstract class Glob
16{
17    /**#@+
18     * Glob constants.
19     */
20    const GLOB_MARK     = 0x01;
21    const GLOB_NOSORT   = 0x02;
22    const GLOB_NOCHECK  = 0x04;
23    const GLOB_NOESCAPE = 0x08;
24    const GLOB_BRACE    = 0x10;
25    const GLOB_ONLYDIR  = 0x20;
26    const GLOB_ERR      = 0x40;
27    /**#@-*/
28
29    /**
30     * Find pathnames matching a pattern.
31     *
32     * @see    http://docs.php.net/glob
33     * @param  string  $pattern
34     * @param  int $flags
35     * @param  bool $forceFallback
36     * @return array
37     * @throws Exception\RuntimeException
38     */
39    public static function glob($pattern, $flags = 0, $forceFallback = false)
40    {
41        if (!defined('GLOB_BRACE') || $forceFallback) {
42            return static::fallbackGlob($pattern, $flags);
43        }
44
45        return static::systemGlob($pattern, $flags);
46    }
47
48    /**
49     * Use the glob function provided by the system.
50     *
51     * @param  string  $pattern
52     * @param  int     $flags
53     * @return array
54     * @throws Exception\RuntimeException
55     */
56    protected static function systemGlob($pattern, $flags)
57    {
58        if ($flags) {
59            $flagMap = array(
60                self::GLOB_MARK     => GLOB_MARK,
61                self::GLOB_NOSORT   => GLOB_NOSORT,
62                self::GLOB_NOCHECK  => GLOB_NOCHECK,
63                self::GLOB_NOESCAPE => GLOB_NOESCAPE,
64                self::GLOB_BRACE    => GLOB_BRACE,
65                self::GLOB_ONLYDIR  => GLOB_ONLYDIR,
66                self::GLOB_ERR      => GLOB_ERR,
67            );
68
69            $globFlags = 0;
70
71            foreach ($flagMap as $internalFlag => $globFlag) {
72                if ($flags & $internalFlag) {
73                    $globFlags |= $globFlag;
74                }
75            }
76        } else {
77            $globFlags = 0;
78        }
79
80        ErrorHandler::start();
81        $res = glob($pattern, $globFlags);
82        $err = ErrorHandler::stop();
83        if ($res === false) {
84            throw new Exception\RuntimeException("glob('{$pattern}', {$globFlags}) failed", 0, $err);
85        }
86        return $res;
87    }
88
89    /**
90     * Expand braces manually, then use the system glob.
91     *
92     * @param  string  $pattern
93     * @param  int     $flags
94     * @return array
95     * @throws Exception\RuntimeException
96     */
97    protected static function fallbackGlob($pattern, $flags)
98    {
99        if (!$flags & self::GLOB_BRACE) {
100            return static::systemGlob($pattern, $flags);
101        }
102
103        $flags &= ~self::GLOB_BRACE;
104        $length = strlen($pattern);
105        $paths  = array();
106
107        if ($flags & self::GLOB_NOESCAPE) {
108            $begin = strpos($pattern, '{');
109        } else {
110            $begin = 0;
111
112            while (true) {
113                if ($begin === $length) {
114                    $begin = false;
115                    break;
116                } elseif ($pattern[$begin] === '\\' && ($begin + 1) < $length) {
117                    $begin++;
118                } elseif ($pattern[$begin] === '{') {
119                    break;
120                }
121
122                $begin++;
123            }
124        }
125
126        if ($begin === false) {
127            return static::systemGlob($pattern, $flags);
128        }
129
130        $next = static::nextBraceSub($pattern, $begin + 1, $flags);
131
132        if ($next === null) {
133            return static::systemGlob($pattern, $flags);
134        }
135
136        $rest = $next;
137
138        while ($pattern[$rest] !== '}') {
139            $rest = static::nextBraceSub($pattern, $rest + 1, $flags);
140
141            if ($rest === null) {
142                return static::systemGlob($pattern, $flags);
143            }
144        }
145
146        $p = $begin + 1;
147
148        while (true) {
149            $subPattern = substr($pattern, 0, $begin)
150                        . substr($pattern, $p, $next - $p)
151                        . substr($pattern, $rest + 1);
152
153            $result = static::fallbackGlob($subPattern, $flags | self::GLOB_BRACE);
154
155            if ($result) {
156                $paths = array_merge($paths, $result);
157            }
158
159            if ($pattern[$next] === '}') {
160                break;
161            }
162
163            $p    = $next + 1;
164            $next = static::nextBraceSub($pattern, $p, $flags);
165        }
166
167        return array_unique($paths);
168    }
169
170    /**
171     * Find the end of the sub-pattern in a brace expression.
172     *
173     * @param  string  $pattern
174     * @param  int $begin
175     * @param  int $flags
176     * @return int|null
177     */
178    protected static function nextBraceSub($pattern, $begin, $flags)
179    {
180        $length  = strlen($pattern);
181        $depth   = 0;
182        $current = $begin;
183
184        while ($current < $length) {
185            if (!$flags & self::GLOB_NOESCAPE && $pattern[$current] === '\\') {
186                if (++$current === $length) {
187                    break;
188                }
189
190                $current++;
191            } else {
192                if (($pattern[$current] === '}' && $depth-- === 0) || ($pattern[$current] === ',' && $depth === 0)) {
193                    break;
194                } elseif ($pattern[$current++] === '{') {
195                    $depth++;
196                }
197            }
198        }
199
200        return ($current < $length ? $current : null);
201    }
202}
203