├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── .scrutinizer.yml ├── ArrayTraits └── src │ ├── ArrayAccess.php │ ├── ArrayAccessWithGetters.php │ ├── Constructor.php │ ├── Countable.php │ ├── Export.php │ ├── ExportInterface.php │ ├── Iterator.php │ ├── NestedArrayAccess.php │ ├── NestedArrayAccessWithGetters.php │ └── Serializable.php ├── Blueprints ├── src │ ├── BlueprintForm.php │ ├── BlueprintSchema.php │ └── Blueprints.php └── tests │ ├── BlueprintFormTest.php │ ├── BlueprintSchemaTest.php │ ├── data │ ├── blueprint │ │ ├── basic.yaml │ │ ├── empty.yaml │ │ ├── extends.yaml │ │ ├── import.yaml │ │ └── partials │ │ │ └── address.yaml │ ├── form │ │ └── items │ │ │ ├── basic.yaml │ │ │ ├── empty.yaml │ │ │ ├── extends.yaml │ │ │ └── import.yaml │ ├── input │ │ ├── basic.yaml │ │ └── empty.yaml │ └── schema │ │ ├── defaults │ │ ├── basic.yaml │ │ └── empty.yaml │ │ ├── extra │ │ ├── basic.yaml │ │ └── empty.yaml │ │ ├── init │ │ ├── basic.yaml │ │ └── empty.yaml │ │ ├── merge │ │ ├── basic.yaml │ │ └── empty.yaml │ │ └── state │ │ ├── basic.yaml │ │ └── empty.yaml │ └── helper.php ├── CHANGELOG.md ├── Compat └── src │ └── Yaml │ ├── Exception │ ├── ExceptionInterface.php │ ├── ParseException.php │ └── RuntimeException.php │ ├── Inline.php │ ├── Parser.php │ ├── Unescaper.php │ └── Yaml.php ├── DI └── src │ ├── Container.php │ └── ServiceProviderInterface.php ├── Event └── src │ ├── Event.php │ ├── EventDispatcher.php │ └── EventSubscriberInterface.php ├── File └── src │ ├── AbstractFile.php │ ├── File.php │ ├── FileInterface.php │ ├── IniFile.php │ ├── JsonFile.php │ ├── LogFile.php │ ├── MarkdownFile.php │ ├── MoFile.php │ ├── PhpFile.php │ └── YamlFile.php ├── LICENSE ├── README.md ├── ResourceLocator ├── src │ ├── RecursiveUniformResourceIterator.php │ ├── ResourceLocatorInterface.php │ ├── UniformResourceIterator.php │ └── UniformResourceLocator.php └── tests │ ├── UniformResourceLocatorTest.php │ └── data │ ├── base │ └── all │ │ ├── base.txt │ │ ├── base_all.txt │ │ ├── base_local.txt │ │ └── base_override.txt │ ├── local │ └── all │ │ ├── base_all.txt │ │ ├── base_local.txt │ │ ├── local.txt │ │ └── local_override.txt │ └── override │ └── all │ ├── base_all.txt │ ├── base_override.txt │ ├── local_override.txt │ └── override.txt ├── Session ├── src │ ├── Message.php │ └── Session.php └── tests │ └── MessageTest.php ├── StreamWrapper └── src │ ├── ReadOnlyStream.php │ ├── Stream.php │ ├── StreamBuilder.php │ └── StreamInterface.php ├── composer.json ├── phpunit.xml └── tests └── phpstan ├── phpstan-bootstrap.php └── phpstan.neon /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: PHP Tests 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | 11 | phpstan-tests: 12 | 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | matrix: 17 | php: [ 8.1, 8.0, 7.4, 7.3, 7.2] 18 | os: [ubuntu-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php }} 27 | extensions: opcache, mbstring 28 | tools: composer:v2 29 | coverage: none 30 | env: 31 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | 33 | - name: Get composer cache directory 34 | id: composer-cache 35 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 36 | 37 | - name: Cache dependencies 38 | uses: actions/cache@v2 39 | with: 40 | path: ${{ steps.composer-cache.outputs.dir }} 41 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 42 | restore-keys: ${{ runner.os }}-composer- 43 | 44 | - name: Install dependencies 45 | run: composer install --prefer-dist --no-progress 46 | 47 | - name: Run phpstan tests 48 | run: composer phpstan 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | vendor/ 3 | coverage/ 4 | composer.lock 5 | 6 | # OS Generated 7 | .DS_Store* 8 | ehthumbs.db 9 | Icon? 10 | Thumbs.db 11 | *.swp 12 | 13 | # PhpStorm 14 | .idea/* 15 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - 'vendor/*' 4 | - '*/tests/*' 5 | checks: 6 | php: 7 | code_rating: true 8 | remove_extra_empty_lines: true 9 | remove_php_closing_tag: true 10 | remove_trailing_whitespace: true 11 | fix_use_statements: 12 | remove_unused: true 13 | preserve_multiple: false 14 | preserve_blanklines: true 15 | order_alphabetically: true 16 | fix_php_opening_tag: true 17 | fix_linefeed: true 18 | fix_line_ending: true 19 | fix_identation_4spaces: true 20 | fix_doc_comments: true 21 | tools: 22 | external_code_coverage: 23 | timeout: 600 24 | runs: 3 25 | php_code_coverage: false 26 | php_code_sniffer: 27 | config: 28 | standard: PSR2 29 | filter: 30 | paths: ['src'] 31 | php_loc: 32 | enabled: true 33 | excluded_dirs: [vendor, tests] 34 | php_cpd: 35 | enabled: true 36 | excluded_dirs: [vendor, tests] 37 | -------------------------------------------------------------------------------- /ArrayTraits/src/ArrayAccess.php: -------------------------------------------------------------------------------- 1 | items[$offset]); 24 | } 25 | 26 | /** 27 | * Returns the value at specified offset. 28 | * 29 | * @param string|int $offset The offset to retrieve. 30 | * @return mixed Can return all value types. 31 | */ 32 | #[\ReturnTypeWillChange] 33 | public function offsetGet($offset) 34 | { 35 | return isset($this->items[$offset]) ? $this->items[$offset] : null; 36 | } 37 | 38 | /** 39 | * Assigns a value to the specified offset. 40 | * 41 | * @param string|int|null $offset The offset to assign the value to. 42 | * @param mixed $value The value to set. 43 | * @return void 44 | */ 45 | #[\ReturnTypeWillChange] 46 | public function offsetSet($offset, $value) 47 | { 48 | if (null === $offset) { 49 | $this->items[] = $value; 50 | } else { 51 | $this->items[$offset] = $value; 52 | } 53 | } 54 | 55 | /** 56 | * Unsets an offset. 57 | * 58 | * @param string|int $offset The offset to unset. 59 | * @return void 60 | */ 61 | #[\ReturnTypeWillChange] 62 | public function offsetUnset($offset) 63 | { 64 | // Hack to make Iterator trait work together with unset. 65 | if (isset($this->iteratorUnset) && (string)$offset === (string)key($this->items)) { 66 | $this->iteratorUnset = true; 67 | } 68 | 69 | unset($this->items[$offset]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ArrayTraits/src/ArrayAccessWithGetters.php: -------------------------------------------------------------------------------- 1 | offsetSet($offset, $value); 27 | } 28 | 29 | /** 30 | * Magic getter method 31 | * 32 | * @param string $offset Asset name value 33 | * @return mixed Asset value 34 | */ 35 | #[\ReturnTypeWillChange] 36 | public function __get($offset) 37 | { 38 | return $this->offsetGet($offset); 39 | } 40 | 41 | /** 42 | * Magic method to determine if the attribute is set 43 | * 44 | * @param string $offset Asset name value 45 | * @return bool True if the value is set 46 | */ 47 | #[\ReturnTypeWillChange] 48 | public function __isset($offset) 49 | { 50 | return $this->offsetExists($offset); 51 | } 52 | 53 | /** 54 | * Magic method to unset the attribute 55 | * 56 | * @param string $offset The name value to unset 57 | * @return void 58 | */ 59 | #[\ReturnTypeWillChange] 60 | public function __unset($offset) 61 | { 62 | $this->offsetUnset($offset); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ArrayTraits/src/Constructor.php: -------------------------------------------------------------------------------- 1 | items = $items; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ArrayTraits/src/Countable.php: -------------------------------------------------------------------------------- 1 | items); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ArrayTraits/src/Export.php: -------------------------------------------------------------------------------- 1 | items; 26 | } 27 | 28 | /** 29 | * Convert object into YAML string. 30 | * 31 | * @param int $inline The level where you switch to inline YAML. 32 | * @param int $indent The amount of spaces to use for indentation of nested nodes. 33 | * @return string A YAML string representing the object. 34 | * @throws DumpException 35 | */ 36 | public function toYaml($inline = 3, $indent = 2) 37 | { 38 | return Yaml::dump($this->toArray(), $inline, $indent, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); 39 | } 40 | 41 | /** 42 | * Convert object into JSON string. 43 | * 44 | * @return string 45 | */ 46 | public function toJson() 47 | { 48 | $string = json_encode($this->toArray()); 49 | if (!\is_string($string)) { 50 | throw new RuntimeException('Failed to encode array', 500); 51 | } 52 | 53 | return $string; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ArrayTraits/src/ExportInterface.php: -------------------------------------------------------------------------------- 1 | items); 26 | } 27 | 28 | /** 29 | * Returns the key of the current element. 30 | * 31 | * @return string|null Returns key on success, or NULL on failure. 32 | */ 33 | #[\ReturnTypeWillChange] 34 | public function key() 35 | { 36 | return (string)key($this->items); 37 | } 38 | 39 | /** 40 | * Moves the current position to the next element. 41 | * 42 | * @return void 43 | */ 44 | #[\ReturnTypeWillChange] 45 | public function next() 46 | { 47 | if ($this->iteratorUnset) { 48 | // If current item was unset, position is already in the next element (do nothing). 49 | $this->iteratorUnset = false; 50 | } else { 51 | next($this->items); 52 | } 53 | } 54 | 55 | /** 56 | * Rewinds back to the first element of the Iterator. 57 | * 58 | * @return void 59 | */ 60 | #[\ReturnTypeWillChange] 61 | public function rewind() 62 | { 63 | $this->iteratorUnset = false; 64 | reset($this->items); 65 | } 66 | 67 | /** 68 | * This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid. 69 | * 70 | * @return bool Returns TRUE on success or FALSE on failure. 71 | */ 72 | #[\ReturnTypeWillChange] 73 | public function valid() 74 | { 75 | return key($this->items) !== null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ArrayTraits/src/NestedArrayAccess.php: -------------------------------------------------------------------------------- 1 | get('this.is.my.nested.variable'); 27 | * 28 | * @param string $name Dot separated path to the requested value. 29 | * @param mixed $default Default value (or null). 30 | * @param string|null $separator Separator, defaults to '.' 31 | * @return mixed Value. 32 | */ 33 | public function get($name, $default = null, $separator = null) 34 | { 35 | $current = $this->items; 36 | if ($name === '') { 37 | return $current; 38 | } 39 | 40 | $path = explode($separator ?: $this->nestedSeparator, $name); 41 | 42 | foreach ($path as $field) { 43 | if (is_object($current) && isset($current->{$field})) { 44 | $current = $current->{$field}; 45 | } elseif (is_array($current) && isset($current[$field])) { 46 | $current = $current[$field]; 47 | } else { 48 | return $default; 49 | } 50 | } 51 | 52 | return $current; 53 | } 54 | 55 | /** 56 | * Set value by using dot notation for nested arrays/objects. 57 | * 58 | * @example $data->set('this.is.my.nested.variable', $value); 59 | * 60 | * @param string $name Dot separated path to the requested value. 61 | * @param mixed $value New value. 62 | * @param string|null $separator Separator, defaults to '.' 63 | * @return $this 64 | */ 65 | public function set($name, $value, $separator = null) 66 | { 67 | $path = $name !== '' ? explode($separator ?: $this->nestedSeparator, $name) : []; 68 | $current = &$this->items; 69 | 70 | foreach ($path as $field) { 71 | if (is_object($current)) { 72 | // Handle objects. 73 | if (!isset($current->{$field})) { 74 | $current->{$field} = []; 75 | } 76 | $current = &$current->{$field}; 77 | } else { 78 | // Handle arrays and scalars. 79 | if (!is_array($current)) { 80 | $current = [$field => []]; 81 | } elseif (!isset($current[$field])) { 82 | $current[$field] = []; 83 | } 84 | $current = &$current[$field]; 85 | } 86 | } 87 | 88 | $current = $value; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * Unset value by using dot notation for nested arrays/objects. 95 | * 96 | * @example $data->undef('this.is.my.nested.variable'); 97 | * 98 | * @param string $name Dot separated path to the requested value. 99 | * @param string|null $separator Separator, defaults to '.' 100 | * @return $this 101 | */ 102 | public function undef($name, $separator = null) 103 | { 104 | // Handle empty string. 105 | if ($name === '') { 106 | $this->items = []; 107 | 108 | return $this; 109 | } 110 | 111 | $path = explode($separator ?: $this->nestedSeparator, $name); 112 | 113 | $var = array_pop($path); 114 | $current = &$this->items; 115 | 116 | foreach ($path as $field) { 117 | if (is_object($current)) { 118 | // Handle objects. 119 | if (!isset($current->{$field})) { 120 | return $this; 121 | } 122 | $current = &$current->{$field}; 123 | } else { 124 | // Handle arrays and scalars. 125 | if (!is_array($current) || !isset($current[$field])) { 126 | return $this; 127 | } 128 | $current = &$current[$field]; 129 | } 130 | } 131 | 132 | unset($current[$var]); 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * Set default value by using dot notation for nested arrays/objects. 139 | * 140 | * @example $data->def('this.is.my.nested.variable', 'default'); 141 | * 142 | * @param string $name Dot separated path to the requested value. 143 | * @param mixed $default Default value (or null). 144 | * @param string|null $separator Separator, defaults to '.' 145 | * @return $this 146 | */ 147 | public function def($name, $default = null, $separator = null) 148 | { 149 | $this->set($name, $this->get($name, $default, $separator), $separator); 150 | 151 | return $this; 152 | } 153 | 154 | /** 155 | * Whether or not an offset exists. 156 | * 157 | * @param string $offset An offset to check for. 158 | * @return bool Returns TRUE on success or FALSE on failure. 159 | */ 160 | #[\ReturnTypeWillChange] 161 | public function offsetExists($offset) 162 | { 163 | return $this->get($offset) !== null; 164 | } 165 | 166 | /** 167 | * Returns the value at specified offset. 168 | * 169 | * @param string $offset The offset to retrieve. 170 | * @return mixed Can return all value types. 171 | */ 172 | #[\ReturnTypeWillChange] 173 | public function offsetGet($offset) 174 | { 175 | return $this->get($offset); 176 | } 177 | 178 | /** 179 | * Assigns a value to the specified offset. 180 | * 181 | * @param string|null $offset The offset to assign the value to. 182 | * @param mixed $value The value to set. 183 | * @return void 184 | */ 185 | #[\ReturnTypeWillChange] 186 | public function offsetSet($offset, $value) 187 | { 188 | if (null === $offset) { 189 | $this->items[] = $value; 190 | } else { 191 | $this->set($offset, $value); 192 | } 193 | } 194 | 195 | /** 196 | * Unsets variable at specified offset. 197 | * 198 | * @param string|null $offset 199 | * @return void 200 | */ 201 | #[\ReturnTypeWillChange] 202 | public function offsetUnset($offset) 203 | { 204 | if (null === $offset) { 205 | $this->items[] = []; 206 | } else { 207 | $this->undef($offset); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /ArrayTraits/src/NestedArrayAccessWithGetters.php: -------------------------------------------------------------------------------- 1 | offsetSet($offset, $value); 27 | } 28 | 29 | /** 30 | * Magic getter method 31 | * 32 | * @param string $offset Asset name value 33 | * @return mixed Asset value 34 | */ 35 | #[\ReturnTypeWillChange] 36 | public function __get($offset) 37 | { 38 | return $this->offsetGet($offset); 39 | } 40 | 41 | /** 42 | * Magic method to determine if the attribute is set 43 | * 44 | * @param string $offset Asset name value 45 | * @return bool True if the value is set 46 | */ 47 | #[\ReturnTypeWillChange] 48 | public function __isset($offset) 49 | { 50 | return $this->offsetExists($offset); 51 | } 52 | 53 | /** 54 | * Magic method to unset the attribute 55 | * 56 | * @param string $offset The name value to unset 57 | * @return void 58 | */ 59 | #[\ReturnTypeWillChange] 60 | public function __unset($offset) 61 | { 62 | $this->offsetUnset($offset); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ArrayTraits/src/Serializable.php: -------------------------------------------------------------------------------- 1 | __serialize()); 25 | } 26 | 27 | /** 28 | * Called during unserialization of the object. 29 | * 30 | * @param string $serialized The string representation of the object. 31 | * @return void 32 | * @final 33 | * @deprecated Override `__unserialize()` instead. 34 | */ 35 | #[\ReturnTypeWillChange] 36 | public function unserialize($serialized) 37 | { 38 | $this->__unserialize(unserialize($serialized)); 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | #[\ReturnTypeWillChange] 45 | public function __serialize() 46 | { 47 | return $this->items; 48 | } 49 | 50 | /** 51 | * @param array $data 52 | * @return void 53 | */ 54 | #[\ReturnTypeWillChange] 55 | public function __unserialize($data) 56 | { 57 | $this->items = $data; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Blueprints/src/Blueprints.php: -------------------------------------------------------------------------------- 1 | setOverrides( 17 | ['extends' => ['extends', 'basic']] 18 | ); 19 | $blueprint->load(); 20 | 21 | // Save test results if they do not exist (data needs to be verified by human!) 22 | $resultFile = YamlFile::instance(__DIR__ . '/data/form/items/' . $test . '.yaml'); 23 | if (!$resultFile->exists()) { 24 | $resultFile->content(['unverified' => true] + $blueprint->toArray()); 25 | $resultFile->save(); 26 | } 27 | 28 | // Test 1: Loaded form. 29 | $this->assertEquals($this->loadYaml($test, 'form/items'), $blueprint->toArray()); 30 | 31 | } 32 | 33 | public function dataProvider() 34 | { 35 | return [ 36 | ['empty'], 37 | ['basic'], 38 | ['import'], 39 | ['extends'] 40 | ]; 41 | } 42 | 43 | protected function loadYaml($test, $type = 'blueprint') 44 | { 45 | $file = YamlFile::instance(__DIR__ . "/data/{$type}/{$test}.yaml"); 46 | $content = $file->content(); 47 | $file->free(); 48 | 49 | return $content; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Blueprints/tests/BlueprintSchemaTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 16 | [ 17 | 'items' => [], 18 | 'rules' => [], 19 | 'nested' => [], 20 | 'dynamic' => [], 21 | 'filter' => ['validation' => true] 22 | ], 23 | $blueprints->getState()); 24 | 25 | $this->assertEquals([], $blueprints->getDefaults()); 26 | } 27 | 28 | /** 29 | * @dataProvider dataProvider 30 | */ 31 | public function testLoad($test) 32 | { 33 | $blueprint = new Blueprint($test); 34 | $blueprint->load()->init(); 35 | 36 | $schema = $blueprint->schema(); 37 | 38 | // Save test results if they do not exist (data needs to be verified by human!) 39 | $resultFile = YamlFile::instance(__DIR__ . '/data/schema/state/' . $test . '.yaml'); 40 | if (!$resultFile->exists()) { 41 | $resultFile->content(['unverified' => true] + $schema->getState()); 42 | $resultFile->save(); 43 | } 44 | 45 | // Test 1: Internal state. 46 | $this->assertEquals($this->loadYaml($test, 'schema/state'), $schema->getState()); 47 | 48 | $schema->init(); 49 | 50 | // Save test results if they do not exist (data needs to be verified by human!) 51 | $resultFile = YamlFile::instance(__DIR__ . '/data/schema/init/' . $test . '.yaml'); 52 | if (!$resultFile->exists()) { 53 | $resultFile->content(['unverified' => true] + $schema->getState()); 54 | $resultFile->save(); 55 | } 56 | 57 | // Test 2: Initialize blueprint. 58 | $this->assertEquals($this->loadYaml($test, 'schema/init'), $schema->getState()); 59 | 60 | // Test 3: Default values. 61 | $this->assertEquals($this->loadYaml($test, 'schema/defaults'), $schema->getDefaults()); 62 | 63 | // Test 4: Extra values. 64 | $this->assertEquals($this->loadYaml($test, 'schema/extra'), $schema->extra($this->loadYaml($test, 'input'))); 65 | 66 | // Test 5: Merge data. 67 | $this->assertEquals( 68 | $this->loadYaml($test, 'schema/merge'), 69 | $schema->mergeData($schema->getDefaults(), $this->loadYaml($test, 'input')) 70 | ); 71 | } 72 | 73 | public function dataProvider() 74 | { 75 | return [ 76 | ['empty'], 77 | ['basic'], 78 | ]; 79 | } 80 | 81 | protected function loadYaml($test, $type = 'blueprint') 82 | { 83 | $file = YamlFile::instance(__DIR__ . "/data/{$type}/{$test}.yaml"); 84 | $content = $file->content(); 85 | $file->free(); 86 | 87 | return $content; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Blueprints/tests/data/blueprint/basic.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | name: 3 | pattern: "[A-Z][a-z]+" 4 | min: 3 5 | max: 15 6 | 7 | form: 8 | validation: loose 9 | fields: 10 | text: 11 | type: text 12 | label: Text 13 | placeholder: 'Enter your text' 14 | 15 | enabled: 16 | type: select 17 | label: Enabled 18 | default: true 19 | data-options@: blueprint_data_option_test 20 | 21 | user.name: 22 | type: name 23 | label: Name 24 | default: John 25 | validate: 26 | type: name 27 | 28 | user.country: 29 | type: select 30 | label: Enabled 31 | default: fi 32 | data-options@: 33 | - blueprint_data_option_test 34 | - { us: 'United States', fi: 'Finland', po: 'Poland' } 35 | - true 36 | 37 | undefined: 38 | type: select 39 | label: Undefined 40 | data-options@: undefined_function 41 | 42 | -------------------------------------------------------------------------------- /Blueprints/tests/data/blueprint/empty.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/Blueprints/tests/data/blueprint/empty.yaml -------------------------------------------------------------------------------- /Blueprints/tests/data/blueprint/extends.yaml: -------------------------------------------------------------------------------- 1 | extends@: parent@ 2 | 3 | form: 4 | fields: 5 | enabled: 6 | default: false 7 | 8 | text: 9 | default: My text 10 | ordering@: enabled 11 | 12 | user.name: 13 | default: Joe 14 | unset-validate@: true 15 | 16 | undefined: 17 | unset@: true 18 | -------------------------------------------------------------------------------- /Blueprints/tests/data/blueprint/import.yaml: -------------------------------------------------------------------------------- 1 | form: 2 | fields: 3 | address: 4 | import@: partials/address 5 | -------------------------------------------------------------------------------- /Blueprints/tests/data/blueprint/partials/address.yaml: -------------------------------------------------------------------------------- 1 | form: 2 | fields: 3 | user.address: 4 | type: text 5 | label: Address 6 | 7 | user.zip: 8 | type: text 9 | label: ZIP code 10 | 11 | user.city: 12 | type: text 13 | label: City 14 | 15 | user.country: 16 | type: text 17 | label: Country -------------------------------------------------------------------------------- /Blueprints/tests/data/form/items/basic.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | name: 3 | pattern: '[A-Z][a-z]+' 4 | min: 3 5 | max: 15 6 | form: 7 | validation: loose 8 | fields: 9 | text: 10 | type: text 11 | label: Text 12 | placeholder: 'Enter your text' 13 | name: text 14 | enabled: 15 | type: select 16 | label: Enabled 17 | default: true 18 | data-options@: blueprint_data_option_test 19 | name: enabled 20 | user.name: 21 | type: name 22 | label: Name 23 | default: John 24 | validate: 25 | type: name 26 | name: user.name 27 | user.country: 28 | type: select 29 | label: Enabled 30 | default: fi 31 | data-options@: 32 | - blueprint_data_option_test 33 | - { us: 'United States', fi: Finland, po: Poland } 34 | - true 35 | name: user.country 36 | undefined: 37 | type: select 38 | label: Undefined 39 | data-options@: undefined_function 40 | name: undefined 41 | -------------------------------------------------------------------------------- /Blueprints/tests/data/form/items/empty.yaml: -------------------------------------------------------------------------------- 1 | { } -------------------------------------------------------------------------------- /Blueprints/tests/data/form/items/extends.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | name: 3 | pattern: '[A-Z][a-z]+' 4 | min: 3 5 | max: 15 6 | form: 7 | validation: loose 8 | fields: 9 | enabled: 10 | type: select 11 | label: Enabled 12 | default: false 13 | data-options@: blueprint_data_option_test 14 | name: enabled 15 | text: 16 | type: text 17 | label: Text 18 | placeholder: 'Enter your text' 19 | default: 'My text' 20 | name: text 21 | user.name: 22 | type: name 23 | label: Name 24 | default: Joe 25 | name: user.name 26 | user.country: 27 | type: select 28 | label: Enabled 29 | default: fi 30 | data-options@: 31 | - blueprint_data_option_test 32 | - { us: 'United States', fi: Finland, po: Poland } 33 | - true 34 | name: user.country 35 | -------------------------------------------------------------------------------- /Blueprints/tests/data/form/items/import.yaml: -------------------------------------------------------------------------------- 1 | form: 2 | fields: 3 | address: 4 | fields: 5 | user.address: { type: text, label: Address, name: user.address } 6 | user.zip: { type: text, label: 'ZIP code', name: user.zip } 7 | user.city: { type: text, label: City, name: user.city } 8 | user.country: { type: text, label: Country, name: user.country } 9 | -------------------------------------------------------------------------------- /Blueprints/tests/data/input/basic.yaml: -------------------------------------------------------------------------------- 1 | text: Testing... 2 | user: 3 | name: Igor 4 | extra: data... 5 | 6 | some: 7 | random: false 8 | variables: 9 | - true 10 | - false 11 | just: for 12 | fun: 13 | -------------------------------------------------------------------------------- /Blueprints/tests/data/input/empty.yaml: -------------------------------------------------------------------------------- 1 | some: 2 | random: false 3 | variables: 4 | - true 5 | - false 6 | just: for 7 | fun: 8 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/defaults/basic.yaml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | user: 3 | name: John 4 | country: fi -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/defaults/empty.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/Blueprints/tests/data/schema/defaults/empty.yaml -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/extra/basic.yaml: -------------------------------------------------------------------------------- 1 | some: 2 | random: false 3 | variables: 4 | - true 5 | - false 6 | just: for 7 | fun: 8 | user.extra: data... 9 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/extra/empty.yaml: -------------------------------------------------------------------------------- 1 | some: 2 | random: false 3 | variables: 4 | - true 5 | - false 6 | just: for 7 | fun: 8 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/init/basic.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | '': 3 | form: 4 | validation: loose 5 | type: _root 6 | form_field: false 7 | text: 8 | type: text 9 | label: Text 10 | placeholder: 'Enter your text' 11 | validation: loose 12 | name: text 13 | enabled: 14 | type: select 15 | label: Enabled 16 | default: true 17 | data-options@: blueprint_data_option_test 18 | validation: loose 19 | name: enabled 20 | options: 21 | 'yes': 'Yes' 22 | 'no': 'No' 23 | user: 24 | type: _parent 25 | name: user 26 | form_field: false 27 | user.name: 28 | type: name 29 | label: Name 30 | default: John 31 | validate: 32 | type: name 33 | validation: loose 34 | name: user.name 35 | user.country: 36 | type: select 37 | label: Enabled 38 | default: fi 39 | data-options@: 40 | - blueprint_data_option_test 41 | - 42 | us: 'United States' 43 | fi: Finland 44 | po: Poland 45 | - true 46 | validation: loose 47 | name: user.country 48 | options: 49 | fi: Finland 50 | po: Poland 51 | us: 'United States' 52 | undefined: 53 | type: select 54 | label: Undefined 55 | data-options@: undefined_function 56 | validation: loose 57 | name: undefined 58 | rules: 59 | name: 60 | pattern: '[A-Z][a-z]+' 61 | min: 3 62 | max: 15 63 | nested: 64 | '': '' 65 | text: text 66 | enabled: enabled 67 | user: 68 | name: user.name 69 | country: user.country 70 | undefined: undefined 71 | dynamic: 72 | enabled: 73 | options: 74 | action: data 75 | params: blueprint_data_option_test 76 | user.country: 77 | options: 78 | action: data 79 | params: 80 | - blueprint_data_option_test 81 | - { us: 'United States', fi: Finland, po: Poland } 82 | - true 83 | undefined: 84 | options: 85 | action: data 86 | params: undefined_function 87 | filter: 88 | validation: true 89 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/init/empty.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | '': 3 | form: { } 4 | type: _root 5 | form_field: false 6 | rules: { } 7 | nested: 8 | '': '' 9 | dynamic: { } 10 | filter: 11 | validation: true 12 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/merge/basic.yaml: -------------------------------------------------------------------------------- 1 | text: Testing... 2 | user: 3 | name: Igor 4 | country: fi 5 | extra: data... 6 | 7 | some: 8 | random: false 9 | variables: 10 | - true 11 | - false 12 | just: for 13 | fun: 14 | enabled: true 15 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/merge/empty.yaml: -------------------------------------------------------------------------------- 1 | some: 2 | random: false 3 | variables: 4 | - true 5 | - false 6 | just: for 7 | fun: 8 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/state/basic.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | '': 3 | form: 4 | validation: loose 5 | type: _root 6 | form_field: false 7 | text: 8 | type: text 9 | label: Text 10 | placeholder: 'Enter your text' 11 | validation: loose 12 | name: text 13 | enabled: 14 | type: select 15 | label: Enabled 16 | default: true 17 | data-options@: blueprint_data_option_test 18 | validation: loose 19 | name: enabled 20 | user: 21 | type: _parent 22 | name: user 23 | form_field: false 24 | user.name: 25 | type: name 26 | label: Name 27 | default: John 28 | validate: 29 | type: name 30 | validation: loose 31 | name: user.name 32 | user.country: 33 | type: select 34 | label: Enabled 35 | default: fi 36 | data-options@: 37 | - blueprint_data_option_test 38 | - 39 | us: 'United States' 40 | fi: Finland 41 | po: Poland 42 | - true 43 | validation: loose 44 | name: user.country 45 | undefined: 46 | type: select 47 | label: Undefined 48 | data-options@: undefined_function 49 | validation: loose 50 | name: undefined 51 | rules: 52 | name: 53 | pattern: '[A-Z][a-z]+' 54 | min: 3 55 | max: 15 56 | nested: 57 | '': '' 58 | text: text 59 | enabled: enabled 60 | user: 61 | name: user.name 62 | country: user.country 63 | undefined: undefined 64 | dynamic: { } 65 | filter: 66 | validation: true 67 | -------------------------------------------------------------------------------- /Blueprints/tests/data/schema/state/empty.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | '': 3 | form: { } 4 | type: _root 5 | form_field: false 6 | rules: { } 7 | nested: 8 | '': '' 9 | dynamic: { } 10 | filter: 11 | validation: true 12 | -------------------------------------------------------------------------------- /Blueprints/tests/helper.php: -------------------------------------------------------------------------------- 1 | 'Yes', 'no' => 'No']; 14 | } 15 | 16 | 17 | class Blueprint extends BlueprintForm 18 | { 19 | /** 20 | * @return BlueprintSchema 21 | */ 22 | public function schema() 23 | { 24 | $schema = new BlueprintSchema(); 25 | $schema->embed('', $this->items); 26 | 27 | return $schema; 28 | } 29 | 30 | /** 31 | * @param string $filename 32 | * @return string 33 | */ 34 | protected function loadFile($filename) 35 | { 36 | $file = YamlFile::instance(__DIR__ . "/data/blueprint/{$filename}.yaml"); 37 | $content = $file->content(); 38 | $file->free(); 39 | 40 | return $content; 41 | } 42 | 43 | /** 44 | * @param string|array $path 45 | * @param string $context 46 | * @return array 47 | */ 48 | protected function getFiles($path, $context = null) 49 | { 50 | if (is_string($path)) { 51 | // Resolve filename. 52 | if (isset($this->overrides[$path])) { 53 | $path = $this->overrides[$path]; 54 | } else { 55 | if ($context === null) { 56 | $context = $this->context; 57 | } 58 | if ($context && $context[strlen($context)-1] !== '/') { 59 | $context .= '/'; 60 | } 61 | $path = $context . $path; 62 | } 63 | } 64 | 65 | return (array)$path; 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.6.5 2 | ## 05/09/2023 3 | 4 | 1. [](#bugfix) 5 | * Fixed another issue with PHP 8.2 6 | 7 | # v1.6.4 8 | ## 03/24/2023 9 | 10 | 1. [](#bugfix) 11 | * Fixed an issue with PHP 8.2 [#36](https://github.com/rockettheme/toolbox/pull/36) 12 | 13 | # v1.6.3 14 | ## 02/19/2023 15 | 16 | 1. [](#bugfix) 17 | * Fixed a bug in ReadOnlyStream that throws deprecated warning PHP 8.2 18 | 19 | # v1.6.2 20 | ## 06/14/2022 21 | 22 | 1. [](#bugfix) 23 | * Removed support for Symfony 5 Event Dispatcher due to compatibility issues [#32](https://github.com/rockettheme/toolbox/issues/32) 24 | 25 | # v1.6.1 26 | ## 02/08/2022 27 | 28 | 1. [](#new) 29 | * Added support for Symfony 5 YAML and Event Dispatcher 30 | 1. [](#bugfix) 31 | * Fixed PHP 5.6 and 7.0 compatibility 32 | 33 | # v1.6.0 34 | ## 12/15/2021 35 | 36 | 1. [](#new) 37 | * Added **PHP 8.1** support 38 | * If you use `ArrayTraits\Seriazable`, make sure you do not override the methods (use PHP 8 methods instead) 39 | 40 | # v1.5.11 41 | ## 10/25/2021 42 | 43 | 1. [](#new) 44 | * Updated phpstan to v1.0 45 | 1. [](#improved) 46 | * Added `parent@: true` option for blueprints to include rules for parent fields 47 | 1. [](#bugfix) 48 | * Fixed deprecated warnings in PHP 8.1 49 | 50 | # v1.5.10 51 | ## 09/29/2021 52 | 53 | 1. [](#improved) 54 | * Improved `UniformResourceLocator` to support `file://` scheme 55 | 1. [](#bugfix) 56 | * Fixed blueprint merge where second blueprint has non-array field definition 57 | * Fixed implicit cast from null to string in `UniformResourceLocator` 58 | 59 | # v1.5.9 60 | ## 04/14/2021 61 | 62 | 1. [](#bugfix) 63 | * Fixed regression in default field type settings 64 | 65 | # v1.5.8 66 | ## 04/12/2021 67 | 68 | 1. [](#bugfix) 69 | * Fixed default field type settings not being merged recursively causing default validation rules to get missing 70 | 71 | # v1.5.7 72 | ## 02/17/2021 73 | 74 | 1. [](#new) 75 | * Pass new phpstan level 8 tests 76 | 1. [](#bugfix) 77 | * Fixed `Trying to access array offset on value of type int` in `BlueprintSchema::getFieldKey()` 78 | 79 | # v1.5.6 80 | ## 12/03/2020 81 | 82 | 1. [](#bugfix) 83 | * Fixed incompatible `File` class 84 | 85 | # v1.5.5 86 | ## 12/01/2020 87 | 88 | 1. [](#bugfix) 89 | * Fixed a PHP8 issue in YAML library 90 | 91 | # v1.5.4 92 | ## 11/27/2020 93 | 94 | 1. [](#new) 95 | * Added PHP 8.0 support 96 | 1. [](#bugfix) 97 | * Fixed `UniformResourceLocator::addPath()` with PHP 8 98 | 99 | # v1.5.3 100 | ## 11/23/2020 101 | 102 | 1. [](#bugfix) 103 | * Fixed `UniformResourceLocator::addPath()` not working properly if override is a stream 104 | 105 | # v1.5.2 106 | ## 05/18/2020 107 | 108 | 1. [](#improved) 109 | * Support symlinks when saving `File` (#30, thanks @schliflo) 110 | 111 | # v1.5.1 112 | ## 03/19/2020 113 | 114 | 1. [](#bugfix) 115 | * Fixed static method call from Blueprints 116 | 117 | # v1.5.0 118 | ## 02/03/2020 119 | 120 | 1. [](#new) 121 | * Updated minimum requirement to PHP 5.6.0 122 | * Deprecated Event classes in favor of PSR-14 123 | * PHP 7.4 compatibility: implemented required `Stream::stream_set_option()` method (#28, thanks @lcharette) 124 | * Pass phpstan level 8 tests 125 | * Added new `UniformResourceLocator::getResource()` method to simplify code where filename is always required 126 | * Added support for `replace-name@` in blueprints (#24, thanks @drzraf) 127 | * Calling `File::instance()` with empty filename is now deprecated 128 | 1. [](#bugfix) 129 | * Fixed `new UniformResourceItarator()` not throwing exception when path is non-existing 130 | * Fixed missing frontmatter if markdown file had UTF-8 BOM (#14, thanks @A----) 131 | * Fixed many other edge cases 132 | 133 | # v1.4.6 134 | ## 03/20/2019 135 | 136 | 1. [](#bugfix) 137 | * Fixed `File::writable()` returning true if an existing file is read-only with the folder being writable 138 | * Fixed `File::save()` silently ignoring failures with read only streams 139 | * Regression: Fixed file saving when temporary file cannot be created to the current folder / stream 140 | 141 | # v1.4.5 142 | ## 02/28/2019 143 | 144 | 1. [](#bugfix) 145 | * Regression: Fixed undefined variable in `BlueprintSchema` 146 | 147 | # v1.4.4 148 | ## 02/28/2019 149 | 150 | 1. [](#bugfix) 151 | * Regression: Fixed issue with directory creation when saving non-existing file 152 | 153 | # v1.4.3 154 | ## 02/26/2019 155 | 156 | 1. [](#improved) 157 | * Minor code optimizations 158 | * Improved `File::save()` to use a temporary file if file isn't locked 159 | 1. [](#bugfix) 160 | * Fixed `Obtaining write lock failed on file...` 161 | * Fixed `mkdir(...)` race condition 162 | 163 | # v1.4.2 164 | ## 08/08/2018 165 | 166 | 1. [](#new) 167 | * Added `UniformResourceLocator::clearCache()` to allow resource cache to be cleared 168 | * Added `$extends` parameter to `BlueprintForm::load()` to override `extends@` 169 | 1. [](#improved) 170 | * Improved messages in `Stream` exceptions 171 | 1. [](#bugfix) 172 | * Fixed bugs when using `mkdir()`, `rmdir()`, `rename()` or creating new files with URIs 173 | 174 | # v1.4.1 175 | ## 06/20/2018 176 | 177 | 1. [](#bugfix) 178 | * Fixed a bug in blueprint extend and embed 179 | 180 | # v1.4.0 181 | ## 06/13/2018 182 | 183 | 1. [](#new) 184 | * `BlueprintForm`: Implemented support for multiple `import@`s and partial `import@`s (#17) 185 | 1. [](#improved) 186 | * `YamlFile`: Added support for `@data` without quoting it (fixes issues with Symfony 3.4 if `compat=true`) 187 | * `YamlFile`: Added compatibility mode which falls back to Symfony YAML 2.8.38 if parsing with newer version fails 188 | * `YamlFile`: Make `compat` and `native` settings global, enable `native` setting by default 189 | * General code cleanup, some optimizations 190 | 1. [](#bugfix) 191 | * `Session`: Removed broken request counter 192 | 193 | # v1.3.9 194 | ## 10/08/2017 195 | 196 | 1. [](#improved) 197 | * Modified `MarkdownFile::encode()` to dump header with 20 levels of indention (was 5) 198 | 199 | # v1.3.8 200 | ## 09/23/2017 201 | 202 | 1. [](#bugfix) 203 | * Fixed bad PHP docblock that was breaking API generation 204 | 205 | # v1.3.7 206 | ## 08/28/2017 207 | 208 | 1. [](#bugfix) 209 | * Fixed `Event` backwards compatibility by removing getters support 210 | 211 | # v1.3.6 212 | ## 08/16/2017 213 | 214 | 1. [](#improved) 215 | * Improved Event class to support getters and export 216 | 217 | # v1.3.5 218 | ## 05/22/2017 219 | 220 | 1. [](#improved) 221 | * Improved exception message in `File::content()` class when failing to load the data 222 | 1. [](#bugfix) 223 | * Fixed `Blueprintform::resolve()` to use slash notation by default instead of dot notation 224 | * Fixed warning if badly formatted `$path` parameter is given to `UniformResourceLocator::addPath()` 225 | * Fixed `Blueprintform::fields()` returning bad value if there were no fields 226 | 227 | # v1.3.4 228 | ## 05/15/2017 229 | 230 | 1. [](#new) 231 | * Blueprint: Add support for a single array field in forms 232 | 1. [](#bugfix) 233 | * Fixed `IniFile::content()` should not fail if file doesn't exist 234 | * Session: Protection against invalid session cookie name throwing exception 235 | * Session: Do not destroy session on CLI 236 | * BlueprintSchema: Fixed warning when field list is not what was expected 237 | 238 | # v1.3.3 239 | ## 10/06/2016 240 | 241 | 1. [](#improved) 242 | * Allow calls without parameter in `UniformResourceLocator::getPaths()` 243 | * Add support for `BlueprintSchema::getPropertyName()` and `getProperty()` 244 | * Add domain parameter to Session constructor 245 | * Add support for `FilesystemIterator::FOLLOW_SYMLINKS` in RecursiveUniformResourceIterator class 246 | 247 | # v1.3.2 248 | ## 05/24/2016 249 | 250 | 1. [](#new) 251 | * Added a new function BlueprintForm::getFilename() 252 | 1. [](#bugfix) 253 | * BlueprintsForm: Detect if user really meant to extend parent blueprint, not another one 254 | 255 | # v1.3.1 256 | ## 04/25/2016 257 | 258 | 1. [](#new) 259 | * Add new function File::rename() 260 | * Add new function UniformResourceLocator::fillCache() 261 | 1. [](#bugfix) 262 | * Fix collections support in BluprintSchema::extra() 263 | * Fix exception in stream wrapper when scheme is not defined in locator 264 | * Prevent UniformResourceLocator from resolving paths outside of defined scheme paths (#8) 265 | * Fix breaking YAML files which start with three dashes (#5) 266 | 267 | # v1.3.0 268 | ## 03/07/2016 269 | 270 | 1. [](#new) 271 | * Add new function UniformResourceLocator::isStream() 272 | * Add new class BlueprintForm 273 | * Renamed Blueprints class into BlueprintSchema 274 | * Add new function BlueprintSchema::extra() to return data fields which haven't been defined in blueprints 275 | * Add support to unset and replace blueprint fields and properties 276 | * Allow arbitrary dynamic fields in Blueprints (property@) 277 | * Add default properties support for form field types 278 | * Remove dependency on ircmaxell/password-compat 279 | * Add support for Symfony 3 280 | * Add a few unit tests 281 | 1. [](#improved) 282 | * UniformResourceLocator::addPath(): Add option to add path after existing one (falls back to be last if path is not found) 283 | 1. [](#bugfix) 284 | * Fix blueprint without a form 285 | * Fix merging data with empty blueprint 286 | 287 | # v1.2.0 288 | ## 10/24/2015 289 | 290 | 1. [](#new) 291 | * **Backwards compatibility break**: Blueprints class needs to be initialized with `init()` if blueprints contain `@data-*` fields 292 | * Renamed NestedArrayAccess::remove() into NestedArrayAccess::undef() to avoid name clashes 293 | 294 | # v1.1.4 295 | ## 10/15/2015 296 | 297 | 1. [](#new) 298 | * Add support for native YAML parsing option to Markdown and YAML file classes 299 | 300 | # v1.1.3 301 | ## 09/14/2015 302 | 303 | 1. [](#bugfix) 304 | * Fix regression: Default values for collections were broken 305 | * Fix Argument 1 passed to `RocketTheme\Toolbox\Blueprints\Blueprints::mergeArrays()` must be of the type array 306 | * Add exception on Blueprint collection merging; only overridden value should be used 307 | * File locking truncates contents of the file 308 | * Stop duplicate Messages getting added to the queue 309 | 310 | # v1.1.2 311 | ## 08/27/2015 312 | 313 | 1. [](#new) 314 | * Creation of Changelog 315 | -------------------------------------------------------------------------------- /Compat/src/Yaml/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace RocketTheme\Toolbox\Compat\Yaml\Exception; 13 | 14 | /** 15 | * Exception interface for all exceptions thrown by the component. 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | interface ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Compat/src/Yaml/Exception/ParseException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace RocketTheme\Toolbox\Compat\Yaml\Exception; 13 | 14 | /** 15 | * Exception class thrown when an error occurs during parsing. 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | class ParseException extends RuntimeException 20 | { 21 | private $parsedFile; 22 | private $parsedLine; 23 | private $snippet; 24 | private $rawMessage; 25 | 26 | /** 27 | * @param string $message The error message 28 | * @param int $parsedLine The line where the error occurred 29 | * @param string|null $snippet The snippet of code near the problem 30 | * @param string|null $parsedFile The file name where the error occurred 31 | * @param \Exception|null $previous The previous exception 32 | */ 33 | public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) 34 | { 35 | $this->parsedFile = $parsedFile; 36 | $this->parsedLine = $parsedLine; 37 | $this->snippet = $snippet; 38 | $this->rawMessage = $message; 39 | 40 | $this->updateRepr(); 41 | 42 | parent::__construct($this->message, 0, $previous); 43 | } 44 | 45 | /** 46 | * Gets the snippet of code near the error. 47 | * 48 | * @return string The snippet of code 49 | */ 50 | public function getSnippet() 51 | { 52 | return $this->snippet; 53 | } 54 | 55 | /** 56 | * Sets the snippet of code near the error. 57 | * 58 | * @param string $snippet The code snippet 59 | */ 60 | public function setSnippet($snippet) 61 | { 62 | $this->snippet = $snippet; 63 | 64 | $this->updateRepr(); 65 | } 66 | 67 | /** 68 | * Gets the filename where the error occurred. 69 | * 70 | * This method returns null if a string is parsed. 71 | * 72 | * @return string The filename 73 | */ 74 | public function getParsedFile() 75 | { 76 | return $this->parsedFile; 77 | } 78 | 79 | /** 80 | * Sets the filename where the error occurred. 81 | * 82 | * @param string $parsedFile The filename 83 | */ 84 | public function setParsedFile($parsedFile) 85 | { 86 | $this->parsedFile = $parsedFile; 87 | 88 | $this->updateRepr(); 89 | } 90 | 91 | /** 92 | * Gets the line where the error occurred. 93 | * 94 | * @return int The file line 95 | */ 96 | public function getParsedLine() 97 | { 98 | return $this->parsedLine; 99 | } 100 | 101 | /** 102 | * Sets the line where the error occurred. 103 | * 104 | * @param int $parsedLine The file line 105 | */ 106 | public function setParsedLine($parsedLine) 107 | { 108 | $this->parsedLine = $parsedLine; 109 | 110 | $this->updateRepr(); 111 | } 112 | 113 | private function updateRepr() 114 | { 115 | $this->message = $this->rawMessage; 116 | 117 | $dot = false; 118 | if ('.' === substr($this->message, -1)) { 119 | $this->message = substr($this->message, 0, -1); 120 | $dot = true; 121 | } 122 | 123 | if (null !== $this->parsedFile) { 124 | if (\PHP_VERSION_ID >= 50400) { 125 | $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; 126 | } else { 127 | $jsonOptions = 0; 128 | } 129 | $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions)); 130 | } 131 | 132 | if ($this->parsedLine >= 0) { 133 | $this->message .= sprintf(' at line %d', $this->parsedLine); 134 | } 135 | 136 | if ($this->snippet) { 137 | $this->message .= sprintf(' (near "%s")', $this->snippet); 138 | } 139 | 140 | if ($dot) { 141 | $this->message .= '.'; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Compat/src/Yaml/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace RocketTheme\Toolbox\Compat\Yaml\Exception; 13 | 14 | /** 15 | * Exception class thrown when an error occurs during parsing. 16 | * 17 | * @author Romain Neutron 18 | */ 19 | class RuntimeException extends \RuntimeException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Compat/src/Yaml/Inline.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace RocketTheme\Toolbox\Compat\Yaml; 13 | 14 | use RocketTheme\Toolbox\Compat\Yaml\Exception\ParseException; 15 | 16 | /** 17 | * Inline implements a YAML parser/dumper for the YAML inline syntax. 18 | * 19 | * @author Fabien Potencier 20 | */ 21 | class Inline 22 | { 23 | const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; 24 | 25 | private static $exceptionOnInvalidType = false; 26 | private static $objectSupport = false; 27 | private static $objectForMap = false; 28 | 29 | /** 30 | * Converts a YAML string to a PHP value. 31 | * 32 | * @param string $value A YAML string 33 | * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise 34 | * @param bool $objectSupport True if object support is enabled, false otherwise 35 | * @param bool $objectForMap True if maps should return a stdClass instead of array() 36 | * @param array $references Mapping of variable names to values 37 | * 38 | * @return mixed A PHP value 39 | * 40 | * @throws ParseException 41 | */ 42 | public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array()) 43 | { 44 | self::$exceptionOnInvalidType = $exceptionOnInvalidType; 45 | self::$objectSupport = $objectSupport; 46 | self::$objectForMap = $objectForMap; 47 | 48 | $value = trim($value ?? ''); 49 | 50 | if ('' === $value) { 51 | return ''; 52 | } 53 | 54 | if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { 55 | $mbEncoding = mb_internal_encoding(); 56 | mb_internal_encoding('ASCII'); 57 | } 58 | 59 | $i = 0; 60 | switch ($value[0]) { 61 | case '[': 62 | $result = self::parseSequence($value, $i, $references); 63 | ++$i; 64 | break; 65 | case '{': 66 | $result = self::parseMapping($value, $i, $references); 67 | ++$i; 68 | break; 69 | default: 70 | $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references); 71 | } 72 | 73 | // some comments are allowed at the end 74 | if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { 75 | throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); 76 | } 77 | 78 | if (isset($mbEncoding)) { 79 | mb_internal_encoding($mbEncoding); 80 | } 81 | 82 | return $result; 83 | } 84 | 85 | /** 86 | * Check if given array is hash or just normal indexed array. 87 | * 88 | * @internal 89 | * 90 | * @param array $value The PHP array to check 91 | * 92 | * @return bool true if value is hash array, false otherwise 93 | */ 94 | public static function isHash(array $value) 95 | { 96 | $expectedKey = 0; 97 | 98 | foreach ($value as $key => $val) { 99 | if ($key !== $expectedKey++) { 100 | return true; 101 | } 102 | } 103 | 104 | return false; 105 | } 106 | 107 | /** 108 | * Parses a YAML scalar. 109 | * 110 | * @param string $scalar 111 | * @param string[] $delimiters 112 | * @param string[] $stringDelimiters 113 | * @param int &$i 114 | * @param bool $evaluate 115 | * @param array $references 116 | * 117 | * @return string 118 | * 119 | * @throws ParseException When malformed inline YAML string is parsed 120 | * 121 | * @internal 122 | */ 123 | public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) 124 | { 125 | if (in_array($scalar[$i], $stringDelimiters)) { 126 | // quoted scalar 127 | $output = self::parseQuotedScalar($scalar, $i); 128 | 129 | if (null !== $delimiters) { 130 | $tmp = ltrim(substr($scalar, $i), ' '); 131 | if (!in_array($tmp[0], $delimiters)) { 132 | throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); 133 | } 134 | } 135 | } else { 136 | // "normal" string 137 | if (!$delimiters) { 138 | $output = substr($scalar, $i); 139 | $i += strlen($output); 140 | 141 | // remove comments 142 | if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) { 143 | $output = substr($output, 0, $match[0][1]); 144 | } 145 | } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { 146 | $output = $match[1]; 147 | $i += strlen($output); 148 | } else { 149 | throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar)); 150 | } 151 | 152 | // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) 153 | if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) { 154 | @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED); 155 | 156 | // to be thrown in 3.0 157 | // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0])); 158 | } 159 | 160 | if ($evaluate) { 161 | $output = self::evaluateScalar($output, $references); 162 | } 163 | } 164 | 165 | return $output; 166 | } 167 | 168 | /** 169 | * Parses a YAML quoted scalar. 170 | * 171 | * @param string $scalar 172 | * @param int &$i 173 | * 174 | * @return string 175 | * 176 | * @throws ParseException When malformed inline YAML string is parsed 177 | */ 178 | private static function parseQuotedScalar($scalar, &$i) 179 | { 180 | if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { 181 | throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i))); 182 | } 183 | 184 | $output = substr($match[0], 1, strlen($match[0]) - 2); 185 | 186 | $unescaper = new Unescaper(); 187 | if ('"' == $scalar[$i]) { 188 | $output = $unescaper->unescapeDoubleQuotedString($output); 189 | } else { 190 | $output = $unescaper->unescapeSingleQuotedString($output); 191 | } 192 | 193 | $i += strlen($match[0]); 194 | 195 | return $output; 196 | } 197 | 198 | /** 199 | * Parses a YAML sequence. 200 | * 201 | * @param string $sequence 202 | * @param int &$i 203 | * @param array $references 204 | * 205 | * @return array 206 | * 207 | * @throws ParseException When malformed inline YAML string is parsed 208 | */ 209 | private static function parseSequence($sequence, &$i = 0, $references = array()) 210 | { 211 | $output = array(); 212 | $len = strlen($sequence); 213 | ++$i; 214 | 215 | // [foo, bar, ...] 216 | while ($i < $len) { 217 | switch ($sequence[$i]) { 218 | case '[': 219 | // nested sequence 220 | $output[] = self::parseSequence($sequence, $i, $references); 221 | break; 222 | case '{': 223 | // nested mapping 224 | $output[] = self::parseMapping($sequence, $i, $references); 225 | break; 226 | case ']': 227 | return $output; 228 | case ',': 229 | case ' ': 230 | break; 231 | default: 232 | $isQuoted = in_array($sequence[$i], array('"', "'")); 233 | $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references); 234 | 235 | // the value can be an array if a reference has been resolved to an array var 236 | if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) { 237 | // embedded mapping? 238 | try { 239 | $pos = 0; 240 | $value = self::parseMapping('{'.$value.'}', $pos, $references); 241 | } catch (\InvalidArgumentException $e) { 242 | // no, it's not 243 | } 244 | } 245 | 246 | $output[] = $value; 247 | 248 | --$i; 249 | } 250 | 251 | ++$i; 252 | } 253 | 254 | throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence)); 255 | } 256 | 257 | /** 258 | * Parses a YAML mapping. 259 | * 260 | * @param string $mapping 261 | * @param int &$i 262 | * @param array $references 263 | * 264 | * @return array|\stdClass 265 | * 266 | * @throws ParseException When malformed inline YAML string is parsed 267 | */ 268 | private static function parseMapping($mapping, &$i = 0, $references = array()) 269 | { 270 | $output = array(); 271 | $len = strlen($mapping); 272 | ++$i; 273 | $allowOverwrite = false; 274 | 275 | // {foo: bar, bar:foo, ...} 276 | while ($i < $len) { 277 | switch ($mapping[$i]) { 278 | case ' ': 279 | case ',': 280 | ++$i; 281 | continue 2; 282 | case '}': 283 | if (self::$objectForMap) { 284 | return (object) $output; 285 | } 286 | 287 | return $output; 288 | } 289 | 290 | // key 291 | $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); 292 | 293 | if ('<<' === $key) { 294 | $allowOverwrite = true; 295 | } 296 | 297 | // value 298 | $done = false; 299 | 300 | while ($i < $len) { 301 | switch ($mapping[$i]) { 302 | case '[': 303 | // nested sequence 304 | $value = self::parseSequence($mapping, $i, $references); 305 | // Spec: Keys MUST be unique; first one wins. 306 | // Parser cannot abort this mapping earlier, since lines 307 | // are processed sequentially. 308 | // But overwriting is allowed when a merge node is used in current block. 309 | if ('<<' === $key) { 310 | foreach ($value as $parsedValue) { 311 | $output += $parsedValue; 312 | } 313 | } elseif ($allowOverwrite || !isset($output[$key])) { 314 | $output[$key] = $value; 315 | } 316 | $done = true; 317 | break; 318 | case '{': 319 | // nested mapping 320 | $value = self::parseMapping($mapping, $i, $references); 321 | // Spec: Keys MUST be unique; first one wins. 322 | // Parser cannot abort this mapping earlier, since lines 323 | // are processed sequentially. 324 | // But overwriting is allowed when a merge node is used in current block. 325 | if ('<<' === $key) { 326 | $output += $value; 327 | } elseif ($allowOverwrite || !isset($output[$key])) { 328 | $output[$key] = $value; 329 | } 330 | $done = true; 331 | break; 332 | case ':': 333 | case ' ': 334 | break; 335 | default: 336 | $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references); 337 | // Spec: Keys MUST be unique; first one wins. 338 | // Parser cannot abort this mapping earlier, since lines 339 | // are processed sequentially. 340 | // But overwriting is allowed when a merge node is used in current block. 341 | if ('<<' === $key) { 342 | $output += $value; 343 | } elseif ($allowOverwrite || !isset($output[$key])) { 344 | $output[$key] = $value; 345 | } 346 | $done = true; 347 | --$i; 348 | } 349 | 350 | ++$i; 351 | 352 | if ($done) { 353 | continue 2; 354 | } 355 | } 356 | } 357 | 358 | throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping)); 359 | } 360 | 361 | /** 362 | * Evaluates scalars and replaces magic values. 363 | * 364 | * @param string $scalar 365 | * @param array $references 366 | * 367 | * @return mixed The evaluated YAML string 368 | * 369 | * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved 370 | */ 371 | private static function evaluateScalar($scalar, $references = array()) 372 | { 373 | $scalar = trim($scalar); 374 | $scalarLower = strtolower($scalar); 375 | 376 | if (0 === strpos($scalar, '*')) { 377 | if (false !== $pos = strpos($scalar, '#')) { 378 | $value = substr($scalar, 1, $pos - 2); 379 | } else { 380 | $value = substr($scalar, 1); 381 | } 382 | 383 | // an unquoted * 384 | if (false === $value || '' === $value) { 385 | throw new ParseException('A reference must contain at least one character.'); 386 | } 387 | 388 | if (!array_key_exists($value, $references)) { 389 | throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); 390 | } 391 | 392 | return $references[$value]; 393 | } 394 | 395 | switch (true) { 396 | case 'null' === $scalarLower: 397 | case '' === $scalar: 398 | case '~' === $scalar: 399 | return; 400 | case 'true' === $scalarLower: 401 | return true; 402 | case 'false' === $scalarLower: 403 | return false; 404 | // Optimise for returning strings. 405 | case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]): 406 | switch (true) { 407 | case 0 === strpos($scalar, '!str'): 408 | return (string) substr($scalar, 5); 409 | case 0 === strpos($scalar, '! '): 410 | return (int) self::parseScalar(substr($scalar, 2)); 411 | case 0 === strpos($scalar, '!php/object:'): 412 | if (self::$objectSupport) { 413 | return unserialize(substr($scalar, 12)); 414 | } 415 | 416 | if (self::$exceptionOnInvalidType) { 417 | throw new ParseException('Object support when parsing a YAML file has been disabled.'); 418 | } 419 | 420 | return; 421 | case 0 === strpos($scalar, '!!php/object:'): 422 | if (self::$objectSupport) { 423 | return unserialize(substr($scalar, 13)); 424 | } 425 | 426 | if (self::$exceptionOnInvalidType) { 427 | throw new ParseException('Object support when parsing a YAML file has been disabled.'); 428 | } 429 | 430 | return; 431 | case 0 === strpos($scalar, '!!float '): 432 | return (float) substr($scalar, 8); 433 | case ctype_digit($scalar): 434 | $raw = $scalar; 435 | $cast = (int) $scalar; 436 | 437 | return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); 438 | case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): 439 | $raw = $scalar; 440 | $cast = (int) $scalar; 441 | 442 | return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); 443 | case is_numeric($scalar): 444 | case Parser::preg_match(self::getHexRegex(), $scalar): 445 | return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; 446 | case '.inf' === $scalarLower: 447 | case '.nan' === $scalarLower: 448 | return -log(0); 449 | case '-.inf' === $scalarLower: 450 | return log(0); 451 | case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): 452 | return (float) str_replace(',', '', $scalar); 453 | case Parser::preg_match(self::getTimestampRegex(), $scalar): 454 | $timeZone = date_default_timezone_get(); 455 | date_default_timezone_set('UTC'); 456 | $time = strtotime($scalar); 457 | date_default_timezone_set($timeZone); 458 | 459 | return $time; 460 | } 461 | // no break 462 | default: 463 | return (string) $scalar; 464 | } 465 | } 466 | 467 | /** 468 | * Gets a regex that matches a YAML date. 469 | * 470 | * @return string The regular expression 471 | * 472 | * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 473 | */ 474 | private static function getTimestampRegex() 475 | { 476 | return <<[0-9][0-9][0-9][0-9]) 479 | -(?P[0-9][0-9]?) 480 | -(?P[0-9][0-9]?) 481 | (?:(?:[Tt]|[ \t]+) 482 | (?P[0-9][0-9]?) 483 | :(?P[0-9][0-9]) 484 | :(?P[0-9][0-9]) 485 | (?:\.(?P[0-9]*))? 486 | (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) 487 | (?::(?P[0-9][0-9]))?))?)? 488 | $~x 489 | EOF; 490 | } 491 | 492 | /** 493 | * Gets a regex that matches a YAML number in hexadecimal notation. 494 | * 495 | * @return string 496 | */ 497 | private static function getHexRegex() 498 | { 499 | return '~^0x[0-9a-f]++$~i'; 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /Compat/src/Yaml/Unescaper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace RocketTheme\Toolbox\Compat\Yaml; 13 | 14 | /** 15 | * Unescaper encapsulates unescaping rules for single and double-quoted 16 | * YAML strings. 17 | * 18 | * @author Matthew Lewinski 19 | * 20 | * @internal 21 | */ 22 | class Unescaper 23 | { 24 | /** 25 | * Parser and Inline assume UTF-8 encoding, so escaped Unicode characters 26 | * must be converted to that encoding. 27 | * 28 | * @deprecated since version 2.5, to be removed in 3.0 29 | * 30 | * @internal 31 | */ 32 | const ENCODING = 'UTF-8'; 33 | 34 | /** 35 | * Regex fragment that matches an escaped character in a double quoted string. 36 | */ 37 | const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; 38 | 39 | /** 40 | * Unescapes a single quoted string. 41 | * 42 | * @param string $value A single quoted string 43 | * 44 | * @return string The unescaped string 45 | */ 46 | public function unescapeSingleQuotedString($value) 47 | { 48 | return str_replace('\'\'', '\'', $value); 49 | } 50 | 51 | /** 52 | * Unescapes a double quoted string. 53 | * 54 | * @param string $value A double quoted string 55 | * 56 | * @return string The unescaped string 57 | */ 58 | public function unescapeDoubleQuotedString($value) 59 | { 60 | $self = $this; 61 | $callback = function ($match) use ($self) { 62 | return $self->unescapeCharacter($match[0]); 63 | }; 64 | 65 | // evaluate the string 66 | return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); 67 | } 68 | 69 | /** 70 | * Unescapes a character that was found in a double-quoted string. 71 | * 72 | * @param string $value An escaped character 73 | * 74 | * @return string The unescaped character 75 | * 76 | * @internal This method is public to be usable as callback. It should not 77 | * be used in user code. Should be changed in 3.0. 78 | */ 79 | public function unescapeCharacter($value) 80 | { 81 | switch ($value[1]) { 82 | case '0': 83 | return "\x0"; 84 | case 'a': 85 | return "\x7"; 86 | case 'b': 87 | return "\x8"; 88 | case 't': 89 | return "\t"; 90 | case "\t": 91 | return "\t"; 92 | case 'n': 93 | return "\n"; 94 | case 'v': 95 | return "\xB"; 96 | case 'f': 97 | return "\xC"; 98 | case 'r': 99 | return "\r"; 100 | case 'e': 101 | return "\x1B"; 102 | case ' ': 103 | return ' '; 104 | case '"': 105 | return '"'; 106 | case '/': 107 | return '/'; 108 | case '\\': 109 | return '\\'; 110 | case 'N': 111 | // U+0085 NEXT LINE 112 | return "\xC2\x85"; 113 | case '_': 114 | // U+00A0 NO-BREAK SPACE 115 | return "\xC2\xA0"; 116 | case 'L': 117 | // U+2028 LINE SEPARATOR 118 | return "\xE2\x80\xA8"; 119 | case 'P': 120 | // U+2029 PARAGRAPH SEPARATOR 121 | return "\xE2\x80\xA9"; 122 | case 'x': 123 | return self::utf8chr(hexdec(substr($value, 2, 2))); 124 | case 'u': 125 | return self::utf8chr(hexdec(substr($value, 2, 4))); 126 | case 'U': 127 | return self::utf8chr(hexdec(substr($value, 2, 8))); 128 | default: 129 | @trigger_error('Not escaping a backslash in a double-quoted string is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', E_USER_DEPRECATED); 130 | 131 | return $value; 132 | } 133 | } 134 | 135 | /** 136 | * Get the UTF-8 character for the given code point. 137 | * 138 | * @param int $c The unicode code point 139 | * 140 | * @return string The corresponding UTF-8 character 141 | */ 142 | private static function utf8chr($c) 143 | { 144 | if (0x80 > $c %= 0x200000) { 145 | return chr($c); 146 | } 147 | if (0x800 > $c) { 148 | return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); 149 | } 150 | if (0x10000 > $c) { 151 | return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); 152 | } 153 | 154 | return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Compat/src/Yaml/Yaml.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace RocketTheme\Toolbox\Compat\Yaml; 13 | 14 | use RocketTheme\Toolbox\Compat\Yaml\Exception\ParseException; 15 | 16 | /** 17 | * Yaml offers convenience methods to load and dump YAML. 18 | * 19 | * @author Fabien Potencier 20 | */ 21 | class Yaml 22 | { 23 | /** 24 | * Parses YAML into a PHP value. 25 | * 26 | * Usage: 27 | * 28 | * $array = Yaml::parse(file_get_contents('config.yml')); 29 | * print_r($array); 30 | * 31 | * 32 | * As this method accepts both plain strings and file names as an input, 33 | * you must validate the input before calling this method. Passing a file 34 | * as an input is a deprecated feature and will be removed in 3.0. 35 | * 36 | * Note: the ability to pass file names to the Yaml::parse method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. 37 | * 38 | * @param string $input Path to a YAML file or a string containing YAML 39 | * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise 40 | * @param bool $objectSupport True if object support is enabled, false otherwise 41 | * @param bool $objectForMap True if maps should return a stdClass instead of array() 42 | * 43 | * @return mixed The YAML converted to a PHP value 44 | * 45 | * @throws ParseException If the YAML is not valid 46 | */ 47 | public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) 48 | { 49 | // if input is a file, process it 50 | $file = ''; 51 | if (false === strpos($input, "\n") && is_file($input)) { 52 | @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); 53 | 54 | if (false === is_readable($input)) { 55 | throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); 56 | } 57 | 58 | $file = $input; 59 | $input = file_get_contents($file); 60 | } 61 | 62 | $yaml = new Parser(); 63 | 64 | try { 65 | return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap); 66 | } catch (ParseException $e) { 67 | if ($file) { 68 | $e->setParsedFile($file); 69 | } 70 | 71 | throw $e; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /DI/src/Container.php: -------------------------------------------------------------------------------- 1 | filename === null checks). 49 | //throw new \InvalidArgumentException('Filename should be non-empty string'); 50 | return new static(); 51 | } 52 | 53 | if (!isset(static::$instances[$filename])) { 54 | static::$instances[$filename] = new static(); 55 | static::$instances[$filename]->init($filename); 56 | } 57 | 58 | return static::$instances[$filename]; 59 | } 60 | 61 | /** 62 | * Set/get settings. 63 | * 64 | * @param array|null $settings 65 | * @return array 66 | */ 67 | public function settings(array $settings = null) 68 | { 69 | if ($settings !== null) { 70 | $this->settings = $settings; 71 | } 72 | 73 | return $this->settings; 74 | } 75 | 76 | /** 77 | * Get setting. 78 | * 79 | * @param string $setting 80 | * @param mixed $default 81 | * @return mixed 82 | */ 83 | public function setting($setting, $default = null) 84 | { 85 | return isset($this->settings[$setting]) ? $this->settings[$setting] : $default; 86 | } 87 | 88 | /** 89 | * Prevent constructor from being used. 90 | */ 91 | protected function __construct() 92 | { 93 | } 94 | 95 | /** 96 | * Prevent cloning. 97 | */ 98 | protected function __clone() 99 | { 100 | //Me not like clones! Me smash clones! 101 | } 102 | 103 | /** 104 | * Set filename. 105 | * 106 | * @param string $filename 107 | * @return void 108 | */ 109 | protected function init($filename) 110 | { 111 | $this->filename = $filename; 112 | } 113 | 114 | /** 115 | * Free the file instance. 116 | * 117 | * @return void 118 | */ 119 | public function free() 120 | { 121 | if ($this->locked) { 122 | $this->unlock(); 123 | } 124 | $this->content = null; 125 | $this->raw = null; 126 | 127 | if (null !== $this->filename) { 128 | unset(static::$instances[$this->filename]); 129 | } 130 | } 131 | 132 | /** 133 | * Get/set the file location. 134 | * 135 | * @param string|null $var 136 | * @return string 137 | */ 138 | public function filename($var = null) 139 | { 140 | if ($var !== null) { 141 | $this->filename = $var; 142 | } 143 | 144 | return $this->filename; 145 | } 146 | 147 | /** 148 | * Return basename of the file. 149 | * 150 | * @return string 151 | */ 152 | public function basename() 153 | { 154 | return null !== $this->filename ? basename($this->filename, $this->extension) : ''; 155 | } 156 | 157 | /** 158 | * Check if file exits. 159 | * 160 | * @return bool 161 | */ 162 | public function exists() 163 | { 164 | return null !== $this->filename && is_file($this->filename); 165 | } 166 | 167 | /** 168 | * Return file modification time. 169 | * 170 | * @return int|false Timestamp or false if file doesn't exist. 171 | */ 172 | public function modified() 173 | { 174 | return null !== $this->filename && $this->exists() ? filemtime($this->filename) : false; 175 | } 176 | 177 | /** 178 | * Lock file for writing. You need to manually unlock(). 179 | * 180 | * @param bool $block For non-blocking lock, set the parameter to false. 181 | * @return bool 182 | * @throws RuntimeException 183 | */ 184 | public function lock($block = true) 185 | { 186 | if (null === $this->filename) { 187 | throw new RuntimeException('Opening file for writing failed because of it has no filename'); 188 | } 189 | 190 | if (!$this->handle) { 191 | if (!$this->mkdir(dirname($this->filename))) { 192 | throw new RuntimeException('Creating directory failed for ' . $this->filename); 193 | } 194 | 195 | $handle = @fopen($this->filename, 'cb+'); 196 | if (!$handle) { 197 | $error = error_get_last() ?: ['message' => 'Unknown error']; 198 | 199 | throw new RuntimeException("Opening file for writing failed on error {$error['message']}"); 200 | } 201 | $this->handle = $handle; 202 | } 203 | $lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB; 204 | 205 | // Some filesystems do not support file locks, only fail if another process holds the lock. 206 | $this->locked = flock($this->handle, $lock, $wouldblock) || !$wouldblock; 207 | 208 | return $this->locked; 209 | } 210 | 211 | /** 212 | * Returns true if file has been locked for writing. 213 | * 214 | * @return bool|null True = locked, false = failed, null = not locked. 215 | */ 216 | public function locked() 217 | { 218 | return $this->locked; 219 | } 220 | 221 | /** 222 | * Unlock file. 223 | * 224 | * @return bool 225 | */ 226 | public function unlock() 227 | { 228 | if (!$this->handle) { 229 | return false; 230 | } 231 | 232 | if ($this->locked) { 233 | flock($this->handle, LOCK_UN); 234 | $this->locked = null; 235 | } 236 | 237 | fclose($this->handle); 238 | $this->handle = null; 239 | 240 | return true; 241 | } 242 | 243 | /** 244 | * Check if file can be written. 245 | * 246 | * @return bool 247 | */ 248 | public function writable() 249 | { 250 | if (null === $this->filename) { 251 | return false; 252 | } 253 | 254 | return $this->exists() ? is_writable($this->filename) : $this->writableDir(dirname($this->filename)); 255 | } 256 | 257 | /** 258 | * (Re)Load a file and return RAW file contents. 259 | * 260 | * @return string 261 | */ 262 | public function load() 263 | { 264 | $this->raw = null !== $this->filename && $this->exists() ? (string) file_get_contents($this->filename) : ''; 265 | $this->content = null; 266 | 267 | return $this->raw; 268 | } 269 | 270 | /** 271 | * Get/set raw file contents. 272 | * 273 | * @param string $var 274 | * @return string 275 | */ 276 | public function raw($var = null) 277 | { 278 | if ($var !== null) { 279 | $this->raw = (string) $var; 280 | $this->content = null; 281 | } 282 | 283 | if (!is_string($this->raw)) { 284 | $this->raw = $this->load(); 285 | } 286 | 287 | return $this->raw; 288 | } 289 | 290 | /** 291 | * Get/set parsed file contents. 292 | * 293 | * @param string|array|null $var 294 | * @return string|array 295 | * @throws RuntimeException 296 | */ 297 | public function content($var = null) 298 | { 299 | if ($var !== null) { 300 | $this->content = $this->check($var); 301 | 302 | // Update RAW, too. 303 | $this->raw = $this->encode($this->content); 304 | 305 | } elseif ($this->content === null) { 306 | // Decode RAW file. 307 | try { 308 | $this->content = $this->decode($this->raw()); 309 | } catch (Exception $e) { 310 | throw new RuntimeException(sprintf('Failed to read %s: %s', $this->filename, $e->getMessage()), 500, $e); 311 | } 312 | } 313 | 314 | return $this->content; 315 | } 316 | 317 | /** 318 | * Save file. 319 | * 320 | * @param mixed $data Optional data to be saved, usually array. 321 | * @return void 322 | * @throws RuntimeException 323 | */ 324 | public function save($data = null) 325 | { 326 | if (null === $this->filename) { 327 | throw new RuntimeException('Failed to save file: no filename'); 328 | } 329 | 330 | if ($data !== null) { 331 | $this->content($data); 332 | } 333 | 334 | $filename = $this->filename; 335 | 336 | if (is_link($filename)) { 337 | $realname = realpath($filename); 338 | if ($realname === false) { 339 | throw new RuntimeException('Failed to save file ' . $filename); 340 | } 341 | 342 | $filename = $realname; 343 | } 344 | 345 | $dir = dirname($filename); 346 | 347 | if (!$dir || !$this->mkdir($dir)) { 348 | throw new RuntimeException('Creating directory failed for ' . $filename); 349 | } 350 | 351 | try { 352 | if ($this->handle) { 353 | $tmp = true; 354 | // As we are using non-truncating locking, make sure that the file is empty before writing. 355 | if (@ftruncate($this->handle, 0) === false || @fwrite($this->handle, $this->raw()) === false) { 356 | // Writing file failed, throw an error. 357 | $tmp = false; 358 | } 359 | } else { 360 | // Create file with a temporary name and rename it to make the save action atomic. 361 | $tmp = $this->tempname($filename); 362 | if (file_put_contents($tmp, $this->raw()) === false) { 363 | $tmp = false; 364 | } elseif (@rename($tmp, $filename) === false) { 365 | @unlink($tmp); 366 | $tmp = false; 367 | } 368 | } 369 | } catch (Exception $e) { 370 | $tmp = false; 371 | } 372 | 373 | if ($tmp === false) { 374 | throw new RuntimeException('Failed to save file ' . $filename); 375 | } 376 | 377 | // Touch the directory as well, thus marking it modified. 378 | @touch($dir); 379 | } 380 | 381 | /** 382 | * Rename file in the filesystem if it exists. 383 | * 384 | * @param string $filename 385 | * @return bool 386 | */ 387 | public function rename($filename) 388 | { 389 | if (null !== $this->filename && $this->exists() && !@rename($this->filename, $filename)) { 390 | return false; 391 | } 392 | 393 | unset(static::$instances[$this->filename]); 394 | static::$instances[$filename] = $this; 395 | 396 | $this->filename = $filename; 397 | 398 | return true; 399 | } 400 | 401 | /** 402 | * Delete file from filesystem. 403 | * 404 | * @return bool 405 | */ 406 | public function delete() 407 | { 408 | return null !== $this->filename && $this->exists() && unlink($this->filename); 409 | } 410 | 411 | /** 412 | * Check contents and make sure it is in correct format. 413 | * 414 | * Override in derived class. 415 | * 416 | * @param mixed $var 417 | * @return mixed 418 | */ 419 | protected function check($var) 420 | { 421 | if (!is_string($var)) { 422 | throw new RuntimeException('Provided data is not a string'); 423 | } 424 | 425 | return $var; 426 | } 427 | 428 | /** 429 | * Encode contents into RAW string. 430 | * 431 | * Override in derived class. 432 | * 433 | * @param array|string $var 434 | * @return string 435 | */ 436 | protected function encode($var) 437 | { 438 | return is_string($var) ? $var : ''; 439 | } 440 | 441 | /** 442 | * Decode RAW string into contents. 443 | * 444 | * Override in derived class. 445 | * 446 | * @param string $var 447 | * @return array|string 448 | */ 449 | protected function decode($var) 450 | { 451 | return $var; 452 | } 453 | 454 | /** 455 | * @param string $dir 456 | * @return bool 457 | */ 458 | private function mkdir($dir) 459 | { 460 | // Silence error for open_basedir; should fail in mkdir instead. 461 | if (@is_dir($dir)) { 462 | return true; 463 | } 464 | 465 | $success = @mkdir($dir, 0777, true); 466 | 467 | if (!$success) { 468 | // Take yet another look, make sure that the folder doesn't exist. 469 | clearstatcache(true, $dir); 470 | if (!@is_dir($dir)) { 471 | return false; 472 | } 473 | } 474 | 475 | return true; 476 | } 477 | 478 | /** 479 | * @param string $dir 480 | * @return bool 481 | * @internal 482 | */ 483 | protected function writableDir($dir) 484 | { 485 | if ($dir && !file_exists($dir)) { 486 | return $this->writableDir(dirname($dir)); 487 | } 488 | 489 | return $dir && is_dir($dir) && is_writable($dir); 490 | } 491 | 492 | /** 493 | * @param string $filename 494 | * @param int $length 495 | * @return string 496 | */ 497 | protected function tempname($filename, $length = 5) 498 | { 499 | do { 500 | $test = $filename . substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length); 501 | } while (file_exists($test)); 502 | 503 | return $test; 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /File/src/File.php: -------------------------------------------------------------------------------- 1 | $value) { 61 | $string .= $key . '="' . preg_replace( 62 | ['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"], 63 | ['\"', '\\\\', '\t', '\n', '\r'], 64 | $value 65 | ) . "\"\n"; 66 | } 67 | 68 | return $string; 69 | } 70 | 71 | /** 72 | * Decode INI file into contents. 73 | * 74 | * @param string $var 75 | * @return array 76 | * @throws RuntimeException 77 | */ 78 | protected function decode($var) 79 | { 80 | $decoded = null !== $this->filename && file_exists($this->filename) ? @parse_ini_file($this->filename) : []; 81 | 82 | if ($decoded === false) { 83 | throw new RuntimeException("Decoding file '{$this->filename}' failed'"); 84 | } 85 | 86 | return $decoded; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /File/src/JsonFile.php: -------------------------------------------------------------------------------- 1 | extension = '.log'; 29 | } 30 | 31 | /** 32 | * @param array|null $var 33 | * @return array 34 | */ 35 | public function content($var = null) 36 | { 37 | /** @var array $content */ 38 | $content = parent::content($var); 39 | 40 | return $content; 41 | } 42 | 43 | /** 44 | * Check contents and make sure it is in correct format. 45 | * 46 | * @param mixed $var 47 | * @return array 48 | */ 49 | protected function check($var) 50 | { 51 | if (!(is_array($var) || is_object($var))) { 52 | throw new \RuntimeException('Provided data is not an array'); 53 | } 54 | 55 | return (array)$var; 56 | } 57 | 58 | /** 59 | * Encode contents into RAW string (unsupported). 60 | * 61 | * @param string $var 62 | * @return string 63 | * @throws BadMethodCallException 64 | */ 65 | protected function encode($var) 66 | { 67 | throw new BadMethodCallException('Saving log file is forbidden.'); 68 | } 69 | 70 | /** 71 | * Decode RAW string into contents. 72 | * 73 | * @param string $var 74 | * @return array 75 | */ 76 | protected function decode($var) 77 | { 78 | $lines = preg_split('#(\r\n|\n|\r)#', $var) ?: []; 79 | 80 | $results = []; 81 | foreach ($lines as $line) { 82 | preg_match('#^\[(.*)\] (.*) @ (.*) @@ (.*)$#', $line, $matches); 83 | if ($matches) { 84 | $results[] = ['date' => $matches[1], 'message' => $matches[2], 'url' => $matches[3], 'file' => $matches[4]]; 85 | } 86 | } 87 | 88 | return $results; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /File/src/MarkdownFile.php: -------------------------------------------------------------------------------- 1 | content(); 47 | 48 | if ($var !== null) { 49 | $content['header'] = $var; 50 | $this->content($content); 51 | } 52 | 53 | return $content['header']; 54 | } 55 | 56 | /** 57 | * Get/set markdown content. 58 | * 59 | * @param string|null $var 60 | * @return string 61 | */ 62 | public function markdown($var = null) 63 | { 64 | $content = $this->content(); 65 | 66 | if ($var !== null) { 67 | $content['markdown'] = (string)$var; 68 | $this->content($content); 69 | } 70 | 71 | return $content['markdown']; 72 | } 73 | 74 | /** 75 | * Get/set frontmatter content. 76 | * 77 | * @param string|null $var 78 | * @return string 79 | */ 80 | public function frontmatter($var = null) 81 | { 82 | $content = $this->content(); 83 | 84 | if ($var !== null) { 85 | $content['frontmatter'] = (string)$var; 86 | $this->content($content); 87 | } 88 | 89 | return $content['frontmatter']; 90 | } 91 | 92 | /** 93 | * Check contents and make sure it is in correct format. 94 | * 95 | * @param mixed $var 96 | * @return array 97 | */ 98 | protected function check($var) 99 | { 100 | if (!(is_array($var) || is_object($var))) { 101 | throw new \RuntimeException('Provided data is not an array'); 102 | } 103 | 104 | $var = (array) $var; 105 | if (!isset($var['header']) || !is_array($var['header'])) { 106 | $var['header'] = []; 107 | } 108 | if (!isset($var['markdown']) || !\is_string($var['markdown'])) { 109 | $var['markdown'] = ''; 110 | } 111 | 112 | return $var; 113 | } 114 | 115 | /** 116 | * Encode contents into RAW string. 117 | * 118 | * @param array $var 119 | * @return string 120 | */ 121 | protected function encode($var) 122 | { 123 | // Create Markdown file with YAML header. 124 | $o = (!empty($var['header']) ? "---\n" . trim(YamlParser::dump($var['header'], 20)) . "\n---\n\n" : '') . $var['markdown']; 125 | 126 | // Normalize line endings to Unix style. 127 | $o = (string)preg_replace("/(\r\n|\r)/", "\n", $o); 128 | 129 | return $o; 130 | } 131 | 132 | /** 133 | * Decode RAW string into contents. 134 | * 135 | * @param string $var 136 | * @return array 137 | */ 138 | protected function decode($var) 139 | { 140 | $content = [ 141 | 'header' => false, 142 | 'frontmatter' => '' 143 | ]; 144 | 145 | $frontmatter_regex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis"; 146 | 147 | // Remove UTF-8 BOM if it exists. 148 | $var = ltrim($var, "\xef\xbb\xbf"); 149 | 150 | // Normalize line endings to Unix style. 151 | $var = (string)preg_replace("/(\r\n|\r)/", "\n", $var); 152 | 153 | // Parse header. 154 | preg_match($frontmatter_regex, ltrim($var), $m); 155 | if(!empty($m)) { 156 | // Normalize frontmatter. 157 | $content['frontmatter'] = $frontmatter = preg_replace("/\n\t/", "\n ", $m[1]); 158 | 159 | // Try native PECL YAML PHP extension first if available. 160 | if (\function_exists('yaml_parse') && $this->setting('native')) { 161 | // Safely decode YAML. 162 | $saved = @ini_get('yaml.decode_php'); 163 | @ini_set('yaml.decode_php', '0'); 164 | $content['header'] = @yaml_parse("---\n" . $frontmatter . "\n..."); 165 | if ($saved !== false) { 166 | @ini_set('yaml.decode_php', $saved); 167 | } 168 | } 169 | 170 | if ($content['header'] === false) { 171 | // YAML hasn't been parsed yet (error or extension isn't available). Fall back to Symfony parser. 172 | try { 173 | $content['header'] = (array) YamlParser::parse($frontmatter); 174 | } catch (ParseException $e) { 175 | if (!$this->setting('compat', true)) { 176 | throw $e; 177 | } 178 | $content['header'] = (array) FallbackYamlParser::parse($frontmatter); 179 | } 180 | } 181 | $content['markdown'] = $m[2]; 182 | } else { 183 | $content['header'] = []; 184 | $content['markdown'] = $var; 185 | } 186 | 187 | return $content; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /File/src/MoFile.php: -------------------------------------------------------------------------------- 1 | endian = 'V'; 84 | $this->str = $var; 85 | $this->len = strlen($var); 86 | 87 | $magic = $this->readInt() & 0xffffffff; 88 | 89 | if ($magic === 0x950412de) { 90 | // Low endian. 91 | $this->endian = 'V'; 92 | } elseif ($magic === 0xde120495) { 93 | // Big endian. 94 | $this->endian = 'N'; 95 | } else { 96 | throw new RuntimeException('Not a Gettext file (.mo).'); 97 | } 98 | 99 | // Skip revision number. 100 | $rev = $this->readInt(); 101 | // Total count. 102 | $total = $this->readInt(); 103 | // Offset of original table. 104 | $originals = $this->readInt(); 105 | // Offset of translation table. 106 | $translations = $this->readInt(); 107 | 108 | if ($originals === false || $translations === false) { 109 | throw new RuntimeException('Bad Gettext file.'); 110 | } 111 | 112 | // Each table consists of string length and offset of the string. 113 | $this->seek($originals); 114 | $table_originals = $this->readIntArray($total * 2); 115 | $this->seek($translations); 116 | $table_translations = $this->readIntArray($total * 2); 117 | 118 | if ($table_originals === false || $table_translations === false) { 119 | throw new RuntimeException('Bad Gettext file.'); 120 | } 121 | 122 | $items = []; 123 | for ($i = 0; $i < $total; $i++) { 124 | $this->seek($table_originals[$i * 2 + 2]); 125 | 126 | // TODO: Original string can have context concatenated on it. We do not yet support that. 127 | $original = $this->read($table_originals[$i * 2 + 1]); 128 | 129 | if ($original) { 130 | $this->seek($table_translations[$i * 2 + 2]); 131 | 132 | // TODO: Plural forms are stored by letting the plural of the original string follow the singular of the original string, separated through a NUL byte. 133 | $translated = $this->read($table_translations[$i * 2 + 1]); 134 | $items[$original] = $translated; 135 | } 136 | } 137 | 138 | return $items; 139 | } 140 | 141 | /** 142 | * @return int|false 143 | */ 144 | protected function readInt() 145 | { 146 | $read = $this->read(4); 147 | if ($read === false) { 148 | return false; 149 | } 150 | 151 | $read = unpack($this->endian, $read); 152 | if ($read === false) { 153 | return false; 154 | } 155 | 156 | return array_shift($read); 157 | } 158 | 159 | /** 160 | * @param int $count 161 | * @return array|false 162 | */ 163 | protected function readIntArray($count) 164 | { 165 | $read = $this->read(4 * $count); 166 | 167 | return is_string($read) ? unpack($this->endian . (string)$count, $read) : false; 168 | } 169 | 170 | /** 171 | * @param int $bytes 172 | * @return string|false 173 | */ 174 | private function read($bytes) 175 | { 176 | $data = substr($this->str, $this->pos, $bytes); 177 | $this->seek($this->pos + $bytes); 178 | 179 | if (strlen($data) < $bytes) { 180 | return false; 181 | } 182 | 183 | return $data; 184 | } 185 | 186 | /** 187 | * @param int $pos 188 | * @return int 189 | */ 190 | private function seek($pos) 191 | { 192 | $this->pos = $pos < $this->len ? $pos : $this->len; 193 | 194 | return $this->pos; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /File/src/PhpFile.php: -------------------------------------------------------------------------------- 1 | filename && function_exists('opcache_invalidate')) { 50 | @opcache_invalidate($this->filename, true); 51 | } 52 | } 53 | 54 | /** 55 | * Check contents and make sure it is in correct format. 56 | * 57 | * @param mixed $var 58 | * @return array 59 | * @throws RuntimeException 60 | */ 61 | protected function check($var) 62 | { 63 | if (!(is_array($var) || is_object($var))) { 64 | throw new RuntimeException('Provided data is not an array'); 65 | } 66 | 67 | return (array)$var; 68 | } 69 | 70 | /** 71 | * Encode configuration object into RAW string (PHP class). 72 | * 73 | * @param array $var 74 | * @return string 75 | * @throws RuntimeException 76 | */ 77 | protected function encode($var) 78 | { 79 | // Build the object variables string 80 | return "encodeArray((array) $var)};\n"; 81 | } 82 | 83 | /** 84 | * Method to get an array as an exported string. 85 | * 86 | * @param array $a The array to get as a string. 87 | * @param int $level Used internally to indent rows. 88 | * @return string 89 | */ 90 | protected function encodeArray(array $a, $level = 0) 91 | { 92 | $r = []; 93 | foreach ($a as $k => $v) { 94 | if (is_array($v) || is_object($v)) { 95 | $r[] = var_export($k, true) . ' => ' . $this->encodeArray((array) $v, $level + 1); 96 | } else { 97 | $r[] = var_export($k, true) . ' => ' . var_export($v, true); 98 | } 99 | } 100 | 101 | $space = str_repeat(' ', $level); 102 | 103 | return "[\n {$space}" . implode(",\n {$space}", $r) . "\n{$space}]"; 104 | } 105 | 106 | /** 107 | * Decode PHP file into contents. 108 | * 109 | * @param string $var 110 | * @return array 111 | */ 112 | protected function decode($var) 113 | { 114 | return (array)include $this->filename; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /File/src/YamlFile.php: -------------------------------------------------------------------------------- 1 | true, 27 | 'native' => true 28 | ]; 29 | 30 | /** 31 | * @param array|null $var 32 | * @return array 33 | */ 34 | public function content($var = null) 35 | { 36 | /** @var array $content */ 37 | $content = parent::content($var); 38 | 39 | return $content; 40 | } 41 | 42 | /** 43 | * Set/get settings. 44 | * 45 | * @param array|null $settings 46 | * @return array 47 | */ 48 | public static function globalSettings(array $settings = null) 49 | { 50 | if ($settings !== null) { 51 | static::$globalSettings = $settings; 52 | } 53 | 54 | return static::$globalSettings; 55 | } 56 | 57 | /** 58 | * Constructor. 59 | */ 60 | protected function __construct() 61 | { 62 | parent::__construct(); 63 | 64 | $this->extension = '.yaml'; 65 | } 66 | 67 | /** 68 | * Set/get settings. 69 | * 70 | * @param array|null $settings 71 | * @return array 72 | */ 73 | public function settings(array $settings = null) 74 | { 75 | if ($settings !== null) { 76 | $this->settings = $settings; 77 | } 78 | 79 | return $this->settings + static::$globalSettings; 80 | } 81 | 82 | /** 83 | * Get setting. 84 | * 85 | * @param string $setting 86 | * @param mixed $default 87 | * @return mixed 88 | */ 89 | public function setting($setting, $default = null) 90 | { 91 | $value = parent::setting($setting); 92 | if (null === $value) { 93 | $value = isset(static::$globalSettings[$setting]) ? static::$globalSettings[$setting] : $default; 94 | } 95 | 96 | return $value; 97 | } 98 | 99 | /** 100 | * Check contents and make sure it is in correct format. 101 | * 102 | * @param mixed $var 103 | * @return array 104 | */ 105 | protected function check($var) 106 | { 107 | if (!(is_array($var) || is_object($var))) { 108 | throw new \RuntimeException('Provided data is not an array'); 109 | } 110 | 111 | return (array)$var; 112 | } 113 | 114 | /** 115 | * Encode contents into RAW string. 116 | * 117 | * @param array $var 118 | * @return string 119 | * @throws DumpException 120 | */ 121 | protected function encode($var) 122 | { 123 | return YamlParser::dump($var, $this->setting('inline', 5), $this->setting('indent', 2), YamlParser::PARSE_EXCEPTION_ON_INVALID_TYPE); 124 | } 125 | 126 | /** 127 | * Decode RAW string into contents. 128 | * 129 | * @param string $var 130 | * @return array 131 | * @throws ParseException 132 | */ 133 | protected function decode($var) 134 | { 135 | // Try native PECL YAML PHP extension first if available. 136 | if (function_exists('yaml_parse') && $this->setting('native', true)) { 137 | // Safely decode YAML. 138 | $saved = @ini_get('yaml.decode_php'); 139 | @ini_set('yaml.decode_php', '0'); 140 | $data = @yaml_parse($var); 141 | if ($saved !== false) { 142 | @ini_set('yaml.decode_php', $saved); 143 | } 144 | 145 | if ($data !== false) { 146 | return (array)$data; 147 | } 148 | } 149 | 150 | try { 151 | return (array)YamlParser::parse($var); 152 | } catch (ParseException $e) { 153 | if ($this->setting('compat', true)) { 154 | return (array)FallbackYamlParser::parse($var); 155 | } 156 | 157 | throw $e; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 - 2020 RocketTheme, LCC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![](https://avatars1.githubusercontent.com/u/1310198?v=2&s=50) RocketTheme Toolbox 2 | 3 | [![PHPStan](https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat)](https://github.com/phpstan/phpstan) 4 | [![Latest Version](http://img.shields.io/packagist/v/rockettheme/toolbox.svg?style=flat)](https://packagist.org/packages/rockettheme/toolbox) 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE) 6 | [![Build Status](https://img.shields.io/travis/rockettheme/toolbox/master.svg?style=flat)](https://travis-ci.org/rockettheme/toolbox) 7 | [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/rockettheme/toolbox.svg?style=flat)](https://scrutinizer-ci.com/g/rockettheme/toolbox/code-structure) 8 | [![Quality Score](https://img.shields.io/scrutinizer/g/rockettheme/toolbox.svg?style=flat)](https://scrutinizer-ci.com/g/rockettheme/toolbox) 9 | [![Total Downloads](https://img.shields.io/packagist/dt/rockettheme/toolbox.svg?style=flat)](https://packagist.org/packages/rockettheme/toolbox) 10 | 11 | RocketTheme\Toolbox package contains a set of reusable PHP interfaces, classes and traits. 12 | 13 | * ArrayTraits 14 | * Blueprints 15 | * DI 16 | * Event 17 | * File 18 | * ResourceLocator 19 | * Session 20 | * StreamWrapper 21 | 22 | ## Installation 23 | 24 | You can use [Composer](http://getcomposer.org/) to download and install this package as well as its dependencies. 25 | 26 | ### Composer 27 | 28 | To add this package as a local, per-project dependency to your project, simply add a dependency on `rockettheme/toolbox` to your project's `composer.json` file. Here is a minimal example of a `composer.json` file that just defines a dependency on Diff: 29 | 30 | { 31 | "require": { 32 | "rockettheme/toolbox": "^1.5" 33 | } 34 | } 35 | 36 | 37 | # Contributing 38 | 39 | We appreciate any contribution to ToolBox, whether it is related to bugs or simply a suggestion or improvement. 40 | However, we ask that any contribution follow our simple guidelines in order to be properly received. 41 | 42 | All our projects follow the [GitFlow branching model][gitflow-model], from development to release. If you are not familiar with it, there are several guides and tutorials to make you understand what it is about. 43 | 44 | You will probably want to get started by installing [this very good collection of git extensions][gitflow-extensions]. 45 | 46 | What you mainly want to know is that: 47 | 48 | - All the main activity happens in the `develop` branch. Any pull request should be addressed only to that branch. We will not consider pull requests made to the `master`. 49 | - It's very well appreciated, and highly suggested, to start a new feature whenever you want to make changes or add functionalities. It will make it much easier for us to just checkout your feature branch and test it, before merging it into `develop` 50 | 51 | 52 | # Getting Started 53 | 54 | * Have fun!!! 55 | 56 | 57 | [gitflow-model]: http://nvie.com/posts/a-successful-git-branching-model/ 58 | [gitflow-extensions]: https://github.com/nvie/gitflow 59 | -------------------------------------------------------------------------------- /ResourceLocator/src/RecursiveUniformResourceIterator.php: -------------------------------------------------------------------------------- 1 | getSubPathName(); 26 | 27 | return (new static($this->getUrl(), $this->flags, $this->locator))->setSubPath($subPath); 28 | } 29 | 30 | /** 31 | * @param bool|null $allow_links 32 | * @return bool 33 | */ 34 | #[\ReturnTypeWillChange] 35 | public function hasChildren($allow_links = null) 36 | { 37 | $allow_links = (bool)($allow_links !== null ? $allow_links : $this->flags & \FilesystemIterator::FOLLOW_SYMLINKS); 38 | 39 | return $this->isDir() && !$this->isDot() && ($allow_links || !$this->isLink()); 40 | } 41 | 42 | /** 43 | * @return string|null 44 | */ 45 | public function getSubPath() 46 | { 47 | return $this->subPath; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getSubPathName() 54 | { 55 | return ($this->subPath ? $this->subPath . '/' : '') . $this->getFilename(); 56 | } 57 | 58 | /** 59 | * @param string $path 60 | * @return $this 61 | * @internal 62 | */ 63 | public function setSubPath($path) 64 | { 65 | $this->subPath = $path; 66 | 67 | return $this; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ResourceLocator/src/ResourceLocatorInterface.php: -------------------------------------------------------------------------------- 1 | getIterator() instead'); 41 | } 42 | if ($path === '') { 43 | throw new BadMethodCallException('Url cannot be empty'); 44 | } 45 | 46 | $this->path = $path; 47 | $this->flags = $flags; 48 | $this->locator = $locator; 49 | $this->rewind(); 50 | } 51 | 52 | /** 53 | * @return $this|\SplFileInfo|string 54 | */ 55 | #[\ReturnTypeWillChange] 56 | public function current() 57 | { 58 | if ($this->getFlags() & static::CURRENT_AS_SELF) { 59 | return $this; 60 | } 61 | 62 | return $this->iterator->current(); 63 | } 64 | 65 | /** 66 | * @return string 67 | */ 68 | #[\ReturnTypeWillChange] 69 | public function key() 70 | { 71 | return $this->iterator->key(); 72 | } 73 | 74 | /** 75 | * @return void 76 | */ 77 | #[\ReturnTypeWillChange] 78 | public function next() 79 | { 80 | do { 81 | $found = $this->findNext(); 82 | } while (null !== $found && !empty($this->found[$found])); 83 | 84 | if (null !== $found) { 85 | // Mark the file as found. 86 | $this->found[$found] = true; 87 | } 88 | } 89 | 90 | /** 91 | * @return bool 92 | */ 93 | #[\ReturnTypeWillChange] 94 | public function valid() 95 | { 96 | return $this->iterator->valid(); 97 | } 98 | 99 | /** 100 | * @return void 101 | */ 102 | #[\ReturnTypeWillChange] 103 | public function rewind() 104 | { 105 | $this->found = []; 106 | $this->stack = $this->locator->findResources($this->path); 107 | 108 | if (!$this->nextIterator()) { 109 | throw new BadMethodCallException('Failed to open dir: ' . $this->path . ' does not exist.'); 110 | } 111 | 112 | // Find the first valid entry. 113 | while (!$this->valid()) { 114 | if ($this->nextIterator() === false) { 115 | return; 116 | } 117 | } 118 | 119 | // Mark the first file as found. 120 | $this->found[$this->getFilename()] = true; 121 | } 122 | 123 | /** 124 | * @return string 125 | */ 126 | public function getUrl() 127 | { 128 | $path = $this->path . ($this->path[strlen($this->path) - 1] === '/' ? '' : '/'); 129 | 130 | return $path . $this->iterator->getFilename(); 131 | } 132 | 133 | /** 134 | * @param int $position 135 | * @return void 136 | */ 137 | #[\ReturnTypeWillChange] 138 | public function seek($position) 139 | { 140 | throw new RuntimeException('Seek not implemented'); 141 | } 142 | 143 | /** 144 | * @return int 145 | */ 146 | #[\ReturnTypeWillChange] 147 | public function getATime() 148 | { 149 | return $this->iterator->getATime(); 150 | } 151 | 152 | /** 153 | * @param string|null $suffix 154 | * @return string 155 | */ 156 | #[\ReturnTypeWillChange] 157 | public function getBasename($suffix = null) 158 | { 159 | return null !== $suffix ? $this->iterator->getBasename($suffix) : $this->iterator->getBasename(); 160 | } 161 | 162 | /** 163 | * @return int 164 | */ 165 | #[\ReturnTypeWillChange] 166 | public function getCTime() 167 | { 168 | return $this->iterator->getCTime(); 169 | } 170 | 171 | /** 172 | * @return string 173 | */ 174 | #[\ReturnTypeWillChange] 175 | public function getExtension() 176 | { 177 | return $this->iterator->getExtension(); 178 | } 179 | 180 | /** 181 | * @return string 182 | */ 183 | #[\ReturnTypeWillChange] 184 | public function getFilename() 185 | { 186 | return $this->iterator->getFilename(); 187 | } 188 | 189 | /** 190 | * @return int 191 | */ 192 | #[\ReturnTypeWillChange] 193 | public function getGroup() 194 | { 195 | return $this->iterator->getGroup(); 196 | } 197 | 198 | /** 199 | * @return int 200 | */ 201 | #[\ReturnTypeWillChange] 202 | public function getInode() 203 | { 204 | return $this->iterator->getInode(); 205 | } 206 | 207 | /** 208 | * @return int 209 | */ 210 | #[\ReturnTypeWillChange] 211 | public function getMTime() 212 | { 213 | return $this->iterator->getMTime(); 214 | } 215 | 216 | /** 217 | * @return int 218 | */ 219 | #[\ReturnTypeWillChange] 220 | public function getOwner() 221 | { 222 | return $this->iterator->getOwner(); 223 | } 224 | 225 | /** 226 | * @return string 227 | */ 228 | #[\ReturnTypeWillChange] 229 | public function getPath() 230 | { 231 | return $this->iterator->getPath(); 232 | } 233 | 234 | /** 235 | * @return string 236 | */ 237 | #[\ReturnTypeWillChange] 238 | public function getPathname() 239 | { 240 | return $this->iterator->getPathname(); 241 | } 242 | 243 | /** 244 | * @return int 245 | */ 246 | #[\ReturnTypeWillChange] 247 | public function getPerms() 248 | { 249 | return $this->iterator->getPerms(); 250 | } 251 | 252 | /** 253 | * @return int 254 | */ 255 | #[\ReturnTypeWillChange] 256 | public function getSize() 257 | { 258 | return $this->iterator->getSize(); 259 | } 260 | 261 | /** 262 | * @return string 263 | */ 264 | #[\ReturnTypeWillChange] 265 | public function getType() 266 | { 267 | return $this->iterator->getType(); 268 | } 269 | 270 | /** 271 | * @return bool 272 | */ 273 | #[\ReturnTypeWillChange] 274 | public function isDir() 275 | { 276 | return $this->iterator->isDir(); 277 | } 278 | 279 | /** 280 | * @return bool 281 | */ 282 | #[\ReturnTypeWillChange] 283 | public function isDot() 284 | { 285 | return $this->iterator->isDot(); 286 | } 287 | 288 | /** 289 | * @return bool 290 | */ 291 | #[\ReturnTypeWillChange] 292 | public function isExecutable() 293 | { 294 | return $this->iterator->isExecutable(); 295 | } 296 | 297 | /** 298 | * @return bool 299 | */ 300 | #[\ReturnTypeWillChange] 301 | public function isFile() 302 | { 303 | return $this->iterator->isFile(); 304 | } 305 | 306 | /** 307 | * @return bool 308 | */ 309 | #[\ReturnTypeWillChange] 310 | public function isLink() 311 | { 312 | return $this->iterator->isLink(); 313 | } 314 | 315 | /** 316 | * @return bool 317 | */ 318 | #[\ReturnTypeWillChange] 319 | public function isReadable() 320 | { 321 | return $this->iterator->isReadable(); 322 | } 323 | 324 | /** 325 | * @return bool 326 | */ 327 | #[\ReturnTypeWillChange] 328 | public function isWritable() 329 | { 330 | return $this->iterator->isWritable(); 331 | } 332 | 333 | /** 334 | * @return string 335 | */ 336 | public function __toString() 337 | { 338 | return (string)$this->iterator; 339 | } 340 | 341 | /** 342 | * @return int 343 | */ 344 | #[\ReturnTypeWillChange] 345 | public function getFlags() 346 | { 347 | return $this->flags !== null ? $this->flags : static::KEY_AS_PATHNAME | static::CURRENT_AS_SELF | static::SKIP_DOTS; 348 | } 349 | 350 | /** 351 | * @param int|null $flags 352 | * @return void 353 | */ 354 | #[\ReturnTypeWillChange] 355 | public function setFlags($flags = null) 356 | { 357 | $this->flags = $flags; 358 | 359 | $this->iterator->setFlags($this->getFlags()); 360 | } 361 | 362 | /** 363 | * @return string|null 364 | */ 365 | protected function findNext() 366 | { 367 | $this->iterator->next(); 368 | 369 | while (!$this->valid()) { 370 | if ($this->nextIterator() === false) { 371 | return null; 372 | } 373 | } 374 | 375 | return $this->getFilename(); 376 | } 377 | 378 | /** 379 | * @return bool 380 | * @phpstan-impure 381 | */ 382 | protected function nextIterator() 383 | { 384 | // Move to the next iterator if it exists. 385 | $path = array_shift($this->stack); 386 | $hasNext = null !== $path; 387 | if ($hasNext) { 388 | $this->iterator = new FilesystemIterator($path, $this->getFlags()); 389 | } 390 | 391 | return $hasNext; 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /ResourceLocator/src/UniformResourceLocator.php: -------------------------------------------------------------------------------- 1 | base = rtrim(str_replace('\\', '/', $base ?: getcwd() ?: ''), '/'); 43 | } 44 | 45 | /** 46 | * Return iterator for the resource URI. 47 | * 48 | * @param string $uri 49 | * @param int|null $flags See constants from FilesystemIterator class. 50 | * @return UniformResourceIterator 51 | */ 52 | public function getIterator($uri, $flags = null) 53 | { 54 | return new UniformResourceIterator($uri, $flags, $this); 55 | } 56 | 57 | /** 58 | * Return recursive iterator for the resource URI. 59 | * 60 | * @param string $uri 61 | * @param int|null $flags See constants from FilesystemIterator class. 62 | * @return RecursiveUniformResourceIterator 63 | */ 64 | public function getRecursiveIterator($uri, $flags = null) 65 | { 66 | return new RecursiveUniformResourceIterator($uri, $flags, $this); 67 | } 68 | 69 | /** 70 | * Reset locator by removing all the schemes. 71 | * 72 | * @return $this 73 | */ 74 | public function reset() 75 | { 76 | $this->schemes = []; 77 | $this->cache = []; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Reset a locator scheme 84 | * 85 | * @param string $scheme The scheme to reset 86 | * @return $this 87 | */ 88 | public function resetScheme($scheme) 89 | { 90 | $this->schemes[$scheme] = []; 91 | $this->cache = []; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Add new paths to the scheme. 98 | * 99 | * @param string $scheme 100 | * @param string $prefix 101 | * @param string|array $paths 102 | * @param bool|string|string[] $override True to add path as override, string 103 | * @param bool $force True to add paths even if them do not exist. 104 | * @return void 105 | * @throws BadMethodCallException 106 | */ 107 | public function addPath($scheme, $prefix, $paths, $override = false, $force = false) 108 | { 109 | $list = []; 110 | 111 | /** @var array $paths */ 112 | $paths = (array)$paths; 113 | foreach ($paths as $path) { 114 | if (is_array($path)) { 115 | // Support stream lookup in ['theme', 'path/to'] format. 116 | if (count($path) !== 2 || !is_string($path[0]) || !is_string($path[1])) { 117 | throw new BadMethodCallException('Invalid stream path given.'); 118 | } 119 | $list[] = $path; 120 | } elseif (false !== strpos($path, '://')) { 121 | // Support stream lookup in 'theme://path/to' format. 122 | $stream = explode('://', $path, 2); 123 | $stream[1] = trim($stream[1], '/'); 124 | 125 | $list[] = $stream; 126 | } else { 127 | // Normalize path. 128 | $path = rtrim(str_replace('\\', '/', $path), '/'); 129 | if ($force || @file_exists("{$this->base}/{$path}") || @file_exists($path)) { 130 | // Support for absolute and relative paths. 131 | $list[] = $path; 132 | } 133 | } 134 | } 135 | 136 | if (isset($this->schemes[$scheme][$prefix])) { 137 | $paths = $this->schemes[$scheme][$prefix]; 138 | if (!$override || $override == 1) { 139 | $list = $override ? array_merge($paths, $list) : array_merge($list, $paths); 140 | } else { 141 | if (is_string($override) && false !== strpos($override, '://')) { 142 | // Support stream lookup in 'theme://path/to' format. 143 | $override = explode('://', $override, 2); 144 | $override[1] = trim($override[1], '/'); 145 | } 146 | $location = (int)array_search($override, $paths, true) ?: count($paths); 147 | array_splice($paths, $location, 0, $list); 148 | $list = $paths; 149 | } 150 | } 151 | 152 | $this->schemes[$scheme][$prefix] = $list; 153 | 154 | // Sort in reverse order to get longer prefixes to be matched first. 155 | krsort($this->schemes[$scheme]); 156 | 157 | $this->cache = []; 158 | } 159 | 160 | /** 161 | * Return base directory. 162 | * 163 | * @return string 164 | */ 165 | public function getBase() 166 | { 167 | return $this->base; 168 | } 169 | 170 | 171 | /** 172 | * Return true if scheme has been defined. 173 | * 174 | * @param string $name 175 | * @return bool 176 | */ 177 | public function schemeExists($name) 178 | { 179 | return isset($this->schemes[$name]); 180 | } 181 | 182 | /** 183 | * Return defined schemes. 184 | * 185 | * @return array 186 | */ 187 | public function getSchemes() 188 | { 189 | return array_keys($this->schemes); 190 | } 191 | 192 | /** 193 | * Return all scheme lookup paths. 194 | * 195 | * @param string $scheme 196 | * @return array 197 | */ 198 | public function getPaths($scheme = null) 199 | { 200 | if (null !== $scheme) { 201 | return isset($this->schemes[$scheme]) ? $this->schemes[$scheme] : []; 202 | } 203 | 204 | return $this->schemes; 205 | } 206 | 207 | /** 208 | * @param string $uri 209 | * @return string|false 210 | * @throws BadMethodCallException 211 | */ 212 | public function __invoke($uri) 213 | { 214 | if (!is_string($uri)) { 215 | throw new BadMethodCallException('Invalid parameter $uri.'); 216 | } 217 | 218 | /** @var string|false $cached */ 219 | $cached = $this->findCached($uri, false, true, false); 220 | 221 | return $cached; 222 | } 223 | 224 | /** 225 | * Returns true if uri is resolvable by using locator. 226 | * 227 | * @param string $uri 228 | * @return bool 229 | */ 230 | public function isStream($uri) 231 | { 232 | try { 233 | $normalized = $this->normalize($uri, true, true); 234 | \assert(is_array($normalized)); 235 | 236 | list ($scheme,) = $normalized; 237 | if (!is_string($scheme)) { 238 | return false; 239 | } 240 | } catch (Exception $e) { 241 | return false; 242 | } 243 | 244 | return $this->schemeExists($scheme); 245 | } 246 | 247 | /** 248 | * Returns the canonicalized URI on success. The resulting path will have no '//', '/./' or '/../' components. 249 | * Trailing delimiter `/` is kept. 250 | * 251 | * If URI is a local file, method always returns absolute path to the file. 252 | * 253 | * By default (if $throwException parameter is not set to true) returns false on failure. 254 | * 255 | * @param string $uri 256 | * @param bool $throwException 257 | * @param bool $splitStream 258 | * @return string[]|string|false 259 | * @throws BadMethodCallException 260 | */ 261 | public function normalize($uri, $throwException = false, $splitStream = false) 262 | { 263 | if (!is_string($uri)) { 264 | if ($throwException) { 265 | throw new BadMethodCallException('Invalid parameter $uri.'); 266 | } 267 | 268 | return false; 269 | } 270 | 271 | $uri = (string)preg_replace('|\\\\|u', '/', $uri); 272 | $segments = explode('://', $uri, 2); 273 | $path = array_pop($segments); 274 | $scheme = array_pop($segments) ?: 'file'; 275 | 276 | // Make all file scheme paths absolute. 277 | if ($scheme === 'file') { 278 | if ('' === $path) { 279 | // Empty path. 280 | $path = $this->base; 281 | } elseif (preg_match('`^(/|([a-z]:/))`ui', $uri) !== 1) { 282 | // Relative path. 283 | $path = "{$this->base}/$path"; 284 | } 285 | } 286 | 287 | // Clean path from '..', '.' and ''. 288 | if ('' !== $path) { 289 | $parts = explode('/', $path); 290 | 291 | $list = []; 292 | foreach ($parts as $i => $part) { 293 | if ($part === '..') { 294 | $part = array_pop($list); 295 | if ($part === null || $part === '' || (!$list && strpos($part, ':'))) { 296 | if ($throwException) { 297 | throw new BadMethodCallException('Invalid parameter $uri.'); 298 | } 299 | 300 | return false; 301 | } 302 | } elseif (($i && $part === '') || $part === '.') { 303 | continue; 304 | } else { 305 | $list[] = $part; 306 | } 307 | } 308 | 309 | if (($l = end($parts)) === '' || $l === '.' || $l === '..') { 310 | $list[] = ''; 311 | } 312 | 313 | $path = implode('/', $list); 314 | } 315 | 316 | if ($splitStream) { 317 | return [$scheme, $path]; 318 | } 319 | 320 | return $scheme !== 'file' ? "{$scheme}://{$path}" : $path; 321 | } 322 | 323 | /** 324 | * Get resource path. If resource does not exist, return path with highest priority. 325 | * 326 | * @param string $uri Input URI to be searched. 327 | * @param bool $absolute Whether to return absolute path. 328 | * @return string 329 | * @throws BadMethodCallException 330 | */ 331 | public function getResource($uri, $absolute = true) 332 | { 333 | $path = $this->findResource($uri, $absolute); 334 | if ($path === false) { 335 | $path = $this->findResource($uri, $absolute, true); 336 | if ($path === false) { 337 | $path = ''; 338 | } 339 | } 340 | 341 | return $path; 342 | } 343 | 344 | /** 345 | * Find highest priority instance from a resource. 346 | * 347 | * @param string $uri Input URI to be searched. 348 | * @param bool $absolute Whether to return absolute path. 349 | * @param bool $first Whether to return first path even if it doesn't exist. 350 | * @return string|false 351 | * @throws BadMethodCallException 352 | */ 353 | public function findResource($uri, $absolute = true, $first = false) 354 | { 355 | if (!is_string($uri)) { 356 | throw new BadMethodCallException('Invalid parameter $uri.'); 357 | } 358 | 359 | /** @var string|false $cached */ 360 | $cached = $this->findCached($uri, false, $absolute, $first); 361 | 362 | return $cached; 363 | } 364 | 365 | /** 366 | * Find all instances from a resource. 367 | * 368 | * @param string $uri Input URI to be searched. 369 | * @param bool $absolute Whether to return absolute path. 370 | * @param bool $all Whether to return all paths even if they don't exist. 371 | * @return array 372 | * @throws BadMethodCallException 373 | */ 374 | public function findResources($uri, $absolute = true, $all = false) 375 | { 376 | if (!is_string($uri)) { 377 | throw new BadMethodCallException('Invalid parameter $uri.'); 378 | } 379 | 380 | /** @var array $cached */ 381 | $cached = $this->findCached($uri, true, $absolute, $all); 382 | 383 | return $cached; 384 | } 385 | 386 | /** 387 | * Find all instances from a list of resources. 388 | * 389 | * @param array $uris Input URIs to be searched. 390 | * @param bool $absolute Whether to return absolute path. 391 | * @param bool $all Whether to return all paths even if they don't exist. 392 | * @return array 393 | * @throws BadMethodCallException 394 | */ 395 | public function mergeResources(array $uris, $absolute = true, $all = false) 396 | { 397 | $uris = array_unique($uris); 398 | 399 | $lists = [[]]; 400 | foreach ($uris as $uri) { 401 | $lists[] = $this->findResources($uri, $absolute, $all); 402 | } 403 | 404 | return array_merge(...$lists); 405 | } 406 | 407 | /** 408 | * Pre-fill cache by a stream. 409 | * 410 | * @param string $uri 411 | * @return $this 412 | */ 413 | public function fillCache($uri) 414 | { 415 | $cacheKey = $uri . '@cache'; 416 | 417 | if (!isset($this->cache[$cacheKey])) { 418 | $this->cache[$cacheKey] = true; 419 | 420 | $iterator = new RecursiveIteratorIterator($this->getRecursiveIterator($uri), RecursiveIteratorIterator::SELF_FIRST); 421 | 422 | /** @var UniformResourceIterator $item */ 423 | foreach ($iterator as $item) { 424 | $key = $item->getUrl() . '@010'; 425 | $this->cache[$key] = $item->getPathname(); 426 | } 427 | } 428 | 429 | return $this; 430 | } 431 | 432 | /** 433 | * Reset locator cache. 434 | * 435 | * @param string $uri 436 | * @return $this 437 | */ 438 | public function clearCache($uri = null) 439 | { 440 | if ($uri) { 441 | $this->clearCached($uri, true, true, true); 442 | $this->clearCached($uri, true, true, false); 443 | $this->clearCached($uri, true, false, true); 444 | $this->clearCached($uri, true, false, false); 445 | $this->clearCached($uri, false, true, true); 446 | $this->clearCached($uri, false, true, false); 447 | $this->clearCached($uri, false, false, true); 448 | $this->clearCached($uri, false, false, false); 449 | } else { 450 | $this->cache = []; 451 | } 452 | 453 | return $this; 454 | } 455 | 456 | /** 457 | * @param string $uri 458 | * @param bool $array 459 | * @param bool $absolute 460 | * @param bool $all 461 | * @return array|string|false 462 | * @throws BadMethodCallException 463 | */ 464 | protected function findCached($uri, $array, $absolute, $all) 465 | { 466 | // Local caching: make sure that the function gets only called at once for each file. 467 | $key = $uri .'@'. (int) $array . (int) $absolute . (int) $all; 468 | 469 | if (!isset($this->cache[$key])) { 470 | try { 471 | $normalized = $this->normalize($uri, true, true); 472 | \assert(is_array($normalized)); 473 | 474 | list ($scheme, $file) = $normalized; 475 | 476 | if ($scheme === 'file') { 477 | // File stream is a special case. 478 | if (!$absolute) { 479 | // Make uri relative. 480 | if ($uri === $this->base) { 481 | $file = '/'; 482 | } elseif (strpos($uri, $this->base . '/') === 0) { 483 | $file = substr($uri, strlen($this->base)); 484 | } else { 485 | throw new RuntimeException("UniformResourceLocator: Absolute file path with relative lookup not allowed", 500); 486 | } 487 | } 488 | 489 | if (!$all && !file_exists($file)) { 490 | $this->cache[$key] = $array ? [] : false; 491 | } else { 492 | $this->cache[$key] = $array ? [$file] : $file; 493 | } 494 | } else { 495 | // Locate files in resource locator streams. 496 | $this->cache[$key] = $this->find($scheme, $file, $array, $absolute, $all); 497 | } 498 | 499 | } catch (BadMethodCallException $e) { 500 | $this->cache[$key] = $array ? [] : false; 501 | } 502 | } 503 | 504 | return $this->cache[$key]; 505 | } 506 | 507 | /** 508 | * @param string $uri 509 | * @param bool $array 510 | * @param bool $absolute 511 | * @param bool $all 512 | * @return void 513 | */ 514 | protected function clearCached($uri, $array, $absolute, $all) 515 | { 516 | // Local caching: make sure that the function gets only called at once for each file. 517 | $key = $uri .'@'. (int) $array . (int) $absolute . (int) $all; 518 | 519 | unset($this->cache[$key]); 520 | } 521 | 522 | /** 523 | * @param string $scheme 524 | * @param string $file 525 | * @param bool $array 526 | * @param bool $absolute 527 | * @param bool $all 528 | * @return array|string|false 529 | * @throws InvalidArgumentException 530 | * @internal 531 | */ 532 | protected function find($scheme, $file, $array, $absolute, $all) 533 | { 534 | if (!isset($this->schemes[$scheme])) { 535 | throw new InvalidArgumentException("Invalid resource {$scheme}://"); 536 | } 537 | 538 | $results = $array ? [] : false; 539 | /** 540 | * @var string $prefix 541 | * @var array $paths 542 | */ 543 | foreach ($this->schemes[$scheme] as $prefix => $paths) { 544 | if ($prefix && strpos($file, $prefix) !== 0) { 545 | continue; 546 | } 547 | 548 | // Remove prefix from filename. 549 | $filename = '/' . trim(substr($file, \strlen($prefix)), '\/'); 550 | 551 | foreach ($paths as $path) { 552 | if (is_array($path)) { 553 | // Handle scheme lookup. 554 | $relPath = trim($path[1] . $filename, '/'); 555 | $found = $this->find($path[0], $relPath, $array, $absolute, $all); 556 | if ($found) { 557 | if (!is_array($found)) { 558 | return $found; 559 | } 560 | $results = array_merge($results, $found); 561 | } 562 | } else { 563 | // TODO: We could provide some extra information about the path to remove preg_match(). 564 | // Check absolute paths for both unix and windows 565 | if (!$path || !preg_match('`^/|\w+:`', $path)) { 566 | // Handle relative path lookup. 567 | $relPath = trim($path . $filename, '/'); 568 | $fullPath = $this->base . '/' . $relPath; 569 | } else { 570 | // Handle absolute path lookup. 571 | $relPath = null; 572 | $fullPath = rtrim($path . $filename, '/'); 573 | } 574 | 575 | if ($all || file_exists($fullPath)) { 576 | if ($absolute) { 577 | $current = $fullPath; 578 | } elseif (null === $relPath) { 579 | throw new RuntimeException("UniformResourceLocator: Absolute stream path with relative lookup not allowed ({$prefix})", 500); 580 | } else { 581 | $current = $relPath; 582 | } 583 | 584 | if (!$array) { 585 | return $current; 586 | } 587 | 588 | $results[] = $current; 589 | } 590 | } 591 | } 592 | } 593 | 594 | return $results; 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /ResourceLocator/tests/UniformResourceLocatorTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(__DIR__ . '/data', self::$locator->getBase()); 24 | } 25 | 26 | /** 27 | * @param $scheme 28 | * @param $path 29 | * @param $lookup 30 | * 31 | * @dataProvider addPathProvider 32 | */ 33 | public function testAddPath($scheme, $path, $lookup) 34 | { 35 | $locator = self::$locator; 36 | 37 | $this->assertFalse($locator->schemeExists($scheme)); 38 | 39 | $locator->addPath($scheme, $path, $lookup); 40 | 41 | $this->assertTrue($locator->schemeExists($scheme)); 42 | } 43 | 44 | public function addPathProvider() { 45 | return [ 46 | ['base', '', 'base'], 47 | ['local', '', 'local'], 48 | ['override', '', 'override'], 49 | ['all', '', ['override://all', 'local://all', 'base://all']], 50 | ]; 51 | } 52 | 53 | /** 54 | * @depends testAddPath 55 | */ 56 | public function testGetSchemes() 57 | { 58 | $this->assertEquals( 59 | ['base', 'local', 'override', 'all'], 60 | self::$locator->getSchemes() 61 | ); 62 | } 63 | 64 | /** 65 | * @depends testAddPath 66 | * @dataProvider getPathsProvider 67 | */ 68 | public function testGetPaths($scheme, $expected) 69 | { 70 | $locator = self::$locator; 71 | 72 | $this->assertEquals($expected, $locator->getPaths($scheme)); 73 | } 74 | 75 | 76 | public function getPathsProvider() { 77 | return [ 78 | ['base', ['' => ['base']]], 79 | ['local', ['' => ['local']]], 80 | ['override', ['' => ['override']]], 81 | ['all', ['' => [['override', 'all'], ['local', 'all'], ['base', 'all']]]], 82 | ['fail', []] 83 | ]; 84 | } 85 | 86 | /** 87 | * @depends testAddPath 88 | */ 89 | public function testSchemeExists() 90 | { 91 | $locator = self::$locator; 92 | 93 | // Partially tested in addPath() tests. 94 | $this->assertFalse($locator->schemeExists('foo')); 95 | $this->assertFalse($locator->schemeExists('file')); 96 | } 97 | 98 | /** 99 | * @depends testAddPath 100 | * @expectedException InvalidArgumentException 101 | */ 102 | public function testGetIterator() 103 | { 104 | $locator = self::$locator; 105 | 106 | $this->assertInstanceOf( 107 | UniformResourceIterator::class, 108 | $locator->getIterator('all://') 109 | ); 110 | 111 | $locator->getIterator('fail://'); 112 | } 113 | 114 | /** 115 | * @depends testAddPath 116 | * @expectedException InvalidArgumentException 117 | */ 118 | public function testGetRecursiveIterator() 119 | { 120 | $locator = self::$locator; 121 | 122 | $this->assertInstanceOf( 123 | RecursiveUniformResourceIterator::class, 124 | $locator->getRecursiveIterator('all://') 125 | ); 126 | 127 | $locator->getRecursiveIterator('fail://'); 128 | } 129 | 130 | /** 131 | * @depends testAddPath 132 | */ 133 | public function testIsStream($uri) 134 | { 135 | $locator = self::$locator; 136 | 137 | // Existing file. 138 | $this->assertEquals(true, $locator->isStream('all://base.txt')); 139 | // Non-existing file. 140 | $this->assertEquals(true, $locator->isStream('all://bar.txt')); 141 | // Unknown uri type. 142 | $this->assertEquals(false, $locator->isStream('fail://base.txt')); 143 | // Bad uri. 144 | $this->assertEquals(false, $locator->isStream('fail://../base.txt')); 145 | } 146 | 147 | /** 148 | * @dataProvider normalizeProvider 149 | */ 150 | public function testNormalize($uri, $path) 151 | { 152 | $locator = self::$locator; 153 | 154 | $this->assertEquals($path, $locator->normalize($uri)); 155 | } 156 | 157 | /** 158 | * @depends testAddPath 159 | * @dataProvider findResourcesProvider 160 | */ 161 | public function testFindResource($uri, $paths) 162 | { 163 | $locator = self::$locator; 164 | $path = $paths ? reset($paths) : false; 165 | $fullPath = !$path ? false : __DIR__ . "/data/{$path}"; 166 | 167 | $this->assertEquals($fullPath, $locator->findResource($uri)); 168 | $this->assertEquals($path, $locator->findResource($uri, false)); 169 | } 170 | 171 | /** 172 | * @depends testAddPath 173 | * @dataProvider findResourcesProvider 174 | */ 175 | public function testFindResources($uri, $paths) 176 | { 177 | $locator = self::$locator; 178 | 179 | $this->assertEquals($paths, $locator->findResources($uri, false)); 180 | } 181 | 182 | /** 183 | * @depends testFindResource 184 | * @dataProvider findResourcesProvider 185 | */ 186 | public function testInvoke($uri, $paths) 187 | { 188 | $locator = self::$locator; 189 | $path = $paths ? reset($paths) : false; 190 | $fullPath = !$path ? false : __DIR__ . "/data/{$path}"; 191 | 192 | $this->assertEquals($fullPath, $locator($uri)); 193 | } 194 | 195 | 196 | public function normalizeProvider() { 197 | return [ 198 | ['', ''], 199 | ['./', ''], 200 | ['././/./', ''], 201 | ['././/../', false], 202 | ['/', '/'], 203 | ['//', '/'], 204 | ['///', '/'], 205 | ['/././', '/'], 206 | ['foo', 'foo'], 207 | ['/foo', '/foo'], 208 | ['//foo', '/foo'], 209 | ['/foo/', '/foo/'], 210 | ['//foo//', '/foo/'], 211 | ['path/to/file.txt', 'path/to/file.txt'], 212 | ['path/to/../file.txt', 'path/file.txt'], 213 | ['path/to/../../file.txt', 'file.txt'], 214 | ['path/to/../../../file.txt', false], 215 | ['/path/to/file.txt', '/path/to/file.txt'], 216 | ['/path/to/../file.txt', '/path/file.txt'], 217 | ['/path/to/../../file.txt', '/file.txt'], 218 | ['/path/to/../../../file.txt', false], 219 | ['c:\\', 'c:/'], 220 | ['c:\\path\\to\file.txt', 'c:/path/to/file.txt'], 221 | ['c:\\path\\to\../file.txt', 'c:/path/file.txt'], 222 | ['c:\\path\\to\../../file.txt', 'c:/file.txt'], 223 | ['c:\\path\\to\../../../file.txt', false], 224 | ['stream://path/to/file.txt', 'stream://path/to/file.txt'], 225 | ['stream://path/to/../file.txt', 'stream://path/file.txt'], 226 | ['stream://path/to/../../file.txt', 'stream://file.txt'], 227 | ['stream://path/to/../../../file.txt', false], 228 | 229 | ]; 230 | } 231 | public function findResourcesProvider() { 232 | return [ 233 | ['all://base.txt', ['base/all/base.txt']], 234 | ['all://base_all.txt', ['override/all/base_all.txt', 'local/all/base_all.txt', 'base/all/base_all.txt']], 235 | ['all://base_local.txt', ['local/all/base_local.txt', 'base/all/base_local.txt']], 236 | ['all://base_override.txt', ['override/all/base_override.txt', 'base/all/base_override.txt']], 237 | ['all://local.txt', ['local/all/local.txt']], 238 | ['all://local_override.txt', ['override/all/local_override.txt', 'local/all/local_override.txt']], 239 | ['all://override.txt', ['override/all/override.txt']], 240 | ['all://missing.txt', []], 241 | ['all://asdf/../base.txt', ['base/all/base.txt']], 242 | ]; 243 | } 244 | 245 | /* * 246 | * @depends testAddPath 247 | * / 248 | public function testMergeResources() 249 | { 250 | $locator = self::$locator; 251 | } 252 | 253 | public function testReset() 254 | { 255 | $locator = self::$locator; 256 | } 257 | 258 | public function testResetScheme() 259 | { 260 | $locator = self::$locator; 261 | } 262 | */ 263 | } 264 | -------------------------------------------------------------------------------- /ResourceLocator/tests/data/base/all/base.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/base/all/base.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/base/all/base_all.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/base/all/base_all.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/base/all/base_local.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/base/all/base_local.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/base/all/base_override.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/base/all/base_override.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/local/all/base_all.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/local/all/base_all.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/local/all/base_local.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/local/all/base_local.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/local/all/local.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/local/all/local.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/local/all/local_override.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/local/all/local_override.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/override/all/base_all.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/override/all/base_all.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/override/all/base_override.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/override/all/base_override.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/override/all/local_override.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/override/all/local_override.txt -------------------------------------------------------------------------------- /ResourceLocator/tests/data/override/all/override.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockettheme/toolbox/74b3c55d6883cf5fed7432e85e2a81a9f61ed4cd/ResourceLocator/tests/data/override/all/override.txt -------------------------------------------------------------------------------- /Session/src/Message.php: -------------------------------------------------------------------------------- 1 | $message, 'scope' => $scope]; 28 | 29 | // don't add duplicates 30 | if (!array_key_exists($key, $this->messages)) { 31 | $this->messages[$key] = $item; 32 | } 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * Clear message queue. 39 | * 40 | * @param string|null $scope 41 | * @return $this 42 | */ 43 | public function clear($scope = null) 44 | { 45 | if ($scope === null) { 46 | $this->messages = []; 47 | } else { 48 | foreach ($this->messages as $key => $message) { 49 | if ($message['scope'] === $scope) { 50 | unset($this->messages[$key]); 51 | } 52 | } 53 | } 54 | return $this; 55 | } 56 | 57 | /** 58 | * Fetch all messages. 59 | * 60 | * @param string|null $scope 61 | * @return array 62 | */ 63 | public function all($scope = null) 64 | { 65 | if ($scope === null) { 66 | return array_values($this->messages); 67 | } 68 | 69 | $messages = []; 70 | foreach ($this->messages as $message) { 71 | if ($message['scope'] === $scope) { 72 | $messages[] = $message; 73 | } 74 | } 75 | 76 | return $messages; 77 | } 78 | 79 | /** 80 | * Fetch and clear message queue. 81 | * 82 | * @param string|null $scope 83 | * @return array 84 | */ 85 | public function fetch($scope = null) 86 | { 87 | $messages = $this->all($scope); 88 | $this->clear($scope); 89 | 90 | return $messages; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /Session/src/Session.php: -------------------------------------------------------------------------------- 1 | isSessionStarted()) { 40 | session_unset(); 41 | session_destroy(); 42 | } 43 | 44 | // Disable transparent sid support 45 | ini_set('session.use_trans_sid', '0'); 46 | 47 | // Only allow cookies 48 | ini_set('session.use_cookies', '1'); 49 | 50 | session_name('msF9kJcW'); 51 | session_set_cookie_params($lifetime, $path, $domain); 52 | 53 | /** @var callable(): void $callable */ 54 | $callable = [$this, 'close']; 55 | 56 | register_shutdown_function($callable); 57 | session_cache_limiter('nocache'); 58 | 59 | self::$instance = $this; 60 | } 61 | 62 | /** 63 | * Get current session instance. 64 | * 65 | * @return Session 66 | * @throws RuntimeException 67 | */ 68 | public function instance() 69 | { 70 | if (null === self::$instance) { 71 | throw new RuntimeException("Session hasn't been initialized.", 500); 72 | } 73 | 74 | return self::$instance; 75 | } 76 | 77 | /** 78 | * Starts the session storage 79 | * 80 | * @return $this 81 | * @throws RuntimeException 82 | */ 83 | public function start() 84 | { 85 | // Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836 86 | if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) { 87 | unset($_COOKIE[session_name()]); 88 | } 89 | 90 | if (!session_start()) { 91 | throw new RuntimeException('Failed to start session.', 500); 92 | } 93 | 94 | $this->started = true; 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * Get session ID 101 | * 102 | * @return string|null Session ID 103 | */ 104 | public function getId() 105 | { 106 | return session_id() ?: null; 107 | } 108 | 109 | /** 110 | * Set session Id 111 | * 112 | * @param string $id Session ID 113 | * @return $this 114 | */ 115 | public function setId($id) 116 | { 117 | session_id($id); 118 | 119 | return $this; 120 | } 121 | 122 | 123 | /** 124 | * Get session name 125 | * 126 | * @return string|null 127 | */ 128 | public function getName() 129 | { 130 | return session_name() ?: null; 131 | } 132 | 133 | /** 134 | * Set session name 135 | * 136 | * @param string $name 137 | * @return $this 138 | */ 139 | public function setName($name) 140 | { 141 | session_name($name); 142 | 143 | return $this; 144 | } 145 | 146 | /** 147 | * Invalidates the current session. 148 | * 149 | * @return $this 150 | */ 151 | public function invalidate() 152 | { 153 | $name = $this->getName(); 154 | if (null !== $name) { 155 | $params = session_get_cookie_params(); 156 | setcookie($name, '', time() - 42000, 157 | $params['path'], $params['domain'], 158 | $params['secure'], $params['httponly'] 159 | ); 160 | } 161 | 162 | session_unset(); 163 | session_destroy(); 164 | 165 | $this->started = false; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Force the session to be saved and closed 172 | * 173 | * @return $this 174 | */ 175 | public function close() 176 | { 177 | if ($this->started) { 178 | session_write_close(); 179 | } 180 | 181 | $this->started = false; 182 | 183 | return $this; 184 | } 185 | 186 | /** 187 | * Checks if an attribute is defined. 188 | * 189 | * @param string $name The attribute name 190 | * @return bool True if the attribute is defined, false otherwise 191 | */ 192 | #[\ReturnTypeWillChange] 193 | public function __isset($name) 194 | { 195 | return isset($_SESSION[$name]); 196 | } 197 | 198 | /** 199 | * Returns an attribute. 200 | * 201 | * @param string $name The attribute name 202 | * @return mixed 203 | */ 204 | #[\ReturnTypeWillChange] 205 | public function __get($name) 206 | { 207 | return isset($_SESSION[$name]) ? $_SESSION[$name] : null; 208 | } 209 | 210 | /** 211 | * Sets an attribute. 212 | * 213 | * @param string $name 214 | * @param mixed $value 215 | * @return void 216 | */ 217 | #[\ReturnTypeWillChange] 218 | public function __set($name, $value) 219 | { 220 | $_SESSION[$name] = $value; 221 | } 222 | 223 | /** 224 | * Removes an attribute. 225 | * 226 | * @param string $name 227 | * @return void 228 | */ 229 | #[\ReturnTypeWillChange] 230 | public function __unset($name) 231 | { 232 | unset($_SESSION[$name]); 233 | } 234 | 235 | /** 236 | * Returns attributes. 237 | * 238 | * @return array Attributes 239 | */ 240 | public function all() 241 | { 242 | return $_SESSION; 243 | } 244 | 245 | 246 | /** 247 | * Retrieve an external iterator 248 | * 249 | * @return ArrayIterator Return an ArrayIterator of $_SESSION 250 | */ 251 | #[\ReturnTypeWillChange] 252 | public function getIterator() 253 | { 254 | return new ArrayIterator($_SESSION); 255 | } 256 | 257 | /** 258 | * Checks if the session was started. 259 | * 260 | * @return bool 261 | */ 262 | public function started() 263 | { 264 | return $this->started; 265 | } 266 | 267 | /** 268 | * http://php.net/manual/en/function.session-status.php#113468 269 | * Check if session is started nicely. 270 | * 271 | * @return bool 272 | */ 273 | protected function isSessionStarted() 274 | { 275 | return php_sapi_name() !== 'cli' ? session_id() !== '' : false; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /Session/tests/MessageTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /StreamWrapper/src/ReadOnlyStream.php: -------------------------------------------------------------------------------- 1 | getPath($uri); 42 | 43 | if (!$path) { 44 | if ($options & STREAM_REPORT_ERRORS) { 45 | trigger_error(sprintf('stream_open(): path for %s does not exist', $uri), E_USER_WARNING); 46 | } 47 | 48 | return false; 49 | } 50 | 51 | $this->uri = $uri; 52 | 53 | $handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode); 54 | if ($handle) { 55 | $this->handle = $handle; 56 | 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /** 64 | * @param int $operation 65 | * @return bool 66 | */ 67 | #[\ReturnTypeWillChange] 68 | public function stream_lock($operation) 69 | { 70 | // Disallow exclusive lock or non-blocking lock requests 71 | if (!in_array($operation, [LOCK_SH, LOCK_UN, LOCK_SH | LOCK_NB], true)) { 72 | trigger_error( 73 | sprintf('stream_lock() exclusive lock operations not allowed for %s', $this->uri), 74 | E_USER_WARNING 75 | ); 76 | 77 | return false; 78 | } 79 | 80 | return flock($this->handle, $operation); 81 | } 82 | 83 | /** 84 | * @param string $uri 85 | * @param int $option 86 | * @param mixed $value 87 | * @return bool 88 | */ 89 | #[\ReturnTypeWillChange] 90 | public function stream_metadata($uri, $option, $value) 91 | { 92 | if ($option !== STREAM_META_TOUCH) { 93 | throw new BadMethodCallException(sprintf('stream_metadata() not allowed for %s', $uri)); 94 | } 95 | 96 | return parent::stream_metadata($uri, $option, $value); 97 | } 98 | 99 | /** 100 | * @param string $data 101 | * @return int|false 102 | */ 103 | #[\ReturnTypeWillChange] 104 | public function stream_write($data) 105 | { 106 | throw new BadMethodCallException(sprintf('stream_write() not allowed for %s', $this->uri)); 107 | } 108 | 109 | /** 110 | * @param string $uri 111 | * @return bool 112 | */ 113 | #[\ReturnTypeWillChange] 114 | public function unlink($uri) 115 | { 116 | throw new BadMethodCallException(sprintf('unlink() not allowed for %s', $uri)); 117 | } 118 | 119 | /** 120 | * @param string $from_uri 121 | * @param string $to_uri 122 | * @return bool 123 | */ 124 | #[\ReturnTypeWillChange] 125 | public function rename($from_uri, $to_uri) 126 | { 127 | throw new BadMethodCallException(sprintf('rename() not allowed for %s', $from_uri)); 128 | } 129 | 130 | /** 131 | * @param string $uri 132 | * @param int $mode 133 | * @param int $options 134 | * @return bool 135 | */ 136 | #[\ReturnTypeWillChange] 137 | public function mkdir($uri, $mode, $options) 138 | { 139 | throw new BadMethodCallException(sprintf('mkdir() not allowed for %s', $uri)); 140 | } 141 | 142 | /** 143 | * @param string $uri 144 | * @param int $options 145 | * @return bool 146 | */ 147 | #[\ReturnTypeWillChange] 148 | public function rmdir($uri, $options) 149 | { 150 | throw new BadMethodCallException(sprintf('rmdir() not allowed for %s', $uri)); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /StreamWrapper/src/Stream.php: -------------------------------------------------------------------------------- 1 | getPath($uri, $mode); 48 | 49 | if (!$path) { 50 | if ($options & STREAM_REPORT_ERRORS) { 51 | trigger_error(sprintf('stream_open(): path for %s does not exist', $uri), E_USER_WARNING); 52 | } 53 | 54 | return false; 55 | } 56 | 57 | $this->uri = $uri; 58 | 59 | $handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode); 60 | if ($handle) { 61 | $this->handle = $handle; 62 | 63 | if (static::$locator instanceof UniformResourceLocator && !in_array($mode, ['r', 'rb', 'rt'], true)) { 64 | static::$locator->clearCache($this->uri); 65 | } 66 | 67 | return true; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | /** 74 | * @return bool 75 | */ 76 | #[\ReturnTypeWillChange] 77 | public function stream_close() 78 | { 79 | return fclose($this->handle); 80 | } 81 | 82 | /** 83 | * @param int $operation 84 | * @return bool 85 | */ 86 | #[\ReturnTypeWillChange] 87 | public function stream_lock($operation) 88 | { 89 | if (in_array($operation, [LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB], true)) { 90 | return flock($this->handle, $operation); 91 | } 92 | 93 | return false; 94 | } 95 | 96 | /** 97 | * @param string $uri 98 | * @param int $option 99 | * @param mixed $value 100 | * @return bool 101 | */ 102 | #[\ReturnTypeWillChange] 103 | public function stream_metadata($uri, $option, $value) 104 | { 105 | $path = $this->findPath($uri); 106 | if ($path) { 107 | switch ($option) { 108 | case STREAM_META_TOUCH: 109 | list($time, $atime) = $value; 110 | return touch($path, $time, $atime); 111 | 112 | case STREAM_META_OWNER_NAME: 113 | case STREAM_META_OWNER: 114 | return chown($path, $value); 115 | 116 | case STREAM_META_GROUP_NAME: 117 | case STREAM_META_GROUP: 118 | return chgrp($path, $value); 119 | 120 | case STREAM_META_ACCESS: 121 | return chmod($path, $value); 122 | } 123 | } 124 | 125 | return false; 126 | } 127 | 128 | /** 129 | * @param int $count 130 | * @return string|false 131 | */ 132 | #[\ReturnTypeWillChange] 133 | public function stream_read($count) 134 | { 135 | if ($count < 1) { 136 | return false; 137 | } 138 | 139 | return fread($this->handle, $count); 140 | } 141 | 142 | /** 143 | * @param string $data 144 | * @return int|false 145 | */ 146 | #[\ReturnTypeWillChange] 147 | public function stream_write($data) 148 | { 149 | return fwrite($this->handle, $data); 150 | } 151 | 152 | /** 153 | * @return bool 154 | */ 155 | #[\ReturnTypeWillChange] 156 | public function stream_eof() 157 | { 158 | return feof($this->handle); 159 | } 160 | 161 | /** 162 | * @param int $offset 163 | * @param int $whence 164 | * @return bool 165 | */ 166 | #[\ReturnTypeWillChange] 167 | public function stream_seek($offset, $whence) 168 | { 169 | // fseek returns 0 on success and -1 on a failure. 170 | return !fseek($this->handle, $offset, $whence); 171 | } 172 | 173 | /** 174 | * @return bool 175 | */ 176 | #[\ReturnTypeWillChange] 177 | public function stream_flush() 178 | { 179 | return fflush($this->handle); 180 | } 181 | 182 | /** 183 | * @return int|false 184 | */ 185 | #[\ReturnTypeWillChange] 186 | public function stream_tell() 187 | { 188 | return ftell($this->handle); 189 | } 190 | 191 | /** 192 | * @return array 193 | */ 194 | #[\ReturnTypeWillChange] 195 | public function stream_stat() 196 | { 197 | return fstat($this->handle) ?: []; 198 | } 199 | 200 | /** 201 | * @param int $option 202 | * @param int $arg1 203 | * @param int $arg2 204 | * @return bool|int 205 | */ 206 | #[\ReturnTypeWillChange] 207 | public function stream_set_option($option, $arg1, $arg2) 208 | { 209 | switch ($option) { 210 | case STREAM_OPTION_BLOCKING: 211 | return stream_set_blocking($this->handle, (bool)$arg1); 212 | case STREAM_OPTION_READ_TIMEOUT: 213 | return stream_set_timeout($this->handle, $arg1, $arg2); 214 | case STREAM_OPTION_WRITE_BUFFER: 215 | return stream_set_write_buffer($this->handle, $arg2); 216 | default: 217 | return false; 218 | } 219 | } 220 | 221 | /** 222 | * @param string $uri 223 | * @return bool 224 | */ 225 | #[\ReturnTypeWillChange] 226 | public function unlink($uri) 227 | { 228 | $path = $this->getPath($uri); 229 | 230 | if (!$path) { 231 | return false; 232 | } 233 | 234 | return unlink($path); 235 | } 236 | 237 | /** 238 | * @param string $fromUri 239 | * @param string $toUri 240 | * @return bool 241 | */ 242 | #[\ReturnTypeWillChange] 243 | public function rename($fromUri, $toUri) 244 | { 245 | $fromPath = $this->getPath($fromUri); 246 | $toPath = $this->getPath($toUri, 'w'); 247 | 248 | if (!$fromPath || !$toPath) { 249 | return false; 250 | } 251 | 252 | if (static::$locator instanceof UniformResourceLocator) { 253 | static::$locator->clearCache($fromUri); 254 | static::$locator->clearCache($toUri); 255 | } 256 | 257 | return rename($fromPath, $toPath); 258 | } 259 | 260 | /** 261 | * @param string $uri 262 | * @param int $mode 263 | * @param int $options 264 | * @return bool 265 | */ 266 | #[\ReturnTypeWillChange] 267 | public function mkdir($uri, $mode, $options) 268 | { 269 | $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE); 270 | $path = $this->getPath($uri, $recursive ? 'd' : 'w'); 271 | 272 | if (!$path) { 273 | if ($options & STREAM_REPORT_ERRORS) { 274 | trigger_error(sprintf('mkdir(): Could not create directory for %s', $uri), E_USER_WARNING); 275 | } 276 | 277 | return false; 278 | } 279 | 280 | if (static::$locator instanceof UniformResourceLocator) { 281 | static::$locator->clearCache($uri); 282 | } 283 | 284 | return ($options & STREAM_REPORT_ERRORS) ? mkdir($path, $mode, $recursive) : @mkdir($path, $mode, $recursive); 285 | } 286 | 287 | /** 288 | * @param string $uri 289 | * @param int $options 290 | * @return bool 291 | */ 292 | #[\ReturnTypeWillChange] 293 | public function rmdir($uri, $options) 294 | { 295 | $path = $this->getPath($uri); 296 | 297 | if (!$path) { 298 | if ($options & STREAM_REPORT_ERRORS) { 299 | trigger_error(sprintf('rmdir(): Directory not found for %s', $uri), E_USER_WARNING); 300 | } 301 | 302 | return false; 303 | } 304 | 305 | if (static::$locator instanceof UniformResourceLocator) { 306 | static::$locator->clearCache($uri); 307 | } 308 | 309 | return ($options & STREAM_REPORT_ERRORS) ? rmdir($path) : @rmdir($path); 310 | } 311 | 312 | /** 313 | * @param string $uri 314 | * @param int $flags 315 | * @return array|false 316 | */ 317 | #[\ReturnTypeWillChange] 318 | public function url_stat($uri, $flags) 319 | { 320 | $path = $this->getPath($uri); 321 | 322 | if (!$path) { 323 | return false; 324 | } 325 | 326 | // Suppress warnings if requested or if the file or directory does not 327 | // exist. This is consistent with PHPs plain filesystem stream wrapper. 328 | return ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) ? @stat($path) : stat($path); 329 | } 330 | 331 | /** 332 | * @param string $uri 333 | * @param int $options 334 | * @return bool 335 | */ 336 | #[\ReturnTypeWillChange] 337 | public function dir_opendir($uri, $options) 338 | { 339 | $path = $this->getPath($uri); 340 | 341 | if ($path === false) { 342 | return false; 343 | } 344 | 345 | $this->uri = $uri; 346 | 347 | $handle = opendir($path); 348 | if ($handle) { 349 | $this->handle = $handle; 350 | 351 | return true; 352 | } 353 | 354 | return false; 355 | } 356 | 357 | /** 358 | * @return string|false 359 | */ 360 | #[\ReturnTypeWillChange] 361 | public function dir_readdir() 362 | { 363 | return readdir($this->handle); 364 | } 365 | 366 | /** 367 | * @return bool 368 | */ 369 | #[\ReturnTypeWillChange] 370 | public function dir_rewinddir() 371 | { 372 | rewinddir($this->handle); 373 | 374 | return true; 375 | } 376 | 377 | /** 378 | * @return bool 379 | */ 380 | #[\ReturnTypeWillChange] 381 | public function dir_closedir() 382 | { 383 | closedir($this->handle); 384 | 385 | return true; 386 | } 387 | 388 | /** 389 | * @param string $uri 390 | * @param string|null $mode 391 | * @return string|false 392 | */ 393 | protected function getPath($uri, $mode = null) 394 | { 395 | if ($mode === null) { 396 | $mode = 'r'; 397 | } 398 | 399 | $path = $this->findPath($uri); 400 | 401 | if ($path && file_exists($path)) { 402 | return $path; 403 | } 404 | 405 | if (strpos($mode[0], 'r') === 0) { 406 | return false; 407 | } 408 | 409 | // We are either opening a file or creating directory. 410 | list($scheme, $target) = explode('://', $uri, 2); 411 | 412 | if ($target === '') { 413 | return false; 414 | } 415 | $target = explode('/', $target); 416 | $filename = []; 417 | 418 | do { 419 | $filename[] = array_pop($target); 420 | 421 | $path = $this->findPath($scheme . '://' . implode('/', $target)); 422 | } while ($target && !$path); 423 | 424 | if (!$path) { 425 | return false; 426 | } 427 | 428 | return $path . '/' . implode('/', array_reverse($filename)); 429 | } 430 | 431 | /** 432 | * @param string $uri 433 | * @return string|false 434 | */ 435 | protected function findPath($uri) 436 | { 437 | return static::$locator && static::$locator->isStream($uri) ? static::$locator->findResource($uri) : false; 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /StreamWrapper/src/StreamBuilder.php: -------------------------------------------------------------------------------- 1 | $handler) { 24 | $this->add($scheme, $handler); 25 | } 26 | } 27 | 28 | /** 29 | * @param string $scheme 30 | * @param string $handler 31 | * @return $this 32 | * @throws InvalidArgumentException 33 | */ 34 | public function add($scheme, $handler) 35 | { 36 | if (isset($this->items[$scheme])) { 37 | if ($handler === $this->items[$scheme]) { 38 | return $this; 39 | } 40 | throw new InvalidArgumentException("Stream '{$scheme}' has already been initialized."); 41 | } 42 | 43 | if (!is_subclass_of($handler, StreamInterface::class)) { 44 | throw new InvalidArgumentException("Stream '{$scheme}' has unknown or invalid type."); 45 | } 46 | 47 | if (!@stream_wrapper_register($scheme, $handler)) { 48 | throw new InvalidArgumentException("Stream '{$scheme}' could not be initialized."); 49 | } 50 | 51 | $this->items[$scheme] = $handler; 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * @param string $scheme 58 | * @return $this 59 | */ 60 | public function remove($scheme) 61 | { 62 | if (isset($this->items[$scheme])) { 63 | stream_wrapper_unregister($scheme); 64 | unset($this->items[$scheme]); 65 | } 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * @return array 72 | */ 73 | public function getStreams() 74 | { 75 | return $this->items; 76 | } 77 | 78 | /** 79 | * @param string $scheme 80 | * @return bool 81 | */ 82 | public function isStream($scheme) 83 | { 84 | return isset($this->items[$scheme]); 85 | } 86 | 87 | /** 88 | * @param string $scheme 89 | * @return StreamInterface|null 90 | */ 91 | public function getStreamType($scheme) 92 | { 93 | return isset($this->items[$scheme]) ? $this->items[$scheme] : null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /StreamWrapper/src/StreamInterface.php: -------------------------------------------------------------------------------- 1 | =5.6.0", 10 | "ext-json": "*", 11 | "pimple/pimple": "^3.0", 12 | "symfony/yaml": "^3.4|^4.0|^5.0", 13 | "symfony/event-dispatcher": "^3.4|^4.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "RocketTheme\\Toolbox\\ArrayTraits\\": "ArrayTraits/src", 18 | "RocketTheme\\Toolbox\\Blueprints\\": "Blueprints/src", 19 | "RocketTheme\\Toolbox\\Compat\\": "Compat/src", 20 | "RocketTheme\\Toolbox\\DI\\": "DI/src", 21 | "RocketTheme\\Toolbox\\Event\\": "Event/src", 22 | "RocketTheme\\Toolbox\\File\\": "File/src", 23 | "RocketTheme\\Toolbox\\ResourceLocator\\": "ResourceLocator/src", 24 | "RocketTheme\\Toolbox\\Session\\": "Session/src", 25 | "RocketTheme\\Toolbox\\StreamWrapper\\": "StreamWrapper/src" 26 | }, 27 | "exclude-from-classmap": [ 28 | "**/tests/" 29 | ] 30 | }, 31 | "archive": { 32 | "exclude": [ 33 | "tests" 34 | ] 35 | }, 36 | "scripts": { 37 | "test": "vendor/bin/phpunit run unit", 38 | "test-windows": "vendor\\bin\\phpunit run unit", 39 | "phpstan": "vendor/bin/phpstan analyse -l 8 -c ./tests/phpstan/phpstan.neon . --memory-limit=128M --no-progress" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ./*/tests/ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/phpstan/phpstan-bootstrap.php: -------------------------------------------------------------------------------- 1 |