1010namespace Piwik \Config ;
1111
1212use Piwik \Config ;
13+ use Piwik \Common ;
14+ use Piwik \Container \StaticContainer ;
15+ use Piwik \Log \LoggerInterface ;
1316
1417abstract class SectionConfig
1518{
@@ -31,39 +34,248 @@ public static function setConfigValue(string $name, $value): void
3134 /**
3235 * Get a setting value
3336 *
37+ * Prefer one of the type-safe getters if a specific type is expected:
38+ * @see getIntegerConfigValue()
39+ * @see getFloatConfigValue()
40+ * @see getBoolConfigValue()
41+ * @see getStringConfigValue()
42+ * @see getArrayConfigValue()
43+ *
3444 * @param string $name Setting name
3545 * @param int|null $idSite Optional site Id
3646 *
3747 * @return mixed|null
3848 */
3949 public static function getConfigValue (string $ name , ?int $ idSite = null )
4050 {
41- $ config = self ::getConfig ();
42- if (!empty ($ idSite )) {
43- $ siteSpecificConfig = self ::getSiteSpecificConfig ($ idSite );
44- $ config = array_merge ($ config , $ siteSpecificConfig );
45- }
51+ return static ::getRawConfigValue ($ name , $ idSite );
52+ }
53+
54+ /**
55+ * @phpstan-return ($default is null ? int|null : int)
56+ */
57+ public static function getIntegerConfigValue (string $ name , ?int $ default = null , ?int $ idSite = null ): ?int
58+ {
59+ return self ::castIntConfigValue ($ name , static ::getRawConfigValue ($ name , $ idSite ), $ default );
60+ }
61+
62+ /**
63+ * @phpstan-return ($default is null ? float|null : float)
64+ */
65+ public static function getFloatConfigValue (string $ name , ?float $ default = null , ?int $ idSite = null ): ?float
66+ {
67+ return self ::castFloatConfigValue ($ name , static ::getRawConfigValue ($ name , $ idSite ), $ default );
68+ }
69+
70+ /**
71+ * @phpstan-return ($default is null ? bool|null : bool)
72+ */
73+ public static function getBoolConfigValue (string $ name , ?bool $ default = null , ?int $ idSite = null ): ?bool
74+ {
75+ return self ::castBoolConfigValue ($ name , static ::getRawConfigValue ($ name , $ idSite ), $ default );
76+ }
77+
78+ /**
79+ * @phpstan-return ($default is null ? string|null : string)
80+ */
81+ public static function getStringConfigValue (string $ name , ?string $ default = null , ?int $ idSite = null ): ?string
82+ {
83+ return self ::castStringConfigValue ($ name , static ::getRawConfigValue ($ name , $ idSite ), $ default );
84+ }
85+
86+ /**
87+ * @return array<mixed>|null
88+ * @phpstan-return ($default is null ? array<mixed>|null : array<mixed>)
89+ */
90+ public static function getArrayConfigValue (string $ name , ?array $ default = null , ?int $ idSite = null ): ?array
91+ {
92+ return self ::castArrayConfigValue ($ name , static ::getRawConfigValue ($ name , $ idSite ), $ default );
93+ }
94+
95+ /**
96+ * @return mixed|null
97+ */
98+ protected static function getRawConfigValue (string $ name , ?int $ idSite = null )
99+ {
100+ $ config = self ::getMergedConfig ($ idSite );
46101 return $ config [$ name ] ?? null ;
47102 }
48103
49104 /**
50105 * Get the section config as an array
51106 *
52- * @return array| string
107+ * @return array< string, mixed>
53108 */
54- private static function getConfig ()
109+ private static function getConfig (): array
55110 {
56- return Config::getInstance ()->{static ::getSectionName ()};
111+ $ config = Config::getInstance ()->{static ::getSectionName ()};
112+ return is_array ($ config ) ? $ config : [];
57113 }
58114
59115 /**
60116 * Get the site specific config (if any) as an array
61117 *
62- * @return array| string
118+ * @return array< string, mixed>
63119 */
64- private static function getSiteSpecificConfig (int $ idSite )
120+ private static function getSiteSpecificConfig (int $ idSite ): array
65121 {
66122 $ key = static ::getSectionName () . '_ ' . $ idSite ;
67- return Config::getInstance ()->$ key ;
123+ $ config = Config::getInstance ()->$ key ;
124+ return is_array ($ config ) ? $ config : [];
125+ }
126+
127+ /**
128+ * @return array<string, mixed>
129+ */
130+ private static function getMergedConfig (?int $ idSite = null ): array
131+ {
132+ $ config = self ::getConfig ();
133+ if (!empty ($ idSite )) {
134+ $ config = array_merge ($ config , self ::getSiteSpecificConfig ($ idSite ));
135+ }
136+ return $ config ;
137+ }
138+
139+ /**
140+ * @param mixed $value
141+ */
142+ private static function castIntConfigValue (string $ name , $ value , ?int $ default ): ?int
143+ {
144+ if ($ value === null ) {
145+ return $ default ;
146+ }
147+
148+ if ((is_string ($ value ) || is_numeric ($ value )) && (string ) $ value === (string ) (int ) $ value ) {
149+ return (int ) $ value ;
150+ }
151+
152+ self ::logTypeCastWarning ($ name , 'int ' , $ value );
153+ return $ default ;
154+ }
155+
156+ /**
157+ * @param mixed $value
158+ */
159+ private static function castFloatConfigValue (string $ name , $ value , ?float $ default ): ?float
160+ {
161+ if ($ value === null ) {
162+ return $ default ;
163+ }
164+
165+ $ parsedFloat = Common::parseFloat ($ value );
166+ if ($ parsedFloat !== null ) {
167+ return $ parsedFloat ;
168+ }
169+
170+ self ::logTypeCastWarning ($ name , 'float ' , $ value );
171+ return $ default ;
172+ }
173+
174+ /**
175+ * @param mixed $value
176+ */
177+ private static function castBoolConfigValue (string $ name , $ value , ?bool $ default ): ?bool
178+ {
179+ if ($ value === null ) {
180+ return $ default ;
181+ }
182+
183+ if ($ value === false || $ value === true ) {
184+ return $ value ;
185+ }
186+
187+ if ((is_string ($ value ) && strtolower ($ value ) === 'false ' ) || $ value === '0 ' || $ value === 0 ) {
188+ return false ;
189+ }
190+
191+ if ((is_string ($ value ) && strtolower ($ value ) === 'true ' ) || $ value === '1 ' || $ value === 1 ) {
192+ return true ;
193+ }
194+
195+ self ::logTypeCastWarning ($ name , 'bool ' , $ value );
196+ return $ default ;
197+ }
198+
199+ /**
200+ * @param mixed $value
201+ */
202+ private static function castStringConfigValue (string $ name , $ value , ?string $ default ): ?string
203+ {
204+ if ($ value === null ) {
205+ return $ default ;
206+ }
207+
208+ if (is_string ($ value ) || is_numeric ($ value )) {
209+ return Common::sanitizeNullBytes ((string ) $ value );
210+ }
211+
212+ self ::logTypeCastWarning ($ name , 'string ' , $ value );
213+ return $ default ;
214+ }
215+
216+ /**
217+ * @param mixed $value
218+ * @return array<mixed>|null
219+ */
220+ private static function castArrayConfigValue (string $ name , $ value , ?array $ default ): ?array
221+ {
222+ if ($ value === null ) {
223+ return $ default ;
224+ }
225+
226+ if (is_array ($ value )) {
227+ /** @var array<mixed> $sanitizedValue */
228+ $ sanitizedValue = self ::filterNullBytes ($ value );
229+ return $ sanitizedValue ;
230+ }
231+
232+ self ::logTypeCastWarning ($ name , 'array ' , $ value );
233+ return $ default ;
234+ }
235+
236+ /**
237+ * @param mixed $value
238+ */
239+ private static function logTypeCastWarning (string $ name , string $ type , $ value ): void
240+ {
241+ StaticContainer::get (LoggerInterface::class)->warning (
242+ 'Failed to cast config value {section}.{name} to {type}; actual type was {actualType}. ' ,
243+ [
244+ 'section ' => static ::getSectionName (),
245+ 'name ' => $ name ,
246+ 'type ' => $ type ,
247+ 'actualType ' => self ::getValueType ($ value ),
248+ ]
249+ );
250+ }
251+
252+ /**
253+ * @param mixed $value
254+ */
255+ private static function getValueType ($ value ): string
256+ {
257+ if (is_object ($ value )) {
258+ return get_class ($ value );
259+ }
260+
261+ return gettype ($ value );
262+ }
263+
264+ /**
265+ * @param mixed $value
266+ * @return mixed
267+ */
268+ private static function filterNullBytes ($ value )
269+ {
270+ if (is_array ($ value )) {
271+ $ result = [];
272+ foreach ($ value as $ key => $ arrayValue ) {
273+ $ result [$ key ] = self ::filterNullBytes ($ arrayValue );
274+ }
275+
276+ return $ result ;
277+ }
278+
279+ return is_string ($ value ) ? Common::sanitizeNullBytes ($ value ) : $ value ;
68280 }
69281}
0 commit comments