Passed
Push — develop ( 5f7986...fd393c )
by Paul
03:01
created

ConfigManager::__get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
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',
1 ignored issue
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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
	 * @return object
56
	 */
57
	public function compile()
58
	{
59
		$configFile = $this->getCompileDestination();
60
		if( $this->shouldCompile( $configFile )) {
61
			$config = $this->normalizeArray( $this->options );
62
			if( $this->parseError ) {
63
				return (object) $config;
64
			}
65
			file_put_contents( $configFile, sprintf( '<?php // DO NOT MODIFY THIS FILE DIRECTLY!%sreturn (object) %s;',
66
				PHP_EOL,
67
				$this->parseRawStrings( var_export( $this->setTimestamp( $config ), true ))
68
			));
69
		}
70
		return include $configFile;
71
	}
72
73
	/**
74
	 * @return string
75
	 */
76
	public function convertArrayToYaml( array $array )
77
	{
78
		return !empty( $array )
79
			? trim( $this->parseRawStrings( $this->dumpYaml( $array )))
80
			: '';
81
	}
82
83
	/**
84
	 * @return string
85
	 */
86
	public function getCompileDestination( $filename = 'pollux-config.php' )
87
	{
88
		$filename = apply_filters( 'pollux/config/dist/file', $filename );
89
		$storagePath = apply_filters( 'pollux/config/dist/location', WP_CONTENT_DIR );
90
		wp_mkdir_p( $storagePath );
91
		return sprintf( '%s%s', trailingslashit( $storagePath ), $filename );
92
	}
93
94
	/**
95
	 * @return string
96
	 */
97
	public function getYamlFile()
98
	{
99
		$theme = wp_get_theme();
100
		$configYaml = apply_filters( 'pollux/config/src/file', 'pollux.yml' );
101
		$configLocations = apply_filters( 'pollux/config/src/location', [
102
			trailingslashit( trailingslashit( $theme->theme_root ) . $theme->stylesheet ),
103
			trailingslashit( trailingslashit( $theme->theme_root ) . $theme->template ),
104
			trailingslashit( WP_CONTENT_DIR ),
105
			trailingslashit( ABSPATH ),
106
			trailingslashit( dirname( ABSPATH )),
107
			trailingslashit( dirname( dirname( ABSPATH ))),
108
		]);
109
		foreach( (array) $configLocations as $location ) {
110
			if( !file_exists( $location . $configYaml ))continue;
111
			return $location . $configYaml;
112
		}
113
		return $this->app->path( 'defaults.yml' );
114
	}
115
116
	/**
117
	 * @return array
118
	 */
119
	public function normalizeArray( array $array )
120
	{
121
		array_walk( $array, function( &$value, $key ) {
122
			if( !is_numeric( $value ) && is_string( $value )) {
123
				$value = $this->parseYaml( $value, $key );
124
				if( $this->parseError == $key ) {
125
					$value = [];
126
				}
127
			}
128
		});
129
		return $array;
130
	}
131
132
	/**
133
	 * @return array
134
	 */
135
	public function normalizeYamlValues( array $array )
136
	{
137
		return array_map( function( $value ) {
138
			return is_array( $value )
139
				? $this->convertArrayToYaml( $value )
140
				: $value;
141
		}, $array );
142
	}
143
144
	/**
145
	 * @return array
146
	 */
147
	public function setTimestamp( array $config, $timestamp = null )
148
	{
149
		$timestamp || $timestamp = time();
150
		$config['updated'] = $timestamp;
151
		return $config;
152
	}
153
154
	/**
155
	 * @return string|null
156
	 */
157
	protected function dumpYaml( array $array )
158
	{
159
		try {
160
			return Yaml::dump( $array, 13, 2 );
161
		}
162
		catch( DumpException $e ) {
163
			$this->app->make( 'Notice' )->addError( $e->getMessage() );
164
		}
165
	}
166
167
	/**
168
	 * @return array
169
	 */
170
	protected function normalize( array $config )
171
	{
172
		return wp_parse_args(
173
			$config,
174
			$this->parseYaml(
175
				file_get_contents( $this->app->path( 'defaults.yml' )),
176
				$this->app->path( 'defaults.yml' )
177
			)
178
		);
179
	}
180
181
	/**
182
	 * @param string $configString
183
	 * @return string
184
	 * @todo only allow raw strings when we can parse them properly without using eval()
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
185
	 */
186
	protected function parseRawStrings( $configString )
187
	{
188
		$strings = apply_filters( 'pollux/config/raw_strings', static::RAW_STRINGS );
189
		if( empty( $strings )) {
190
			return $configString;
191
		}
192
		$pattern = '/(\')((' . implode( '|', $strings ) . ')\(?.+\))(\')/';
193
		return stripslashes(
194
			preg_replace_callback( $pattern, function( $matches ) {
195
				return str_replace( "''", "'", $matches[2] );
196
			}, $configString )
197
		);
198
	}
199
200
	/**
201
	 * @link http://api.symfony.com/3.2/Symfony/Component/Yaml/Exception/ParseException.html
202
	 * @return array
203
	 */
204
	protected function parseYaml( $value, $file = null )
205
	{
206
		try {
207
			return (array) Yaml::parse( $value );
208
		}
209
		catch( ParseException $e ) {
210
			$this->parseError = $file;
211
			if( $file ) {
212
				$file = sprintf( '<code>%s</code>', $file );
213
			}
214
			$this->app->make( 'Notice' )->addError([
215
				sprintf( '<strong>Pollux Error:</strong> Unable to parse config at line %s (near "%s").',
216
					$e->getParsedLine(),
217
					$e->getSnippet()
218
				),
219
				$file
220
			]);
221
			return $value;
222
		}
223
	}
224
225
	/**
226
	 * @param string $configFile
227
	 * @return bool
228
	 */
229
	protected function shouldCompile( $configFile )
230
	{
231
		if( !file_exists( $configFile )) {
232
			return true;
233
		}
234
		$config = include $configFile;
235
		if( $this->updated >= $config->updated ) {
236
			return true;
237
		}
238
		return filemtime( $this->getYamlFile() ) >= $config->updated;
239
	}
240
}
241