This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace SilverStripe\MSSQL; |
||
4 | |||
5 | use SilverStripe\Core\Config\Configurable; |
||
6 | use SilverStripe\Core\Injector\Injectable; |
||
7 | use SilverStripe\Core\ClassInfo; |
||
8 | use SilverStripe\ORM\ArrayList; |
||
9 | use SilverStripe\ORM\Connect\Database; |
||
10 | use SilverStripe\ORM\DataList; |
||
11 | use SilverStripe\ORM\DB; |
||
12 | use SilverStripe\ORM\DataObject; |
||
13 | use SilverStripe\ORM\PaginatedList; |
||
14 | use SilverStripe\ORM\Queries\SQLSelect; |
||
15 | |||
16 | /** |
||
17 | * Microsoft SQL Server 2008+ connector class. |
||
18 | */ |
||
19 | class MSSQLDatabase extends Database |
||
20 | { |
||
21 | use Configurable; |
||
22 | use Injectable; |
||
23 | |||
24 | /** |
||
25 | * Words that will trigger an error if passed to a SQL Server fulltext search |
||
26 | */ |
||
27 | public static $noiseWords = array('about', '1', 'after', '2', 'all', 'also', '3', 'an', '4', 'and', '5', 'another', '6', 'any', '7', 'are', '8', 'as', '9', 'at', '0', 'be', '$', 'because', 'been', 'before', 'being', 'between', 'both', 'but', 'by', 'came', 'can', 'come', 'could', 'did', 'do', 'does', 'each', 'else', 'for', 'from', 'get', 'got', 'has', 'had', 'he', 'have', 'her', 'here', 'him', 'himself', 'his', 'how', 'if', 'in', 'into', 'is', 'it', 'its', 'just', 'like', 'make', 'many', 'me', 'might', 'more', 'most', 'much', 'must', 'my', 'never', 'no', 'now', 'of', 'on', 'only', 'or', 'other', 'our', 'out', 'over', 're', 'said', 'same', 'see', 'should', 'since', 'so', 'some', 'still', 'such', 'take', 'than', 'that', 'the', 'their', 'them', 'then', 'there', 'these', 'they', 'this', 'those', 'through', 'to', 'too', 'under', 'up', 'use', 'very', 'want', 'was', 'way', 'we', 'well', 'were', 'what', 'when', 'where', 'which', 'while', 'who', 'will', 'with', 'would', 'you', 'your', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'); |
||
28 | |||
29 | /** |
||
30 | * Transactions will work with FreeTDS, but not entirely with sqlsrv driver on Windows with MARS enabled. |
||
31 | * TODO: |
||
32 | * - after the test fails with open transaction, the transaction should be rolled back, |
||
33 | * otherwise other tests will break claiming that transaction is still open. |
||
34 | * - figure out SAVEPOINTS |
||
35 | * - READ ONLY transactions |
||
36 | */ |
||
37 | protected $supportsTransactions = true; |
||
38 | |||
39 | /** |
||
40 | * Cached flag to determine if full-text is enabled. This is set by |
||
41 | * {@link MSSQLDatabase::fullTextEnabled()} |
||
42 | * |
||
43 | * @var boolean |
||
44 | */ |
||
45 | protected $fullTextEnabled = null; |
||
46 | |||
47 | /** |
||
48 | * @var bool |
||
49 | */ |
||
50 | protected $transactionNesting = 0; |
||
51 | |||
52 | /** |
||
53 | * Set the default collation of the MSSQL nvarchar fields that we create. |
||
54 | * We don't apply this to the database as a whole, so that we can use unicode collations. |
||
55 | * |
||
56 | * @param string $collation |
||
57 | */ |
||
58 | public static function set_collation($collation) |
||
59 | { |
||
60 | static::config()->set('collation', $collation); |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * The default collation of the MSSQL nvarchar fields that we create. |
||
65 | * We don't apply this to the database as a whole, so that we can use |
||
66 | * unicode collations. |
||
67 | * |
||
68 | * @return string |
||
69 | */ |
||
70 | public static function get_collation() |
||
71 | { |
||
72 | return static::config()->get('collation'); |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Connect to a MS SQL database. |
||
77 | * @param array $parameters An map of parameters, which should include: |
||
78 | * - server: The server, eg, localhost |
||
79 | * - username: The username to log on with |
||
80 | * - password: The password to log on with |
||
81 | * - database: The database to connect to |
||
82 | * - windowsauthentication: Set to true to use windows authentication |
||
83 | * instead of username/password |
||
84 | */ |
||
85 | public function connect($parameters) |
||
86 | { |
||
87 | parent::connect($parameters); |
||
88 | |||
89 | // Configure the connection |
||
90 | $this->query('SET QUOTED_IDENTIFIER ON'); |
||
91 | $this->query('SET TEXTSIZE 2147483647'); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * Checks whether the current SQL Server version has full-text |
||
96 | * support installed and full-text is enabled for this database. |
||
97 | * |
||
98 | * @return boolean |
||
99 | */ |
||
100 | public function fullTextEnabled() |
||
101 | { |
||
102 | if ($this->fullTextEnabled === null) { |
||
103 | $this->fullTextEnabled = $this->updateFullTextEnabled(); |
||
0 ignored issues
–
show
|
|||
104 | } |
||
105 | return $this->fullTextEnabled; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Checks whether the current SQL Server version has full-text |
||
110 | * support installed and full-text is enabled for this database. |
||
111 | * |
||
112 | * @return boolean |
||
113 | */ |
||
114 | protected function updateFullTextEnabled() |
||
115 | { |
||
116 | // Check if installed |
||
117 | $isInstalled = $this->query("SELECT fulltextserviceproperty('isfulltextinstalled')")->value(); |
||
118 | if (!$isInstalled) { |
||
119 | return false; |
||
120 | } |
||
121 | |||
122 | // Check if current database is enabled |
||
123 | $database = $this->getSelectedDatabase(); |
||
124 | $enabledForDb = $this->preparedQuery( |
||
125 | "SELECT is_fulltext_enabled FROM sys.databases WHERE name = ?", |
||
126 | array($database) |
||
127 | )->value(); |
||
128 | return $enabledForDb; |
||
129 | } |
||
130 | |||
131 | public function supportsCollations() |
||
132 | { |
||
133 | return true; |
||
134 | } |
||
135 | |||
136 | public function supportsTimezoneOverride() |
||
137 | { |
||
138 | return true; |
||
139 | } |
||
140 | |||
141 | public function getDatabaseServer() |
||
142 | { |
||
143 | return "sqlsrv"; |
||
144 | } |
||
145 | |||
146 | public function selectDatabase($name, $create = false, $errorLevel = E_USER_ERROR) |
||
147 | { |
||
148 | $this->fullTextEnabled = null; |
||
149 | |||
150 | return parent::selectDatabase($name, $create, $errorLevel); |
||
151 | } |
||
152 | |||
153 | public function clearTable($table) |
||
154 | { |
||
155 | $this->query("TRUNCATE TABLE \"$table\""); |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * SQL Server uses CURRENT_TIMESTAMP for the current date/time. |
||
160 | */ |
||
161 | public function now() |
||
162 | { |
||
163 | return 'CURRENT_TIMESTAMP'; |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Returns the database-specific version of the random() function |
||
168 | */ |
||
169 | public function random() |
||
170 | { |
||
171 | return 'RAND()'; |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * The core search engine configuration. |
||
176 | * Picks up the fulltext-indexed tables from the database and executes search on all of them. |
||
177 | * Results are obtained as ID-ClassName pairs which is later used to reconstruct the DataObjectSet. |
||
178 | * |
||
179 | * @param array $classesToSearch computes all descendants and includes them. Check is done via WHERE clause. |
||
180 | * @param string $keywords Keywords as a space separated string |
||
181 | * @param int $start |
||
182 | * @param int $pageLength |
||
183 | * @param string $sortBy |
||
184 | * @param string $extraFilter |
||
185 | * @param bool $booleanSearch |
||
186 | * @param string $alternativeFileFilter |
||
187 | * @param bool $invertedMatch |
||
188 | * @return PaginatedList DataObjectSet of result pages |
||
189 | */ |
||
190 | public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) |
||
191 | { |
||
192 | $start = (int)$start; |
||
193 | $pageLength = (int)$pageLength; |
||
194 | $results = new ArrayList(); |
||
195 | |||
196 | if (!$this->fullTextEnabled()) { |
||
197 | return new PaginatedList($results); |
||
198 | } |
||
199 | if (!in_array(substr($sortBy, 0, 9), array('"Relevanc', 'Relevance'))) { |
||
200 | user_error("Non-relevance sort not supported.", E_USER_ERROR); |
||
201 | } |
||
202 | |||
203 | $allClassesToSearch = array(); |
||
204 | foreach ($classesToSearch as $class) { |
||
205 | $allClassesToSearch = array_merge($allClassesToSearch, array_values(ClassInfo::dataClassesFor($class))); |
||
206 | } |
||
207 | $allClassesToSearch = array_unique($allClassesToSearch); |
||
208 | |||
209 | //Get a list of all the tables and columns we'll be searching on: |
||
210 | $fulltextColumns = $this->query('EXEC sp_help_fulltext_columns'); |
||
211 | $queries = array(); |
||
212 | |||
213 | // Sort the columns back into tables. |
||
214 | $tables = array(); |
||
215 | foreach ($fulltextColumns as $column) { |
||
216 | // Skip extension tables. |
||
217 | if (substr($column['TABLE_NAME'], -5) == '_Live' || substr($column['TABLE_NAME'], -9) == '_versions') { |
||
218 | continue; |
||
219 | } |
||
220 | |||
221 | // Add the column to table. |
||
222 | $table = &$tables[$column['TABLE_NAME']]; |
||
223 | if (!$table) { |
||
224 | $table = array($column['FULLTEXT_COLUMN_NAME']); |
||
225 | } else { |
||
226 | array_push($table, $column['FULLTEXT_COLUMN_NAME']); |
||
227 | } |
||
228 | } |
||
229 | |||
230 | // Create one query per each table, $columns not used. We want just the ID and the ClassName of the object from this query. |
||
231 | foreach ($tables as $tableName => $columns) { |
||
232 | $class = DataObject::getSchema()->tableClass($tableName); |
||
233 | $join = $this->fullTextSearchMSSQL($tableName, $keywords); |
||
234 | if (!$join) { |
||
235 | return new PaginatedList($results); |
||
236 | } // avoid "Null or empty full-text predicate" |
||
237 | |||
238 | // Check if we need to add ShowInSearch |
||
239 | $where = null; |
||
240 | if ($class === 'SilverStripe\\CMS\\Model\\SiteTree') { |
||
241 | $where = array("\"$tableName\".\"ShowInSearch\"!=0"); |
||
242 | } elseif ($class === 'SilverStripe\\Assets\\File') { |
||
243 | // File.ShowInSearch was added later, keep the database driver backwards compatible |
||
244 | // by checking for its existence first |
||
245 | $fields = $this->getSchemaManager()->fieldList($tableName); |
||
246 | if (array_key_exists('ShowInSearch', $fields)) { |
||
247 | $where = array("\"$tableName\".\"ShowInSearch\"!=0"); |
||
248 | } |
||
249 | } |
||
250 | |||
251 | $queries[$tableName] = DataList::create($class)->where($where)->dataQuery()->query(); |
||
0 ignored issues
–
show
It seems like
$where defined by null on line 239 can also be of type null ; however, SilverStripe\ORM\DataList::where() does only seem to accept string|array|object<Silv...ries\SQLConditionGroup> , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
252 | $queries[$tableName]->setOrderBy(array()); |
||
253 | |||
254 | // Join with CONTAINSTABLE, a full text searcher that includes relevance factor |
||
255 | $queries[$tableName]->setFrom(array("\"$tableName\" INNER JOIN $join AS \"ft\" ON \"$tableName\".\"ID\"=\"ft\".\"KEY\"")); |
||
256 | // Join with the base class if needed, as we want to test agains the ClassName |
||
257 | if ($tableName != $tableName) { |
||
258 | $queries[$tableName]->setFrom("INNER JOIN \"$tableName\" ON \"$tableName\".\"ID\"=\"$tableName\".\"ID\""); |
||
259 | } |
||
260 | |||
261 | $queries[$tableName]->setSelect(array("\"$tableName\".\"ID\"")); |
||
262 | $queries[$tableName]->selectField("'$tableName'", 'Source'); |
||
263 | $queries[$tableName]->selectField('Rank', 'Relevance'); |
||
264 | if ($extraFilter) { |
||
265 | $queries[$tableName]->addWhere($extraFilter); |
||
266 | } |
||
267 | if (count($allClassesToSearch)) { |
||
268 | $classesPlaceholder = DB::placeholders($allClassesToSearch); |
||
269 | $queries[$tableName]->addWhere(array( |
||
270 | "\"$tableName\".\"ClassName\" IN ($classesPlaceholder)" => |
||
271 | $allClassesToSearch |
||
272 | )); |
||
273 | } |
||
274 | // Reset the parameters that would get in the way |
||
275 | } |
||
276 | |||
277 | // Generate SQL |
||
278 | $querySQLs = array(); |
||
279 | $queryParameters = array(); |
||
280 | foreach ($queries as $query) { |
||
281 | /** @var SQLSelect $query */ |
||
282 | $querySQLs[] = $query->sql($parameters); |
||
283 | $queryParameters = array_merge($queryParameters, $parameters); |
||
284 | } |
||
285 | |||
286 | // Unite the SQL |
||
287 | $fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy"; |
||
288 | |||
289 | // Perform the search |
||
290 | $result = $this->preparedQuery($fullQuery, $queryParameters); |
||
291 | |||
292 | // Regenerate DataObjectSet - watch out, numRecords doesn't work on sqlsrv driver on Windows. |
||
293 | $current = -1; |
||
294 | $objects = array(); |
||
295 | foreach ($result as $row) { |
||
296 | $current++; |
||
297 | |||
298 | // Select a subset for paging |
||
299 | if ($current >= $start && $current < $start + $pageLength) { |
||
300 | $objects[] = DataObject::get_by_id($row['Source'], $row['ID']); |
||
301 | } |
||
302 | } |
||
303 | |||
304 | if (isset($objects)) { |
||
305 | $results = new ArrayList($objects); |
||
306 | } else { |
||
307 | $results = new ArrayList(); |
||
308 | } |
||
309 | $list = new PaginatedList($results); |
||
310 | $list->setPageStart($start); |
||
311 | $list->setPageLength($pageLength); |
||
312 | $list->setTotalItems($current+1); |
||
313 | return $list; |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * Allow auto-increment primary key editing on the given table. |
||
318 | * Some databases need to enable this specially. |
||
319 | * |
||
320 | * @param string $table The name of the table to have PK editing allowed on |
||
321 | * @param bool $allow True to start, false to finish |
||
322 | */ |
||
323 | public function allowPrimaryKeyEditing($table, $allow = true) |
||
324 | { |
||
325 | $this->query("SET IDENTITY_INSERT \"$table\" " . ($allow ? "ON" : "OFF")); |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * Returns a SQL fragment for querying a fulltext search index |
||
330 | * |
||
331 | * @param string $tableName specific - table name |
||
332 | * @param string $keywords The search query |
||
333 | * @param array $fields The list of field names to search on, or null to include all |
||
334 | * @return string Clause, or null if keyword set is empty or the string with JOIN clause to be added to SQL query |
||
335 | */ |
||
336 | public function fullTextSearchMSSQL($tableName, $keywords, $fields = null) |
||
337 | { |
||
338 | // Make sure we are getting an array of fields |
||
339 | if (isset($fields) && !is_array($fields)) { |
||
340 | $fields = array($fields); |
||
341 | } |
||
342 | |||
343 | // Strip unfriendly characters, SQLServer "CONTAINS" predicate will crash on & and | and ignore others anyway. |
||
344 | if (function_exists('mb_ereg_replace')) { |
||
345 | $keywords = mb_ereg_replace('[^\w\s]', '', trim($keywords)); |
||
346 | } else { |
||
347 | $keywords = $this->escapeString(str_replace(array('&', '|', '!', '"', '\''), '', trim($keywords))); |
||
348 | } |
||
349 | |||
350 | // Remove stopwords, concat with ANDs |
||
351 | $keywordList = explode(' ', $keywords); |
||
352 | $keywordList = $this->removeStopwords($keywordList); |
||
353 | |||
354 | // remove any empty values from the array |
||
355 | $keywordList = array_filter($keywordList); |
||
356 | if (empty($keywordList)) { |
||
357 | return null; |
||
358 | } |
||
359 | |||
360 | $keywords = implode(' AND ', $keywordList); |
||
361 | if ($fields) { |
||
362 | $fieldNames = '"' . implode('", "', $fields) . '"'; |
||
363 | } else { |
||
364 | $fieldNames = "*"; |
||
365 | } |
||
366 | |||
367 | return "CONTAINSTABLE(\"$tableName\", ($fieldNames), '$keywords')"; |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * Remove stopwords that would kill a MSSQL full-text query |
||
372 | * |
||
373 | * @param array $keywords |
||
374 | * |
||
375 | * @return array $keywords with stopwords removed |
||
376 | */ |
||
377 | public function removeStopwords($keywords) |
||
378 | { |
||
379 | $goodKeywords = array(); |
||
380 | foreach ($keywords as $keyword) { |
||
381 | if (in_array($keyword, self::$noiseWords)) { |
||
382 | continue; |
||
383 | } |
||
384 | $goodKeywords[] = trim($keyword); |
||
385 | } |
||
386 | return $goodKeywords; |
||
387 | } |
||
388 | |||
389 | /** |
||
390 | * Does this database support transactions? |
||
391 | */ |
||
392 | public function supportsTransactions() |
||
393 | { |
||
394 | return $this->supportsTransactions; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * This is a quick lookup to discover if the database supports particular extensions |
||
399 | * Currently, MSSQL supports no extensions |
||
400 | * |
||
401 | * @param array $extensions List of extensions to check for support of. The key of this array |
||
402 | * will be an extension name, and the value the configuration for that extension. This |
||
403 | * could be one of partitions, tablespaces, or clustering |
||
404 | * @return boolean Flag indicating support for all of the above |
||
405 | */ |
||
406 | public function supportsExtensions($extensions = array('partitions', 'tablespaces', 'clustering')) |
||
407 | { |
||
408 | if (isset($extensions['partitions'])) { |
||
409 | return false; |
||
410 | } elseif (isset($extensions['tablespaces'])) { |
||
411 | return false; |
||
412 | } elseif (isset($extensions['clustering'])) { |
||
413 | return false; |
||
414 | } else { |
||
415 | return false; |
||
416 | } |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * Start transaction. READ ONLY not supported. |
||
421 | * |
||
422 | * @param bool $transactionMode |
||
423 | * @param bool $sessionCharacteristics |
||
424 | */ |
||
425 | public function transactionStart($transactionMode = false, $sessionCharacteristics = false) |
||
426 | { |
||
427 | View Code Duplication | if ($this->transactionNesting > 0) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
428 | $this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionNesting); |
||
429 | } elseif ($this->connector instanceof SQLServerConnector) { |
||
430 | $this->connector->transactionStart(); |
||
431 | } else { |
||
432 | $this->query('BEGIN TRANSACTION'); |
||
433 | } |
||
434 | ++$this->transactionNesting; |
||
435 | } |
||
436 | |||
437 | public function transactionSavepoint($savepoint) |
||
438 | { |
||
439 | $this->query("SAVE TRANSACTION \"$savepoint\""); |
||
440 | } |
||
441 | |||
442 | public function transactionRollback($savepoint = false) |
||
443 | { |
||
444 | // Named transaction |
||
445 | if ($savepoint) { |
||
446 | $this->query("ROLLBACK TRANSACTION \"$savepoint\""); |
||
447 | return true; |
||
448 | } |
||
449 | |||
450 | // Fail if transaction isn't available |
||
451 | if (!$this->transactionNesting) { |
||
452 | return false; |
||
453 | } |
||
454 | |||
455 | --$this->transactionNesting; |
||
456 | View Code Duplication | if ($this->transactionNesting > 0) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
457 | $this->transactionRollback('NESTEDTRANSACTION' . $this->transactionNesting); |
||
0 ignored issues
–
show
'NESTEDTRANSACTION' . $this->transactionNesting is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
458 | } elseif ($this->connector instanceof SQLServerConnector) { |
||
459 | $this->connector->transactionRollback(); |
||
460 | } else { |
||
461 | $this->query('ROLLBACK TRANSACTION'); |
||
462 | } |
||
463 | return true; |
||
464 | } |
||
465 | |||
466 | public function transactionEnd($chain = false) |
||
467 | { |
||
468 | // Fail if transaction isn't available |
||
469 | if (!$this->transactionNesting) { |
||
470 | return false; |
||
471 | } |
||
472 | --$this->transactionNesting; |
||
473 | View Code Duplication | if ($this->transactionNesting <= 0) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
474 | $this->transactionNesting = 0; |
||
0 ignored issues
–
show
The property
$transactionNesting was declared of type boolean , but 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||
475 | if ($this->connector instanceof SQLServerConnector) { |
||
476 | $this->connector->transactionEnd(); |
||
477 | } else { |
||
478 | $this->query('COMMIT TRANSACTION'); |
||
479 | } |
||
480 | } |
||
481 | return true; |
||
482 | } |
||
483 | |||
484 | /** |
||
485 | * In error condition, set transactionNesting to zero |
||
486 | */ |
||
487 | protected function resetTransactionNesting() |
||
488 | { |
||
489 | $this->transactionNesting = 0; |
||
0 ignored issues
–
show
The property
$transactionNesting was declared of type boolean , but 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||
490 | } |
||
491 | |||
492 | public function query($sql, $errorLevel = E_USER_ERROR) |
||
493 | { |
||
494 | $this->inspectQuery($sql); |
||
495 | return parent::query($sql, $errorLevel); |
||
496 | } |
||
497 | |||
498 | public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) |
||
499 | { |
||
500 | $this->inspectQuery($sql); |
||
501 | return parent::preparedQuery($sql, $parameters, $errorLevel); |
||
502 | } |
||
503 | |||
504 | protected function inspectQuery($sql) |
||
505 | { |
||
506 | // Any DDL discards transactions. |
||
507 | $isDDL = $this->getConnector()->isQueryDDL($sql); |
||
508 | |||
509 | if ($isDDL) { |
||
510 | $this->resetTransactionNesting(); |
||
511 | } |
||
512 | } |
||
513 | |||
514 | public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, $parameterised = false) |
||
515 | { |
||
516 | if ($exact) { |
||
517 | $comp = ($negate) ? '!=' : '='; |
||
518 | } else { |
||
519 | $comp = 'LIKE'; |
||
520 | if ($negate) { |
||
521 | $comp = 'NOT ' . $comp; |
||
522 | } |
||
523 | } |
||
524 | |||
525 | // Field definitions are case insensitive by default, |
||
526 | // change used collation for case sensitive searches. |
||
527 | $collateClause = ''; |
||
528 | if ($caseSensitive === true) { |
||
529 | if (self::get_collation()) { |
||
530 | $collation = preg_replace('/_CI_/', '_CS_', self::get_collation()); |
||
531 | } else { |
||
532 | $collation = 'Latin1_General_CS_AS'; |
||
533 | } |
||
534 | $collateClause = ' COLLATE ' . $collation; |
||
535 | } elseif ($caseSensitive === false) { |
||
536 | if (self::get_collation()) { |
||
537 | $collation = preg_replace('/_CS_/', '_CI_', self::get_collation()); |
||
538 | } else { |
||
539 | $collation = 'Latin1_General_CI_AS'; |
||
540 | } |
||
541 | $collateClause = ' COLLATE ' . $collation; |
||
542 | } |
||
543 | |||
544 | $clause = sprintf("%s %s %s", $field, $comp, $parameterised ? '?' : "'$value'"); |
||
545 | if ($collateClause) { |
||
546 | $clause .= $collateClause; |
||
547 | } |
||
548 | |||
549 | return $clause; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * Function to return an SQL datetime expression for MSSQL |
||
554 | * used for querying a datetime in a certain format |
||
555 | * |
||
556 | * @param string $date to be formated, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' |
||
557 | * @param string $format to be used, supported specifiers: |
||
558 | * %Y = Year (four digits) |
||
559 | * %m = Month (01..12) |
||
560 | * %d = Day (01..31) |
||
561 | * %H = Hour (00..23) |
||
562 | * %i = Minutes (00..59) |
||
563 | * %s = Seconds (00..59) |
||
564 | * %U = unix timestamp, can only be used on it's own |
||
565 | * @return string SQL datetime expression to query for a formatted datetime |
||
566 | */ |
||
567 | public function formattedDatetimeClause($date, $format) |
||
568 | { |
||
569 | preg_match_all('/%(.)/', $format, $matches); |
||
570 | foreach ($matches[1] as $match) { |
||
571 | if (array_search($match, array('Y', 'm', 'd', 'H', 'i', 's', 'U')) === false) { |
||
572 | user_error('formattedDatetimeClause(): unsupported format character %' . $match, E_USER_WARNING); |
||
573 | } |
||
574 | } |
||
575 | |||
576 | View Code Duplication | if (preg_match('/^now$/i', $date)) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
577 | $date = "CURRENT_TIMESTAMP"; |
||
578 | } elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date)) { |
||
579 | $date = "'$date.000'"; |
||
580 | } |
||
581 | |||
582 | if ($format == '%U') { |
||
583 | return "DATEDIFF(s, '1970-01-01 00:00:00', DATEADD(hour, DATEDIFF(hour, GETDATE(), GETUTCDATE()), $date))"; |
||
584 | } |
||
585 | |||
586 | $trans = array( |
||
587 | 'Y' => 'yy', |
||
588 | 'm' => 'mm', |
||
589 | 'd' => 'dd', |
||
590 | 'H' => 'hh', |
||
591 | 'i' => 'mi', |
||
592 | 's' => 'ss', |
||
593 | ); |
||
594 | |||
595 | $strings = array(); |
||
596 | $buffer = $format; |
||
597 | while (strlen($buffer)) { |
||
598 | if (substr($buffer, 0, 1) == '%') { |
||
599 | $f = substr($buffer, 1, 1); |
||
600 | $flen = $f == 'Y' ? 4 : 2; |
||
601 | $strings[] = "RIGHT('0' + CAST(DATEPART({$trans[$f]},$date) AS VARCHAR), $flen)"; |
||
602 | $buffer = substr($buffer, 2); |
||
603 | } else { |
||
604 | $pos = strpos($buffer, '%'); |
||
605 | if ($pos === false) { |
||
606 | $strings[] = $buffer; |
||
607 | $buffer = ''; |
||
608 | } else { |
||
609 | $strings[] = "'".substr($buffer, 0, $pos)."'"; |
||
610 | $buffer = substr($buffer, $pos); |
||
611 | } |
||
612 | } |
||
613 | } |
||
614 | |||
615 | return '(' . implode(' + ', $strings) . ')'; |
||
616 | } |
||
617 | |||
618 | /** |
||
619 | * Function to return an SQL datetime expression for MSSQL. |
||
620 | * used for querying a datetime addition |
||
621 | * |
||
622 | * @param string $date, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' |
||
0 ignored issues
–
show
There is no parameter named
$date, . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not. ![]() |
|||
623 | * @param string $interval to be added, use the format [sign][integer] [qualifier], e.g. -1 Day, +15 minutes, +1 YEAR |
||
624 | * supported qualifiers: |
||
625 | * - years |
||
626 | * - months |
||
627 | * - days |
||
628 | * - hours |
||
629 | * - minutes |
||
630 | * - seconds |
||
631 | * This includes the singular forms as well |
||
632 | * @return string SQL datetime expression to query for a datetime (YYYY-MM-DD hh:mm:ss) which is the result of the addition |
||
633 | */ |
||
634 | public function datetimeIntervalClause($date, $interval) |
||
635 | { |
||
636 | $trans = array( |
||
637 | 'year' => 'yy', |
||
638 | 'month' => 'mm', |
||
639 | 'day' => 'dd', |
||
640 | 'hour' => 'hh', |
||
641 | 'minute' => 'mi', |
||
642 | 'second' => 'ss', |
||
643 | ); |
||
644 | |||
645 | $singularinterval = preg_replace('/(year|month|day|hour|minute|second)s/i', '$1', $interval); |
||
646 | |||
647 | if ( |
||
648 | !($params = preg_match('/([-+]\d+) (\w+)/i', $singularinterval, $matches)) || |
||
649 | !isset($trans[strtolower($matches[2])]) |
||
650 | ) { |
||
651 | user_error('datetimeIntervalClause(): invalid interval ' . $interval, E_USER_WARNING); |
||
652 | } |
||
653 | |||
654 | View Code Duplication | if (preg_match('/^now$/i', $date)) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
655 | $date = "CURRENT_TIMESTAMP"; |
||
656 | } elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date)) { |
||
657 | $date = "'$date'"; |
||
658 | } |
||
659 | |||
660 | return "CONVERT(VARCHAR, DATEADD(" . $trans[strtolower($matches[2])] . ", " . (int)$matches[1] . ", $date), 120)"; |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Function to return an SQL datetime expression for MSSQL. |
||
665 | * used for querying a datetime substraction |
||
666 | * |
||
667 | * @param string $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' |
||
0 ignored issues
–
show
There is no parameter named
$date1, . Did you maybe mean $date1 ?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit. Consider the following example. The parameter /**
* @param array $germany
* @param array $ireland
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was changed, but the annotation was not. ![]() |
|||
668 | * @param string $date2 to be substracted of $date1, can be either 'now', literal datetime like '1973-10-14 10:30:00' or field name, e.g. '"SiteTree"."Created"' |
||
669 | * @return string SQL datetime expression to query for the interval between $date1 and $date2 in seconds which is the result of the substraction |
||
670 | */ |
||
671 | public function datetimeDifferenceClause($date1, $date2) |
||
672 | { |
||
673 | View Code Duplication | if (preg_match('/^now$/i', $date1)) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
674 | $date1 = "CURRENT_TIMESTAMP"; |
||
675 | } elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date1)) { |
||
676 | $date1 = "'$date1'"; |
||
677 | } |
||
678 | |||
679 | View Code Duplication | if (preg_match('/^now$/i', $date2)) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
680 | $date2 = "CURRENT_TIMESTAMP"; |
||
681 | } elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i', $date2)) { |
||
682 | $date2 = "'$date2'"; |
||
683 | } |
||
684 | |||
685 | return "DATEDIFF(s, $date2, $date1)"; |
||
686 | } |
||
687 | } |
||
688 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.