Issues (4)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/inflector.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
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 ICanBoogie;
13
14
/**
15
 * The Inflector transforms words from singular to plural, class names to table names, modularized
16
 * class names to ones without, and class names to foreign keys. Inflections can be localized, the
17
 * default english inflections for pluralization, singularization, and uncountable words are
18
 * kept in `lib/inflections/en.php`.
19
 *
20
 * @property-read Inflections $inflections Inflections used by the inflector.
21
 */
22
class Inflector
23
{
24
	/**
25
	 * Default inflector locale.
26
	 *
27
	 * Alias to {@link INFLECTOR_DEFAULT_LOCALE}.
28
	 */
29
	public const DEFAULT_LOCALE = INFLECTOR_DEFAULT_LOCALE;
30
31
	/**
32
	 * {@link camelize()} option to downcase the first letter.
33
	 */
34
	public const DOWNCASE_FIRST_LETTER = true;
35
36
	/**
37
	 * {@link camelize()} option to keep the first letter as is.
38
	 */
39
	public const UPCASE_FIRST_LETTER = false;
40
41
	/**
42
	 * @var array<string, Inflector>
43
	 */
44
	static private $inflectors = [];
45
46
	/**
47
	 * Returns an inflector for the specified locale.
48
	 *
49
	 * Note: Inflectors are shared for the same locale. If you need to alter an inflector you
50
	 * MUST clone it first.
51
	 */
52
	static public function get(string $locale = self::DEFAULT_LOCALE): self
53
	{
54
		if (isset(self::$inflectors[$locale]))
55
		{
56
			return self::$inflectors[$locale];
57
		}
58
59
		return self::$inflectors[$locale] = new static(Inflections::get($locale));
60
	}
61
62
	/**
63
	 * Inflections used by the inflector.
64
	 *
65
	 * @var Inflections
66
	 */
67
	protected $inflections;
68
69
	public function __construct(Inflections $inflections = null)
70
	{
71
		$this->inflections = $inflections ?: new Inflections;
72
	}
73
74
	/**
75
	 * Returns the {@link $inflections} property.
76
	 *
77
	 * @throws PropertyNotDefined in attempt to read an inaccessible property. If the {@link PropertyNotDefined}
78
	 * class is not available a {@link \InvalidArgumentException} is thrown instead.
79
	 */
80
	public function __get(string $property)
81
	{
82
		if ($property === 'inflections')
83
		{
84
			return $this->$property;
85
		}
86
87
		if (class_exists('ICanBoogie\PropertyNotDefined'))
88
		{
89
			throw new PropertyNotDefined([ $property, $this ]);
90
		}
91
		else
92
		{
93
			throw new \InvalidArgumentException("Property not defined: $property");
94
		}
95
	}
96
97
	/**
98
	 * Clone inflections.
99
	 */
100
	public function __clone()
101
	{
102
		$this->inflections = clone $this->inflections;
103
	}
104
105
	/**
106
	 * Applies inflection rules for {@link singularize} and {@link pluralize}.
107
	 *
108
	 * <pre>
109
	 * $this->apply_inflections('post', $this->plurals);    // "posts"
110
	 * $this->apply_inflections('posts', $this->singulars); // "post"
111
	 * </pre>
112
	 *
113
	 * @param array<string, string> $rules
114
	 */
115
	private function apply_inflections(string $word, array $rules): string
116
	{
117
		$rc = (string) $word;
118
119
		if (!$rc)
120
		{
121
			return $rc;
122
		}
123
124
		if (preg_match('/\b[[:word:]]+\Z/u', downcase($rc), $matches))
125
		{
126
			if (isset($this->inflections->uncountables[$matches[0]]))
127
			{
128
				return $rc;
129
			}
130
		}
131
132
		foreach ($rules as $rule => $replacement)
133
		{
134
			$rc = preg_replace($rule, $replacement, $rc, -1, $count);
135
136
			if ($count) break;
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
137
		}
138
139
		return $rc;
140
	}
141
142
	/**
143
	 * Returns the plural form of the word in the string.
144
	 *
145
	 * <pre>
146
	 * $this->pluralize('post');       // "posts"
147
	 * $this->pluralize('children');   // "child"
148
	 * $this->pluralize('sheep');      // "sheep"
149
	 * $this->pluralize('words');      // "words"
150
	 * $this->pluralize('CamelChild'); // "CamelChildren"
151
	 * </pre>
152
	 */
153
	public function pluralize(string $word): string
154
	{
155
		return $this->apply_inflections($word, $this->inflections->plurals);
156
	}
157
158
	/**
159
	 * The reverse of {@link pluralize}, returns the singular form of a word in a string.
160
	 *
161
	 * <pre>
162
	 * $this->singularize('posts');         // "post"
163
	 * $this->singularize('children');      // "child"
164
	 * $this->singularize('sheep');         // "sheep"
165
	 * $this->singularize('word');          // "word"
166
	 * $this->singularize('CamelChildren'); // "CamelChild"
167
	 * </pre>
168
	 */
169
	public function singularize(string $word): string
170
	{
171
		return $this->apply_inflections($word, $this->inflections->singulars);
172
	}
173
174
	/**
175
	 * By default, {@link camelize} converts strings to UpperCamelCase.
176
	 *
177
	 * {@link camelize} will also convert "/" to "\" which is useful for converting paths to
178
	 * namespaces.
179
	 *
180
	 * <pre>
181
	 * $this->camelize('active_model');                // 'ActiveModel'
182
	 * $this->camelize('active_model', true);          // 'activeModel'
183
	 * $this->camelize('active_model/errors');         // 'ActiveModel\Errors'
184
	 * $this->camelize('active_model/errors', true);   // 'activeModel\Errors'
185
	 * </pre>
186
	 *
187
	 * As a rule of thumb you can think of {@link camelize} as the inverse of {@link underscore},
188
	 * though there are cases where that does not hold:
189
	 *
190
	 * <pre>
191
	 * $this->camelize($this->underscore('SSLError')); // "SslError"
192
	 * </pre>
193
	 *
194
	 * @param bool $downcase_first_letter One of {@link UPCASE_FIRST_LETTER},
195
	 * {@link DOWNCASE_FIRST_LETTER}.
196
	 */
197
	public function camelize(string $term, bool $downcase_first_letter = self::UPCASE_FIRST_LETTER): string
198
	{
199
		$string = (string) $term;
200
		$acronyms = $this->inflections->acronyms;
201
202
		if ($downcase_first_letter)
203
		{
204
			$string = preg_replace_callback('/^(?:' . trim($this->inflections->acronym_regex, '/') . '(?=\b|[[:upper:]_])|\w)/u', function(array $matches): string {
205
206
				return downcase($matches[0]);
207
208
			}, $string, 1);
209
		}
210
		else
211
		{
212
			$string = preg_replace_callback('/^[[:lower:]\d]*/u', function(array $matches) use($acronyms): string {
213
214
				$m = $matches[0];
215
216
				return !empty($acronyms[$m]) ? $acronyms[$m] : capitalize($m, true);
217
218
			}, $string, 1);
219
		}
220
221
		$string = preg_replace_callback('/(?:_|-|(\/))([[:alnum:]]*)/u', function(array $matches) use($acronyms): string {
222
223
			list(, $m1, $m2) = $matches;
224
225
			return $m1 . (isset($acronyms[$m2]) ? $acronyms[$m2] : capitalize($m2, true));
226
227
		}, $string);
228
229
		$string = str_replace('/', '\\', $string);
230
231
		return $string;
232
	}
233
234
	/**
235
	 * Makes an underscored, lowercase form from the expression in the string.
236
	 *
237
	 * Changes "\" to "/" to convert namespaces to paths.
238
	 *
239
	 * <pre>
240
	 * $this->underscore('ActiveModel');        // 'active_model'
241
	 * $this->underscore('ActiveModel\Errors'); // 'active_model/errors'
242
	 * </pre>
243
	 *
244
	 * As a rule of thumb you can think of {@link underscore} as the inverse of {@link camelize()},
245
	 * though there are cases where that does not hold:
246
	 *
247
	 * <pre>
248
	 * $this->camelize($this->underscore('SSLError')); // "SslError"
249
	 * </pre>
250
	 */
251
	public function underscore(string $camel_cased_word): string
252
	{
253
		$word = (string) $camel_cased_word;
254
		$word = str_replace('\\', '/', $word);
255
		$word = preg_replace_callback('/(?:([[:alpha:]\d])|^)(' . trim($this->inflections->acronym_regex, '/') . ')(?=\b|[^[:lower:]])/u', function(array $matches): string {
256
257
			list(, $m1, $m2) = $matches;
258
259
			return $m1 . ($m1 ? '_' : '') . downcase($m2);
260
261
		}, $word);
262
263
		$word = preg_replace('/([[:upper:]\d]+)([[:upper:]][[:lower:]])/u', '\1_\2', $word);
264
		$word = preg_replace('/([[:lower:]\d])([[:upper:]])/u','\1_\2', $word);
265
		$word = preg_replace('/\-+|\s+/', '_', $word);
266
		$word = downcase($word);
267
268
		return $word;
269
	}
270
271
	/**
272
	 * Capitalizes the first word and turns underscores into spaces and strips a trailing "_id",
273
	 * if any. Like {@link titleize()}, this is meant for creating pretty output.
274
	 *
275
	 * <pre>
276
	 * $this->humanize('employee_salary'); // "Employee salary"
277
	 * $this->humanize('author_id');       // "Author"
278
	 * </pre>
279
	 */
280
	public function humanize(string $lower_case_and_underscored_word): string
281
	{
282
		$result = (string) $lower_case_and_underscored_word;
283
284
		foreach ($this->inflections->humans as $rule => $replacement)
285
		{
286
			$result = preg_replace($rule, $replacement, $result, 1, $count);
287
288
			if ($count) break;
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
289
		}
290
291
		$acronyms = $this->inflections->acronyms;
292
293
		$result = preg_replace('/_id$/', "", $result);
294
		$result = strtr($result, '_', ' ');
295
		$result = preg_replace_callback('/([[:alnum:]]+)/u', function(array $matches) use($acronyms): string {
296
297
			list($m) = $matches;
298
299
			return !empty($acronyms[$m]) ? $acronyms[$m] : downcase($m);
300
301
		}, $result);
302
303
		$result = preg_replace_callback('/^[[:lower:]]/u', function(array $matches): string {
304
305
			return upcase($matches[0]);
306
307
		}, $result);
308
309
		return $result;
310
	}
311
312
	/**
313
	 * Capitalizes all the words and replaces some characters in the string to create a nicer
314
	 * looking title. {@link titleize()} is meant for creating pretty output. It is not used in
315
	 * the Rails internals.
316
	 *
317
	 * <pre>
318
	 * $this->titleize('man from the boondocks');  // "Man From The Boondocks"
319
	 * $this->titleize('x-men: the last stand');   // "X Men: The Last Stand"
320
	 * $this->titleize('TheManWithoutAPast');      // "The Man Without A Past"
321
	 * $this->titleize('raiders_of_the_lost_ark'); // "Raiders Of The Lost Ark"
322
	 * </pre>
323
	 */
324
	public function titleize(string $str): string
325
	{
326
		$str = $this->underscore($str);
327
		$str = $this->humanize($str);
328
329
		$str = preg_replace_callback('/\b(?<![\'’`])[[:lower:]]/u', function(array $matches): string {
330
331
			return upcase($matches[0]);
332
333
		}, $str);
334
335
		return $str;
336
	}
337
338
	/**
339
	 * Replaces underscores with dashes in the string.
340
	 *
341
	 * <pre>
342
	 * $this->dasherize('puni_puni'); // "puni-puni"
343
	 * </pre>
344
	 */
345
	public function dasherize(string $underscored_word): string
346
	{
347
		return strtr($underscored_word, '_', '-');
348
	}
349
350
	/**
351
	 * Makes an hyphenated, lowercase form from the expression in the string.
352
	 *
353
	 * This is a combination of {@link underscore} and {@link dasherize}.
354
	 */
355
	public function hyphenate(string $str): string
356
	{
357
		return $this->dasherize($this->underscore($str));
358
	}
359
360
	/**
361
	 * Returns the suffix that should be added to a number to denote the position in an ordered
362
	 * sequence such as 1st, 2nd, 3rd, 4th.
363
	 *
364
	 * <pre>
365
	 * $this->ordinal(1);     // "st"
366
	 * $this->ordinal(2);     // "nd"
367
	 * $this->ordinal(1002);  // "nd"
368
	 * $this->ordinal(1003);  // "rd"
369
	 * $this->ordinal(-11);   // "th"
370
	 * $this->ordinal(-1021); // "st"
371
	 * </pre>
372
	 */
373
	public function ordinal(int $number): string
374
	{
375
		$abs_number = abs($number);
376
377
		if (($abs_number % 100) > 10 && ($abs_number % 100) < 14)
378
		{
379
			return 'th';
380
		}
381
382
		switch ($abs_number % 10)
383
		{
384
			case 1; return "st";
385
			case 2; return "nd";
386
			case 3; return "rd";
387
			default: return "th";
388
		}
389
	}
390
391
	/**
392
	 * Turns a number into an ordinal string used to denote the position in an ordered sequence
393
	 * such as 1st, 2nd, 3rd, 4th.
394
	 *
395
	 * <pre>
396
	 * $this->ordinalize(1);     // "1st"
397
	 * $this->ordinalize(2);     // "2nd"
398
	 * $this->ordinalize(1002);  // "1002nd"
399
	 * $this->ordinalize(1003);  // "1003rd"
400
	 * $this->ordinalize(-11);   // "-11th"
401
	 * $this->ordinalize(-1021); // "-1021st"
402
	 * </pre>
403
	 */
404
	public function ordinalize(int $number): string
405
	{
406
		return $number . $this->ordinal($number);
407
	}
408
409
	/**
410
	 * Returns true if the word is uncountable, false otherwise.
411
	 *
412
	 * <pre>
413
	 * $this->is_uncountable('advice');    // true
414
	 * $this->is_uncountable('weather');   // true
415
	 * $this->is_uncountable('cat');       // false
416
	 * </pre>
417
	 */
418
	public function is_uncountable(string $word): bool
419
	{
420
		$rc = (string) $word;
421
422
		return $rc
423
			&& preg_match('/\b[[:word:]]+\Z/u', downcase($rc), $matches)
424
			&& isset($this->inflections->uncountables[$matches[0]]);
425
	}
426
}
427