ConfigManager   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 84
c 5
b 1
f 0
dl 0
loc 214
ccs 0
cts 92
cp 0
rs 9.84
wmc 32

14 Methods

Rating   Name   Duplication   Size   Complexity  
A convertArrayToYaml() 0 5 2
A __construct() 0 5 1
A getYamlFile() 0 6 2
A dumpYaml() 0 7 2
A parseYaml() 0 18 3
A shouldCompile() 0 10 3
A normalize() 0 7 1
A buildConfig() 0 12 3
A compile() 0 14 4
A parseRawStrings() 0 11 2
A getCompileDestination() 0 6 1
A normalizeYamlValues() 0 7 2
A normalizeArray() 0 11 4
A setTimestamp() 0 5 2
1
<?php
2
3
namespace GeminiLabs\Pollux\Config;
4
5
use GeminiLabs\Pollux\Application;
6
use GeminiLabs\Pollux\Config\Config;
7
use GeminiLabs\Pollux\MetaBox\SiteMetaManager;
8
use Symfony\Component\Yaml\Exception\DumpException;
9
use Symfony\Component\Yaml\Exception\ParseException;
10
use Symfony\Component\Yaml\Yaml;
11
12
/**
13
 * @property int $updated
14
 */
15
class ConfigManager extends SiteMetaManager
16
{
17
	const RAW_STRINGS = [
18
		// '__', '_n', '_x', 'esc_attr__', 'esc_html__', 'sprintf',
19
	];
20
21
	public $compiled;
22
23
	public $parseError = false;
24
25
	/**
26
	 * @var Application
27
	 */
28
	protected $app;
29
30
	public function __construct( Application $app )
31
	{
32
		$this->app = $app;
33
		$this->options = $this->buildConfig();
34
		$this->compiled = $this->compile();
35
	}
36
37
	/**
38
	 * @return array
39
	 */
40
	public function buildConfig()
41
	{
42
		$yamlFile = $this->getYamlFile();
43
		$yaml = $this->normalizeYamlValues( $this->normalize(
44
			$this->parseYaml( file_get_contents( $yamlFile ), $yamlFile )
45
		));
46
		if( !$yaml['disable_config'] ) {
47
			$config = array_filter( (array) get_option( Config::id(), [] ));
48
		}
49
		return empty( $config )
50
			? $this->setTimestamp( $yaml, filemtime( $yamlFile ))
51
			: $this->normalizeYamlValues( $this->normalize( $config ));
52
	}
53
54
	/**
55
	 * @param bool $force
56
	 * @return object
57
	 */
58
	public function compile( $force = false )
59
	{
60
		$configFile = $this->getCompileDestination();
61
		if( $force || $this->shouldCompile( $configFile )) {
62
			$config = $this->normalizeArray( $this->options );
0 ignored issues
show
Bug introduced by
It seems like $this->options can also be of type boolean; however, parameter $array of GeminiLabs\Pollux\Config...nager::normalizeArray() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
			$config = $this->normalizeArray( /** @scrutinizer ignore-type */ $this->options );
Loading history...
63
			if( $this->parseError ) {
64
				return (object) $config;
65
			}
66
			file_put_contents( $configFile, sprintf( '<?php // DO NOT MODIFY THIS FILE DIRECTLY!%sreturn (object) %s;',
67
				PHP_EOL,
68
				$this->parseRawStrings( var_export( $this->setTimestamp( $config ), true ))
69
			));
70
		}
71
		return include $configFile;
72
	}
73
74
	/**
75
	 * @return string
76
	 */
77
	public function convertArrayToYaml( array $array )
78
	{
79
		return !empty( $array )
80
			? trim( $this->parseRawStrings( $this->dumpYaml( $array )))
81
			: '';
82
	}
83
84
	/**
85
	 * @return string
86
	 */
87
	public function getCompileDestination( $filename = 'pollux-config.php' )
88
	{
89
		$filename = apply_filters( 'pollux/config/dist/file', $filename );
90
		$storagePath = apply_filters( 'pollux/config/dist/location', WP_CONTENT_DIR );
91
		wp_mkdir_p( $storagePath );
92
		return sprintf( '%s%s', trailingslashit( $storagePath ), $filename );
93
	}
94
95
	/**
96
	 * @return string
97
	 */
98
	public function getYamlFile()
99
	{
100
		if( $file = $this->app->getFile( 'pollux.yml' )) {
101
			return $file;
102
		}
103
		return $this->app->path( 'defaults.yml' );
104
	}
105
106
	/**
107
	 * @return array
108
	 */
109
	public function normalizeArray( array $array )
110
	{
111
		array_walk( $array, function( &$value, $key ) {
112
			if( !is_numeric( $value ) && is_string( $value )) {
113
				$value = $this->parseYaml( $value, $key );
114
				if( $this->parseError == $key ) {
115
					$value = [];
116
				}
117
			}
118
		});
119
		return $array;
120
	}
121
122
	/**
123
	 * @return array
124
	 */
125
	public function normalizeYamlValues( array $array )
126
	{
127
		return array_map( function( $value ) {
128
			return is_array( $value )
129
				? $this->convertArrayToYaml( $value )
130
				: $value;
131
		}, $array );
132
	}
133
134
	/**
135
	 * @return array
136
	 */
137
	public function setTimestamp( array $config, $timestamp = null )
138
	{
139
		$timestamp || $timestamp = time();
140
		$config['updated'] = $timestamp;
141
		return $config;
142
	}
143
144
	/**
145
	 * @return string|null
146
	 */
147
	protected function dumpYaml( array $array )
148
	{
149
		try {
150
			return Yaml::dump( $array, 13, 2 );
151
		}
152
		catch( DumpException $e ) {
153
			$this->app->make( 'Notice' )->addError( $e->getMessage() );
154
		}
155
	}
156
157
	/**
158
	 * @return array
159
	 */
160
	protected function normalize( array $config )
161
	{
162
		return wp_parse_args(
163
			$config,
164
			$this->parseYaml(
165
				file_get_contents( $this->app->path( 'defaults.yml' )),
166
				$this->app->path( 'defaults.yml' )
167
			)
168
		);
169
	}
170
171
	/**
172
	 * @param string $configString
173
	 * @return string
174
	 * @todo only allow raw strings when we can parse them properly without using eval()
175
	 */
176
	protected function parseRawStrings( $configString )
177
	{
178
		$strings = apply_filters( 'pollux/config/raw_strings', static::RAW_STRINGS );
179
		if( empty( $strings )) {
180
			return $configString;
181
		}
182
		$pattern = '/(\')((' . implode( '|', $strings ) . ')\(?.+\))(\')/';
183
		return stripslashes(
184
			preg_replace_callback( $pattern, function( $matches ) {
185
				return str_replace( "''", "'", $matches[2] );
186
			}, $configString )
187
		);
188
	}
189
190
	/**
191
	 * @link http://api.symfony.com/3.2/Symfony/Component/Yaml/Exception/ParseException.html
192
	 * @return array
193
	 */
194
	protected function parseYaml( $value, $file = null )
195
	{
196
		try {
197
			return (array) Yaml::parse( $value );
198
		}
199
		catch( ParseException $e ) {
200
			$this->parseError = $file;
201
			if( $file ) {
202
				$file = sprintf( '<code>%s</code>', $file );
203
			}
204
			$this->app->make( 'Notice' )->addError([
205
				sprintf( '<strong>Pollux Error:</strong> Unable to parse config at line %s (near "%s").',
206
					$e->getParsedLine(),
207
					$e->getSnippet()
208
				),
209
				$file
210
			]);
211
			return $value;
212
		}
213
	}
214
215
	/**
216
	 * @param string $configFile
217
	 * @return bool
218
	 */
219
	protected function shouldCompile( $configFile )
220
	{
221
		if( !file_exists( $configFile )) {
222
			return true;
223
		}
224
		$config = include $configFile;
225
		if( $this->updated >= $config->updated ) {
226
			return true;
227
		}
228
		return filemtime( $this->getYamlFile() ) >= $config->updated;
229
	}
230
}
231