Glob.php 6.0 KB

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