diff options
Diffstat (limited to 'inc')
-rw-r--r-- | inc/database.inc.php | 94 | ||||
-rw-r--r-- | inc/download.inc.php | 12 | ||||
-rw-r--r-- | inc/property.inc.php | 8 | ||||
-rw-r--r-- | inc/util.inc.php | 49 |
4 files changed, 137 insertions, 26 deletions
diff --git a/inc/database.inc.php b/inc/database.inc.php index 4a5821f4..ff98f5ee 100644 --- a/inc/database.inc.php +++ b/inc/database.inc.php @@ -132,4 +132,98 @@ class Database return self::$dbh->prepare($query); } + /** + * Insert row into table, returning the generated key. + * This requires the table to have an AUTO_INCREMENT column and + * usually requires the given $uniqueValues to span across a UNIQUE index. + * The code first tries to SELECT the key for the given values without + * inserting first. This means this function is best used for cases + * where you expect that the entry already exists in the table, so + * only one SELECT will run. For all the entries that do not exist, + * an INSERT or INSERT IGNORE is run, depending on whether $additionalValues + * is empty or not. Another reason we don't run the INSERT (IGNORE) first + * is that it will increase the AUTO_INCREMENT value on InnoDB, even when + * no INSERT took place. So if you expect a lot of collisions you might + * use this function to prevent your A_I value from counting up too + * quickly. + * Other than that, this is just a dumb version of running INSERT and then + * getting the LAST_INSERT_ID(), or doing a query for the existing ID in + * case of a key collision. + * + * @param string $table table to insert into + * @param string $aiKey name of the AUTO_INCREMENT column + * @param array $uniqueValues assoc array containing columnName => value mapping + * @param array $additionalValues assoc array containing columnName => value mapping + * @return int[] list of AUTO_INCREMENT values matching the list of $values + */ + public static function insertIgnore($table, $aiKey, $uniqueValues, $additionalValues = false) + { + // Sanity checks + if (array_key_exists($aiKey, $uniqueValues)) { + Util::traceError("$aiKey must not be in \$uniqueValues"); + } + if (is_array($additionalValues) && array_key_exists($aiKey, $additionalValues)) { + Util::traceError("$aiKey must not be in \$additionalValues"); + } + // Simple SELECT first + $selectSql = 'SELECT ' . $aiKey . ' FROM ' . $table . ' WHERE 1'; + foreach ($uniqueValues as $key => $value) { + $selectSql .= ' AND ' . $key . ' = :' . $key; + } + $selectSql .= ' LIMIT 1'; + $res = self::queryFirst($selectSql, $uniqueValues); + if ($res !== false) { + // Exists + if (!empty($additionalValues)) { + // Simulate ON DUPLICATE KEY UPDATE ... + $updateSql = 'UPDATE ' . $table . ' SET '; + $first = true; + foreach ($additionalValues as $key => $value) { + if ($first) { + $first = false; + } else { + $updateSql .= ', '; + } + $updateSql .= $key . ' = :' . $key; + } + $updateSql .= ' WHERE ' . $aiKey . ' = :' . $aiKey; + $additionalValues[$aiKey] = $res[$aiKey]; + Database::exec($updateSql, $additionalValues); + } + return $res[$aiKey]; + } + // Does not exist: + if (empty($additionalValues)) { + $combined =& $uniqueValues; + } else { + $combined = $uniqueValues + $additionalValues; + } + // Aight, try INSERT or INSERT IGNORE + $insertSql = 'INTO ' . $table . ' (' . implode(', ', array_keys($combined)) + . ') VALUES (:' . implode(', :', array_keys($combined)) . ')'; + if (empty($additionalValues)) { + // Simple INSERT IGNORE + $insertSql = 'INSERT IGNORE ' . $insertSql; + } else { + // INSERT ... ON DUPLICATE (in case we have a race) + $insertSql = 'INSERT ' . $insertSql . ' ON DUPLICATE KEY UPDATE '; + $first = true; + foreach ($additionalValues as $key => $value) { + if ($first) { + $first = false; + } else { + $insertSql .= ', '; + } + $insertSql .= $key . ' = VALUES(' . $key . ')'; + } + } + self::exec($insertSql, $combined); + // Insert done, retrieve key again + $res = self::queryFirst($selectSql, $uniqueValues); + if ($res === false) { + Util::traceError('Could not find value in table ' . $table . ' that was just inserted'); + } + return $res[$aiKey]; + } + } diff --git a/inc/download.inc.php b/inc/download.inc.php index 51601545..a2054f78 100644 --- a/inc/download.inc.php +++ b/inc/download.inc.php @@ -49,8 +49,8 @@ class Download curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $data = curl_exec($ch); $head = self::getContents($head); - if (preg_match('#^HTTP/\d+\.\d+ (\d+) #', $head, $out)) { - $code = (int) $out[1]; + if (preg_match_all('#^HTTP/\d+\.\d+ (\d+) #m', $head, $out)) { + $code = (int) array_pop($out[1]); } else { $code = 999; } @@ -83,8 +83,8 @@ class Download curl_setopt($ch, CURLOPT_POSTFIELDS, $string); $data = curl_exec($ch); $head = self::getContents($head); - if (preg_match('#^HTTP/\d+\.\d+ (\d+) #', $head, $out)) { - $code = (int) $out[1]; + if (preg_match_all('#^HTTP/\d+\.\d+ (\d+) #m', $head, $out)) { + $code = (int) array_pop($out[1]); } else { $code = 999; } @@ -116,8 +116,8 @@ class Download @unlink($target); return false; } - if (preg_match_all('#\bHTTP/\d+\.\d+ (\d+) #', $head, $out, PREG_SET_ORDER)) { - $code = (int) $out[count($out) - 1][1]; + if (preg_match_all('#^HTTP/\d+\.\d+ (\d+) #m', $head, $out)) { + $code = (int) array_pop($out[1]); } else { $code = '999 ' . curl_error($ch); } diff --git a/inc/property.inc.php b/inc/property.inc.php index b3d8081a..b33e1bff 100644 --- a/inc/property.inc.php +++ b/inc/property.inc.php @@ -146,19 +146,19 @@ class Property public static function getVersionCheckInformation() { - $data = json_decode(self::get('versioncheck-data'), true); - if (isset($data['time']) && $data['time'] + 120 > time()) + $data = json_decode(self::get('versioncheck-data', '[]'), true); + if (isset($data['time']) && $data['time'] + 60 > time()) return $data; $task = Taskmanager::submit('DownloadText', array( 'url' => CONFIG_REMOTE_ML . '/list.php' )); if (!isset($task['id'])) return 'Could not start list download (' . Message::asString() . ')'; - if ($task['statusCode'] !== TASK_FINISHED) { + if (!Taskmanager::isFinished($task)) { $task = Taskmanager::waitComplete($task['id'], 5000); } if ($task['statusCode'] !== TASK_FINISHED || !isset($task['data']['content'])) { - return $task['data']['error']; + return isset($task['data']['error']) ? $task['data']['error'] : 'Timeout'; } $data = json_decode($task['data']['content'], true); $data['time'] = time(); diff --git a/inc/util.inc.php b/inc/util.inc.php index d454d18d..5d1a4563 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -21,6 +21,11 @@ class Util exit(1); } Header('HTTP/1.1 500 Internal Server Error'); + if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === false ) { + Header('Content-Type: text/plain; charset=utf-8'); + echo 'API ERROR: ', $message, "\n", self::formatBacktracePlain(debug_backtrace()); + exit(0); + } Header('Content-Type: text/html; charset=utf-8'); echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><style>', "\n", ".arg { color: red; background: white; }\n", @@ -79,19 +84,38 @@ SADFACE; exit(0); } + private static function formatArgument($arg, $expandArray = true) + { + if (is_string($arg)) { + $arg = "'$arg'"; + } elseif (is_object($arg)) { + $arg = 'instanceof ' . get_class($arg); + } elseif (is_array($arg)) { + if ($expandArray && count($arg) < 20) { + $expanded = ''; + foreach ($arg as $key => $value) { + if (!empty($expanded)) { + $expanded .= ', '; + } + $expanded .= $key . ': ' . self::formatArgument($value, false); + if (strlen($expanded) > 200) + break; + } + if (strlen($expanded) <= 200) + return '[' . $expanded . ']'; + } + $arg = 'Array(' . count($arg) . ')'; + } + return $arg; + } + public static function formatBacktraceHtml($trace, $escape = true) { $output = ''; foreach ($trace as $idx => $line) { $args = array(); foreach ($line['args'] as $arg) { - if (is_string($arg)) { - $arg = "'$arg'"; - } elseif (is_object($arg)) { - $arg = 'instanceof ' . get_class($arg); - } elseif (is_array($arg)) { - $arg = 'Array(' . count($arg) . ')'; - } + $arg = self::formatArgument($arg); $args[] = '<span class="arg">' . htmlspecialchars($arg) . '</span>'; } $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT); @@ -111,14 +135,7 @@ SADFACE; foreach ($trace as $idx => $line) { $args = array(); foreach ($line['args'] as $arg) { - if (is_string($arg)) { - $arg = "'$arg'"; - } elseif (is_object($arg)) { - $arg = 'instanceof ' . get_class($arg); - } elseif (is_array($arg)) { - $arg = 'Array(' . count($arg) . ')'; - } - $args[] = $arg; + $args[] = self::formatArgument($arg); } $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT); $args = implode(', ', $args); @@ -375,7 +392,7 @@ SADFACE; * @param bool $secure true = only use strong random sources * @return string|bool string of requested length, false on error */ - public static function randomBytes($length, $secure) + public static function randomBytes($length, $secure = true) { if (function_exists('random_bytes')) { return random_bytes($length); |