diff --git a/include/api.php b/include/api.php index 5969ccd5f1..dc5d640487 100644 --- a/include/api.php +++ b/include/api.php @@ -2164,10 +2164,10 @@ function api_statuses_mentions($type) // get last network messages // params - $since_id = $_REQUEST['since_id'] ?? 0; - $max_id = $_REQUEST['max_id'] ?? 0; - $count = $_REQUEST['count'] ?? 20; - $page = $_REQUEST['page'] ?? 1; + $since_id = intval($_REQUEST['since_id'] ?? 0); + $max_id = intval($_REQUEST['max_id'] ?? 0); + $count = intval($_REQUEST['count'] ?? 20); + $page = intval($_REQUEST['page'] ?? 1); $start = max(0, ($page - 1) * $count); diff --git a/mod/message.php b/mod/message.php index 80eac343ed..7a8b133f5b 100644 --- a/mod/message.php +++ b/mod/message.php @@ -385,7 +385,7 @@ function message_content(App $a) * @param int $limit * @return array */ -function get_messages($uid, $start, $limit) +function get_messages(int $uid, int $start, int $limit) { return DBA::toArray(DBA::p('SELECT m.`id`, diff --git a/src/Database/DBA.php b/src/Database/DBA.php index 8b25f617f6..1a7ff3bc54 100644 --- a/src/Database/DBA.php +++ b/src/Database/DBA.php @@ -776,6 +776,18 @@ class DBA return DI::dba()->toArray($stmt, $do_close); } + /** + * Cast field types according to the table definition + * + * @param string $table + * @param array $fields + * @return array casted fields + */ + public static function castFields(string $table, array $fields) + { + return DI::dba()->castFields($table, $fields); + } + /** * Returns the error number of the last query * diff --git a/src/Database/Database.php b/src/Database/Database.php index 951dd3e116..641ebd3894 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -928,6 +928,9 @@ class Database switch ($this->driver) { case self::PDO: $columns = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($stmt->table) && is_array($columns)) { + $columns = $this->castFields($stmt->table, $columns); + } break; case self::MYSQLI: if (get_class($stmt) == 'mysqli_result') { @@ -984,6 +987,8 @@ class Database return false; } + $param = $this->castFields($table, $param); + $table_string = DBA::buildTableString($table); $fields_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], array_keys($param))); @@ -1032,6 +1037,8 @@ class Database return false; } + $param = $this->castFields($table, $param); + $table_string = DBA::buildTableString($table); $fields_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], array_keys($param))); @@ -1444,6 +1451,8 @@ class Database return true; } + $fields = $this->castFields($table, $fields); + $table_string = DBA::buildTableString($table); $condition_string = DBA::buildCondition($condition); @@ -1552,6 +1561,10 @@ class Database $result = $this->p($sql, $condition); + if (($this->driver == self::PDO) && !empty($result) && is_string($table)) { + $result->table = $table; + } + return $result; } @@ -1596,7 +1609,8 @@ class Database $row = $this->fetchFirst($sql, $condition); - return $row['count']; + // Ensure to always return either a "null" or a numeric value + return is_numeric($row['count']) ? (int)$row['count'] : $row['count']; } /** @@ -1625,6 +1639,71 @@ class Database return $data; } + /** + * Cast field types according to the table definition + * + * @param string $table + * @param array $fields + * @return array casted fields + */ + public function castFields(string $table, array $fields) { + // When there is no data, we don't need to do something + if (empty($fields)) { + return $fields; + } + + // We only need to cast fields with PDO + if ($this->driver != self::PDO) { + return $fields; + } + + // We only need to cast when emulating the prepares + if (!$this->connection->getAttribute(PDO::ATTR_EMULATE_PREPARES)) { + return $fields; + } + + $types = []; + + $tables = DBStructure::definition('', false); + if (empty($tables[$table])) { + // When a matching table wasn't found we check if it is a view + $views = View::definition('', false); + if (empty($views[$table])) { + return $fields; + } + + foreach(array_keys($fields) as $field) { + if (!empty($views[$table]['fields'][$field])) { + $viewdef = $views[$table]['fields'][$field]; + if (!empty($tables[$viewdef[0]]['fields'][$viewdef[1]]['type'])) { + $types[$field] = $tables[$viewdef[0]]['fields'][$viewdef[1]]['type']; + } + } + } + } else { + foreach ($tables[$table]['fields'] as $field => $definition) { + $types[$field] = $definition['type']; + } + } + + foreach ($fields as $field => $content) { + if (is_null($content) || empty($types[$field])) { + continue; + } + + if ((substr($types[$field], 0, 7) == 'tinyint') || (substr($types[$field], 0, 8) == 'smallint') || + (substr($types[$field], 0, 9) == 'mediumint') || (substr($types[$field], 0, 3) == 'int') || + (substr($types[$field], 0, 6) == 'bigint') || (substr($types[$field], 0, 7) == 'boolean')) { + $fields[$field] = (int)$content; + } + if ((substr($types[$field], 0, 5) == 'float') || (substr($types[$field], 0, 6) == 'double')) { + $fields[$field] = (float)$content; + } + } + + return $fields; + } + /** * Returns the error number of the last query * diff --git a/src/Model/Item.php b/src/Model/Item.php index 36d96cd07c..9fb35ac5d7 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -233,6 +233,8 @@ class Item return $row; } + $row = DBA::castFields('item', $row); + // ---------------------- Transform item structure data ---------------------- // We prefer the data from the user's contact over the public one @@ -3024,7 +3026,7 @@ class Item return $recipients; } - public static function expire($uid, $days, $network = "", $force = false) + public static function expire(int $uid, int $days, string $network = "", bool $force = false) { if (!$uid || ($days < 1)) { return; diff --git a/src/Model/Tag.php b/src/Model/Tag.php index 0ab4b3a49d..2bdbbc8aca 100644 --- a/src/Model/Tag.php +++ b/src/Model/Tag.php @@ -517,7 +517,7 @@ class Tag * @return array * @throws \Exception */ - public static function setGlobalTrendingHashtags(int $period, $limit = 10) + public static function setGlobalTrendingHashtags(int $period, int $limit = 10) { $tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score` FROM `tag-search-view` @@ -560,7 +560,7 @@ class Tag * @return array * @throws \Exception */ - public static function setLocalTrendingHashtags(int $period, $limit = 10) + public static function setLocalTrendingHashtags(int $period, int $limit = 10) { $tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score` FROM `tag-search-view`