diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e75765b
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+language: php
+php:
+ - "5.5"
+ - "5.4"
+ - "5.3"
+ - "5.2"
+
+services:
+ - mysql
+
+before_script:
+ - ./tests/setup/db-mysql.sh
+ - ./tests/setup/profile.sh travis
+
+script: phpunit --colors tests
+
+branches:
+ only:
+ - testing
diff --git a/FilterHelper.php b/FilterHelper.php
new file mode 100644
index 0000000..8780664
--- /dev/null
+++ b/FilterHelper.php
@@ -0,0 +1,262 @@
+connection = $db_connection;
+ $this->table = $table;
+ }
+
+ public function add($data) { #$name, $db_name = NULL, $method = 'GET', $op = '=', $default = NULL, $force = NULL) {
+ $this->filters[] = $data; #array('name' => $name, 'db-name' => $db_name, $method => 'GET', 'operator' => $op, 'default' => $default, 'force-value' => $force);
+ }
+
+ public function evaluate($source, $prefix = ' WHERE ') {
+ $db_cond = '';
+ $this->source = $source;
+
+ foreach ($this->filters AS $filter) {
+
+ if (isset($filter['conditions'])) {
+ if ($filter['type'] != 'switch') {
+ throw new HttpException(500, NULL, 'Only filters of type "switch" can have conditions');
+ }
+ $conditions = $filter['conditions'];
+ } else {
+ $conditions = array($filter);
+ }
+
+ # if the filter is a switch and it is disabled: skip the entire thing
+ if (self::extractType($filter, $null) == 'switch') {
+ if ($this->extractValue($filter, $value)) {
+ if (!$this->coerceValue($value, 'switch', $filter)) { # returns false for disabled filters
+ continue;
+ }
+ }
+ }
+
+ $cond = $this->evaluateConditions($conditions, $filter);
+ if ($cond) {
+ $db_cond .= ($db_cond ? ' AND ' : '') . $cond;
+ }
+ }
+ return $db_cond ? $prefix . $db_cond : '';
+ }
+
+ public function evaluateJoins() {
+ $table_list = array();
+
+ foreach ($this->filters AS $filter) { # joins are only supported on filter-level
+ if (isset($filter['db-table']) && $filter['db-table'] != $this->table) {
+ if (!isset($filter['join-ref']) || !isset($filter['join-key'])) {
+ throw new HttpException(500, NULL, 'Must specify JOIN key and reference for filters');
+ }
+
+ if (!isset($table_list[$filter['db-table']])) {
+ $table_list[$filter['db-table']][$filter['join-ref']] = $filter['join-key'];
+ } else {
+ $table_list[$filter['db-table']] = array($filter['join-ref'] => $filter['join-key']);
+ }
+ }
+ }
+
+ if (count($table_list) > 0) {
+ array_walk($table_list, array($this, 'extractJoinCondition'));
+ return ' LEFT JOIN ' . implode(' AND ', array_keys($table_list)) . ' ON (' . implode(' AND ', $table_list) . ')';
+ }
+ return '';
+ }
+
+ /*
+ * Table join condition evaluation
+ */
+ function extractJoinCondition(&$arr, $tbl) {
+ $join = '';
+ foreach ($arr AS $ref => $key) {
+ $join .= ($join ? ' AND ' : '') . '`' . $this->table . '`.`' . $ref . '` = `' . $tbl . '`.`' . $key . '`';
+ }
+ $arr = $join;
+ }
+
+ /*
+ * Main conversion method
+ */
+ private function evaluateConditions($conditions, $filter) {
+ $logic = self::extractLogic($conditions);
+
+ $db_cond = '';
+ foreach ($conditions AS $condition) {
+ if (array_key_exists(0, $condition)) { # another array of sub-conditions
+ # obey the value of the switch containing this condition set by reversing it if the switch was turned off
+ if ($this->should_reverse_logic($filter, $filter_value)) {
+ $operator = self::reverseOperator($operator);
+ $logic = self::reverseLogic($logic);
+ }
+
+ $cond = $this->evaluateConditions($condition, $filter);
+ if ($cond) {
+ $db_cond .= ($db_cond ? ' ' . $logic . ' ' : '') . $cond;
+ }
+
+ } else {
+ $data = array_merge(self::FromParams(array('db-name', 'name', 'type', 'default', 'db-table', 'join-key', 'join-on'), $filter), $condition); # collect data
+
+ if (!isset($data['name']) && !isset($data['db-name'])) {
+ throw new HttpException(500, NULL, 'Must specify "name" or "db-name" for filter');
+ }
+ $key = '`' . (isset($data['db-table']) ? $data['db-table'] : $this->table) . '`.`' . (isset($data['db-name']) ? $data['db-name'] : $data['name']) . '`'; # the name is also used as column name if no other is specified
+
+ # Get the value for comparison
+ if (!$this->getValue($data, $value, $null_check)) {
+ continue;
+ }
+
+ if ($null_check === NULL) {
+ $operator = isset($data['operator']) ? $data['operator'] : '=';
+ self::validateOperator($operator);
+
+ # obey the value of the switch containing this condition by reversing it if the switch was turned off
+ if ($this->should_reverse_logic($filter, $filter_value) && $filter_value != $value) {
+ $operator = self::reverseOperator($operator);
+ $logic = self::reverseLogic($logic);
+ }
+
+ $db_cond .= ($db_cond ? ' ' . $logic . ' ' : '') . $key . ' ' . $operator . ' ' . $value;
+ } else {
+ $db_cond .= ($db_cond ? ' ' . $logic . ' ' : '') . $key . ' IS ' . (($null_check xor $value == 'TRUE') ? 'NOT ' : '') . 'NULL';
+ }
+ }
+ }
+ return $db_cond ? '(' . $db_cond . ')' : $db_cond;
+ }
+
+ private function should_reverse_logic($filter, &$filter_value) {
+ return $this->getValue($filter, $filter_value, $filter_null)
+ && $this->extractType($filter, $filter_null) == 'switch'
+ && !$filter_null
+ && $filter_value == 'FALSE';
+ }
+
+ /*
+ * Logic handling code
+ */
+ private static $logic_map = array('AND' => 'OR', 'OR' => 'AND');
+
+ private static function extractLogic(&$conditions) {
+ $logic = isset($conditions['logic']) ? strtoupper($conditions['logic']) : 'AND'; # default is 'AND'
+ self::validateLogic($logic);
+ unset($conditions['logic']); # avoid iterating through it below
+
+ return $logic;
+ }
+
+ private static function validateLogic($logic) {
+ if (!in_array($logic, array_keys(self::$logic_map))) {
+ throw new HttpException(500, NULL, 'Unsupported filter logic "' . $logic . '"');
+ }
+ }
+
+ private static function reverseLogic($logic) {
+ return self::$logic_map[$logic];
+ }
+
+ /*
+ * Type handling code
+ */
+ private static function extractType($filter, &$null_check) {
+ $null_check = isset($filter['null']) ? $filter['null'] : NULL;
+ return $null_check !== NULL ? 'switch' : (isset($filter['type']) ? $filter['type'] : 'string');
+ }
+
+ /*
+ * Operator handling code
+ */
+ private static $operator_map = array('>' => '<=', '<=' => '>', '<' => '>=', '>=' => '<', '=' => '!=', '!=' => '=', 'REGEXP' => 'NOT REGEXP', 'NOT REGEXP' => 'REGEXP');
+
+ private static function reverseOperator($operator) {
+ return self::$operator_map[$operator];
+ }
+
+ private static function validateOperator($operator) {
+ if (!in_array($operator, array_keys(self::$operator_map))) {
+ throw new HttpException(500, NULL, 'Unsupported filter operator "' . $operator . '"');
+ }
+ }
+
+ /*
+ * Value handling code
+ */
+ private function extractValue($filter, &$value) {
+ if (isset($filter['value'])) { # a value has explicitly been specified
+ $value = $filter['value'];
+ } else if (isset($filter['name'])) { # a name to look for in the parameters (GET or POST) has been specified
+ if (isset($this->source[$filter['name']])) {
+ $value = $this->source[$filter['name']];
+ } else if (isset($filter['default'])) { # it is not specified in parameters, but a default was provided
+ $value = $filter['default'];
+ } else {
+ return false;
+ }
+ } else { # neither name nor value specified => error
+ throw new HttpException(500, NULL, 'Must specify "name" or "value" for filter');
+ }
+
+ return true;
+ }
+
+ private function getValue($filter, &$value, &$null_check) {
+ $type = $this->extractType($filter, $null_check);
+ return $this->extractValue($filter, $value) AND $this->coerceValue($value, $type, $filter);
+ }
+
+ private function coerceValue(&$value, $type, $filter) {
+ switch ($type) {
+ case 'string': $value = '"' . $this->connection->real_escape_string($value) . '"';
+ break;
+ case 'int': $value = (int)$value;
+ break;
+ case 'bool': $value = $value ? 'TRUE' : 'FALSE';
+ break;
+ case 'binary': $value = 'UNHEX("' . $this->connection->real_escape_string($value) . '")';
+ break;
+ case 'expr': break;
+ case 'custom':
+ if (!isset($filter['coerce']) || !is_callable($filter['coerce'])) {
+ throw new HttpException(500, NULL, 'None or invalid callback for filter value coerce');
+ }
+ $value = call_user_func($filter['coerce'], $value, $this->connection);
+ break;
+ case 'switch':
+ if (in_array($value, array('yes', 'true', 1, '+1'))) {
+ $value = 'TRUE';
+ } else if (in_array($value, array('no', 'false', -1))) {
+ $value = 'FALSE';
+ } else if (in_array($value, array('both', '0'))) {
+ return false;
+ } else {
+ throw new HttpException(400, NULL, 'Invalid value "' . $value . '" for switch specified!');
+ }
+ break;
+ default:
+ throw new HttpException(500, NULL, 'Unsupported filter type "' . $type . '"');
+ }
+ return true;
+ }
+
+ /*
+ * Public function for filtering HTTP params for supported filters
+ */
+ public static function FromParams($filters, $source = NULL) {
+ $source = $source !== NULL ? $source : $_GET;
+ if (!is_array($source)) {
+ throw new HttpException(500, NULL, 'Must provide valid array as filter source');
+ }
+ return array_intersect_key($source, array_flip($filters));
+ }
+}
+?>
\ No newline at end of file
diff --git a/Item.php b/Item.php
index 2bba61d..951852f 100644
--- a/Item.php
+++ b/Item.php
@@ -9,8 +9,8 @@ class Item
public static function getId($name, $version)
{
$db_connection = db_ensure_connection();
- $name = mysql_real_escape_string($name, $db_connection);
- $version = mysql_real_escape_string($version, $db_connection);
+ $name = $db_connection->real_escape_string($name);
+ $version = $db_connection->real_escape_string($version);
$db_cond = "name = '$name'";
if (!$special_version = in_array($version, array("latest", "first")))
@@ -19,19 +19,19 @@ public static function getId($name, $version)
}
$db_query = 'SELECT HEX(id) AS id, version FROM ' . DB_TABLE_ITEMS . ' WHERE ' . $db_cond;
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
- throw new HttpException(500, NULL, mysql_error());
+ throw new HttpException(500, NULL, $db_connection->error);
}
- if (mysql_num_rows($db_result) < 1)
+ if ($db_result->num_rows < 1)
{
throw new HttpException(404);
}
if (!$special_version)
{
- $db_entry = mysql_fetch_assoc($db_result);
+ $db_entry = $db_result->fetch_assoc();
}
else
{
@@ -46,38 +46,38 @@ public static function getId($name, $version)
public static function get($id, array $cols)
{
$db_connection = db_ensure_connection();
- $id = mysql_real_escape_string($id, $db_connection);
+ $id = $db_connection->real_escape_string($id);
$db_query = 'SELECT ' . implode(', ', $c = array_map('EnwrapColName', $cols)) . ' FROM ' . DB_TABLE_ITEMS . " WHERE `id` = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
- throw new HttpException(500, NULL, mysql_error());
+ throw new HttpException(500, NULL, $db_connection->error);
}
- return mysql_fetch_assoc($db_result);
+ return $db_result->fetch_assoc();
}
public static function existsId($id)
{
$db_connection = db_ensure_connection();
- $id = mysql_real_escape_string($id, $db_connection);
+ $id = $db_connection->real_escape_string($id);
$db_query = "SELECT COUNT(*) FROM " . DB_TABLE_ITEMS . " WHERE id = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
- throw new HttpException(500, NULL, mysql_error());
+ throw new HttpException(500, NULL, $db_connection->error);
}
- $db_entry = mysql_fetch_assoc($db_result);
+ $db_entry = $db_result->fetch_assoc();
return $db_entry["COUNT(*)"] > 0;
}
public static function exists($name, $version = NULL)
{
$db_connection = db_ensure_connection();
- $name = mysql_real_escape_string($name, $db_connection);
- $version = $version == NULL ? NULL : mysql_real_escape_string($version);
+ $name = $db_connection->real_escape_string($name);
+ $version = $version == NULL ? NULL : $db_connection->real_escape_string($version);
$db_cond = "name = '$name'";
if ($version != NULL)
@@ -86,13 +86,13 @@ public static function exists($name, $version = NULL)
}
$db_query = "SELECT COUNT(*) FROM " . DB_TABLE_ITEMS . " WHERE $db_cond";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
- throw new HttpException(500, NULL, mysql_error());
+ throw new HttpException(500, NULL, $db_connection->error);
}
- $db_entry = mysql_fetch_assoc($db_result);
+ $db_entry = $db_result->fetch_assoc();
return $db_entry["COUNT(*)"] > 0;
}
@@ -104,16 +104,16 @@ public static function getUser($name, $version)
public static function getUserForId($id)
{
$db_connection = db_ensure_connection();
- $id = mysql_real_escape_string($id, $db_connection);
+ $id = $db_connection->real_escape_string($id);
$db_query = "SELECT HEX(user) AS user FROM " . DB_TABLE_ITEMS . " WHERE id = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
- throw new HttpException(500, NULL, mysql_error());
+ throw new HttpException(500, NULL, $db_connection->error);
}
- $db_entry = mysql_fetch_assoc($db_result);
+ $db_entry = $db_result->fetch_assoc();
return $db_entry["user"];
}
diff --git a/MySQL/DB_TABLE_CANDIDATES.sql b/MySQL/DB_TABLE_CANDIDATES.sql
new file mode 100644
index 0000000..8d53171
--- /dev/null
+++ b/MySQL/DB_TABLE_CANDIDATES.sql
@@ -0,0 +1,22 @@
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+
+CREATE TABLE IF NOT EXISTS `candidates` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `item` binary(16) NOT NULL,
+ `user` binary(16) NOT NULL,
+ `reason` text NOT NULL,
+ `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `approval` timestamp NULL DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/MySQL/DB_TABLE_CANDIDATE_VOTING.sql b/MySQL/DB_TABLE_CANDIDATE_VOTING.sql
new file mode 100644
index 0000000..67f43aa
--- /dev/null
+++ b/MySQL/DB_TABLE_CANDIDATE_VOTING.sql
@@ -0,0 +1,24 @@
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+
+CREATE TABLE IF NOT EXISTS `candidate_voting` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `candidate` int(11) NOT NULL,
+ `user` binary(16) NOT NULL,
+ `accept` tinyint(1) NOT NULL,
+ `final` tinyint(1) NOT NULL DEFAULT '0',
+ `reason` text NOT NULL,
+ `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `voting` (`candidate`,`user`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/MySQL/DB_TABLE_STDLIB.sql b/MySQL/DB_TABLE_STDLIB.sql
new file mode 100644
index 0000000..4ab4323
--- /dev/null
+++ b/MySQL/DB_TABLE_STDLIB.sql
@@ -0,0 +1,18 @@
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+
+CREATE TABLE IF NOT EXISTS `stdlib` (
+ `release` tinytext NOT NULL,
+ `item` binary(16) NOT NULL,
+ `comment` text NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/MySQL/DB_TABLE_STDLIB_PENDING.sql b/MySQL/DB_TABLE_STDLIB_PENDING.sql
new file mode 100644
index 0000000..8c51892
--- /dev/null
+++ b/MySQL/DB_TABLE_STDLIB_PENDING.sql
@@ -0,0 +1,19 @@
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+
+CREATE TABLE IF NOT EXISTS `stdlib_pending` (
+ `item` binary(16) NOT NULL,
+ `comment` text NOT NULL,
+ `delay` tinytext,
+ PRIMARY KEY (`item`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/MySQL/DB_TABLE_STDLIB_RELEASES.sql b/MySQL/DB_TABLE_STDLIB_RELEASES.sql
new file mode 100644
index 0000000..2224f37
--- /dev/null
+++ b/MySQL/DB_TABLE_STDLIB_RELEASES.sql
@@ -0,0 +1,19 @@
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+
+CREATE TABLE IF NOT EXISTS `stdlib_releases` (
+ `release` tinytext NOT NULL,
+ `date` timestamp NULL DEFAULT NULL,
+ `description` text NOT NULL,
+ `published` tinyint(1) NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/MySQL/code/semver_compare.sql b/MySQL/code/semver_compare.sql
new file mode 100644
index 0000000..8eea44f
--- /dev/null
+++ b/MySQL/code/semver_compare.sql
@@ -0,0 +1,102 @@
+DROP FUNCTION IF EXISTS semver_compare;
+
+DELIMITER //
+
+CREATE FUNCTION semver_compare(version1 varchar(50), version2 varchar(50))
+ RETURNS INT(1)
+ LANGUAGE SQL
+ DETERMINISTIC
+ BEGIN
+ CALL semver_parts(version1, @maj1, @min1, @pat1, @pre1, @build1);
+ CALL semver_parts(version2, @maj2, @min2, @pat2, @pre2, @build2);
+
+ SET @maj1 = CAST(@maj1 AS UNSIGNED), @maj2 = CAST(@maj2 AS UNSIGNED);
+ IF (@maj1 != @maj2) THEN
+ RETURN IF(@maj1 < @maj2, -1, 1);
+ END IF;
+
+ SET @min1 = CAST(@min1 AS UNSIGNED), @min2 = CAST(@min2 AS UNSIGNED);
+ IF (@min1 != @min2) THEN
+ RETURN IF(@min1 < @min2, -1, 1);
+ END IF;
+
+ SET @pat1 = CAST(@pat1 AS UNSIGNED), @pat2 = CAST(@pat2 AS UNSIGNED);
+ IF (@pat1 != @pat2) THEN
+ RETURN IF(@pat1 < @pat2, -1, 1);
+ END IF;
+
+ IF (@pre1 IS NULL AND @pre2 IS NOT NULL) THEN
+ RETURN +1;
+ ELSEIF (@pre1 IS NOT NULL AND @pre2 IS NULL) THEN
+ RETURN -1;
+ ELSEIF (@pre1 IS NOT NULL AND @pre2 IS NOT NULL) THEN
+ -- compare each prerelease part
+ SELECT (LENGTH(@pre1) - LENGTH(REPLACE(@pre1, '.', ''))) / LENGTH('.') + 1 INTO @pre_parts1; -- count the number of parts
+ SELECT (LENGTH(@pre2) - LENGTH(REPLACE(@pre2, '.', ''))) / LENGTH('.') + 1 INTO @pre_parts2;
+ SET @m = 1;
+
+ WHILE (@m <= LEAST(@pre_parts1, @pre_parts2)) DO
+ SET @part1 = TRIM(LEADING CONCAT(SUBSTRING_INDEX(@pre1, '.', @m - 1), '.') FROM SUBSTRING_INDEX(@pre1, '.', @m));
+ SET @part2 = TRIM(LEADING CONCAT(SUBSTRING_INDEX(@pre2, '.', @m - 1), '.') FROM SUBSTRING_INDEX(@pre2, '.', @m));
+
+ IF (@part1 RLIKE '^[[:digit:]]+$' AND @part2 RLIKE '^[[:digit:]]+$') THEN
+ SET @part1 = CAST(@part1 AS SIGNED), @part2 = CAST(@part2 AS SIGNED);
+ IF (@part1 < @part2) THEN
+ RETURN -1;
+ ELSEIF (@part1 > @part2) THEN
+ RETURN +1;
+ END IF;
+ ELSEIF ((@cmp := STRCMP(@part1, @part2)) != 0) THEN
+ RETURN @cmp;
+ END IF;
+
+ SET @m = @m + 1;
+ END WHILE;
+
+ IF (@pre_parts1 < @pre_parts2) THEN -- the longer one wins
+ RETURN -1;
+ ELSEIF (@pre_parts2 > @pre_parts1) THEN
+ RETURN +1;
+ END IF;
+ END IF;
+
+ IF (@build1 IS NULL AND @build2 IS NOT NULL) THEN
+ RETURN -1;
+ ELSEIF (@build1 IS NOT NULL AND @build2 IS NULL) THEN
+ RETURN 1;
+ ELSEIF (@build1 IS NOT NULL AND @build2 IS NOT NULL) THEN
+ -- compare each build part
+ SELECT (LENGTH(@build1) - LENGTH(REPLACE(@build1, '.', ''))) / LENGTH('.') + 1 INTO @build_parts1; -- count the number of parts
+ SELECT (LENGTH(@build2) - LENGTH(REPLACE(@build2, '.', ''))) / LENGTH('.') + 1 INTO @build_parts2;
+ SET @m = 1;
+
+ WHILE (@m <= LEAST(@build_parts1, @build_parts2)) DO
+ SET @part1 = TRIM(LEADING CONCAT(SUBSTRING_INDEX(@build1, '.', @m - 1), '.') FROM SUBSTRING_INDEX(@build1, '.', @m));
+ SET @part2 = TRIM(LEADING CONCAT(SUBSTRING_INDEX(@build2, '.', @m - 1), '.') FROM SUBSTRING_INDEX(@build2, '.', @m));
+
+ IF (@part1 RLIKE '^[[:digit:]]+$' AND @part2 RLIKE '^[[:digit:]]+$') THEN
+ SET @part1 = CAST(@part1 AS SIGNED), @part2 = CAST(@part2 AS SIGNED);
+ IF (@part1 < @part2) THEN
+ RETURN -1;
+ ELSEIF (@part1 > @part2) THEN
+ RETURN +1;
+ END IF;
+ ELSEIF ((@cmp := STRCMP(@part1, @part2)) != 0) THEN
+ RETURN @cmp;
+ END IF;
+
+ SET @m = @m + 1;
+ END WHILE;
+
+ IF (@build_parts1 < @build_parts2) THEN -- the longer one wins
+ RETURN -1;
+ ELSEIF (@build_parts1 > @build_parts2) THEN
+ RETURN +1;
+ END IF;
+ END IF;
+
+ RETURN 0;
+ END
+ //
+
+DELIMITER ;
\ No newline at end of file
diff --git a/MySQL/code/semver_parts.sql b/MySQL/code/semver_parts.sql
new file mode 100644
index 0000000..06f7daa
--- /dev/null
+++ b/MySQL/code/semver_parts.sql
@@ -0,0 +1,21 @@
+DROP PROCEDURE IF EXISTS semver_parts;
+
+DELIMITER //
+
+CREATE PROCEDURE semver_parts(IN version VARCHAR(50), OUT major VARCHAR(50), OUT minor VARCHAR(50), OUT patch VARCHAR(50), OUT prerelease VARCHAR(50), OUT build VARCHAR(50))
+ LANGUAGE SQL
+ DETERMINISTIC
+ BEGIN
+ SET @pre_start = INSTR(version, '-'), @build_start = INSTR(version, '+');
+ SET @end_patch = LEAST(IF(@pre_start = 0, LENGTH(version), @pre_start - 1), IF(@build_start = 0, LENGTH(version), @build_start - 1));
+
+ SELECT SUBSTRING_INDEX(version, '.', 1) INTO major;
+ SELECT TRIM(LEADING CONCAT(major, '.') FROM SUBSTRING_INDEX(version, '.', 2)) INTO minor;
+ SELECT SUBSTRING(version, LOCATE('.', version, @t := LENGTH(CONCAT(major, '.', minor, '.'))) + 1, @end_patch - @t) INTO patch;
+
+ SELECT (CASE @pre_start WHEN 0 THEN NULL ELSE SUBSTRING(version, @pre_start + 1, IF(@build_start = 0, LENGTH(version), @build_start - @pre_start - 1)) END) INTO prerelease;
+ SELECT (CASE @build_start WHEN 0 THEN NULL ELSE SUBSTRING(version, @build_start + 1, LENGTH(version)) END) INTO build;
+ END;
+ //
+
+DELIMITER ;
\ No newline at end of file
diff --git a/MySQL/code/semver_sort.sql b/MySQL/code/semver_sort.sql
new file mode 100644
index 0000000..ce860dd
--- /dev/null
+++ b/MySQL/code/semver_sort.sql
@@ -0,0 +1,36 @@
+DROP PROCEDURE IF EXISTS semver_sort;
+
+DELIMITER //
+
+CREATE PROCEDURE semver_sort()
+ LANGUAGE SQL
+ NOT DETERMINISTIC
+ MODIFIES SQL DATA
+ BEGIN
+ SELECT COUNT(*) INTO @length FROM `semver_index`;
+ SET @gap := @length, @swapped = FALSE;
+
+ WHILE (@gap > 1 || @swapped) DO -- an implementation of the comb sort algorithm (see )
+ IF @gap > 1 THEN
+ SET @gap := FLOOR(@gap / 1.3);
+ END IF;
+
+ SET @i := 1, @swapped := FALSE;
+ WHILE ((@j := @i + @gap) <= @length) DO
+ SELECT `version` INTO @a FROM `semver_index` WHERE `position` = @i; -- read the entries for comparison
+ SELECT `version` INTO @b FROM `semver_index` WHERE `position` = @j;
+
+ IF (semver_compare(@a, @b) > 0) THEN
+ UPDATE `semver_index` SET `position` = -1 WHERE `position` = @i; -- temp item
+ UPDATE `semver_index` SET `position` = @i WHERE `position` = @j;
+ UPDATE `semver_index` SET `position` = @j WHERE `position` = -1;
+
+ SET @swapped := TRUE;
+ END IF;
+ SET @i := @i + 1;
+ END WHILE;
+ END WHILE;
+ END;
+ //
+
+DELIMITER ;
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e776004
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+# ALD
+[](http://travis-ci.org/maul-esel/ALD-API)
+[](http://travis-ci.org/maul-esel/ALD-API)
+
+***ALD*** is short for ***A***utoHotkey ***L***ibrary ***D***istribution. It is a system for management, distribution and installation of libraries and applications written in AutoHotkey.
+This system consists of several parts. The most important part is the HTTP server API. Also, there can be a lot of different clients: websites, desktop apps and more.
+More information on the ALD system can be read up [in the wiki](https://github.com/maul-esel/ALD-API/wiki/The-ALD-model).
+
+## This repo
+The code in this repo is part of the ALD system. It's the HTTP API that is running on an ALD server to handle the libraries and applications stored on that server.
+
+### Get started
+To use the API, you only need to be able to issue standard HTTP requests. Read up the documentation on the API methods in the [wiki](https://github.com/maul-esel/ALD-API/wiki).
+
+Keep in mind that this is still in development, and breaking changes can occur at any time. At the moment, version `0.1.0` is live on `api.libba.net`. To test the current version from your client,
+just issue a `GET` request to `http://api.libba.net/version` (with the appropriate `Accept` header).
+
+Also note that the wiki documentation is slightly out of date and incomplete at the moment.
+
+### Development
+You can watch active development in this repo. For example check the open [issues](https://github.com/maul-esel/ALD-API/issues) and [pull requests](https://github.com/maul-esel/ALD-API/issues), and comment on them if you have something to say.
+If you wish further information, or want to get involved in development, be my guest. Just contact me, and if you want to contribute, fork the repo on github. I can then give you more information on planned features and tasks to complete.
+
+## ALD Clients
+There are several clients for this backend in development. Most importantly, there's [libba.net](http://libba.net), the official website, which presents the data stored in the backend in a user-friendly way.
\ No newline at end of file
diff --git a/SortHelper.php b/SortHelper.php
new file mode 100644
index 0000000..fd5a931
--- /dev/null
+++ b/SortHelper.php
@@ -0,0 +1,64 @@
+ $dir) {
+ if (array_key_exists($key, $allowed)) {
+ $db_order .= ($db_order) ? ', ' : 'ORDER BY ';
+ $db_order .= $allowed[$key] . ' ' . ($dir ? 'ASC' : 'DESC');
+ }
+ # TODO: throw an error here?
+ }
+
+ return $db_order;
+ }
+
+ public static function getListFromParam($param) {
+ $parts = explode(' ', $param);
+
+ $keys = array_map(array('SortHelper', '_cleanupSortKey'), $parts);
+ $dirs = array_map(array('SortHelper', '_getSortDir'), $parts);
+
+ return array_combine($keys, $dirs);
+ }
+
+ static function _cleanupSortKey($k) {
+ return ltrim($k, '!');
+ }
+
+ static function _getSortDir($k) {
+ return substr($k, 0, 1) != '!';
+ }
+
+ public static function PrepareSemverSorting($table, $column, $db_cond = '') {
+ $db_connection = db_ensure_connection();
+ $table = $db_connection->real_escape_string($table);
+ $column = $db_connection->real_escape_string($column);
+
+ $db_query = 'DROP TEMPORARY TABLE IF EXISTS `semver_index`';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ $db_query = 'CREATE TEMPORARY TABLE `semver_index` ('
+ . '`position` int NOT NULL AUTO_INCREMENT PRIMARY KEY,'
+ . '`version` varchar(50) NOT NULL'
+ . ') SELECT DISTINCT `' . $column . '` AS version FROM `' . $table . '` ' . $db_cond;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ $db_query = 'CALL semver_sort()';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/UpdateType.php b/UpdateType.php
new file mode 100644
index 0000000..1c75039
--- /dev/null
+++ b/UpdateType.php
@@ -0,0 +1,129 @@
+ 'major', self::MINOR => 'minor', self::PATCH => 'patch',
+ self::BUILD_INCREASE => 'build-increase', self::PRERELEASE_INCREASE => 'prerelease-increase',
+ self::ADD => 'add', self::REMOVE => 'remove');
+
+ const USAGE_ITEMS = 'items';
+ const USAGE_STDLIB = 'stdlib';
+ const USAGE_STDLIB_RELEASES = 'stdlib_releases';
+
+ private static $usage = array(self::USAGE_ITEMS => array(self::MAJOR, self::MINOR, self::PATCH, self::BUILD_INCREASE, self::PRERELEASE_INCREASE, self::ADD),
+ self::USAGE_STDLIB => array(self::MAJOR, self::MINOR, self::PATCH, self::ADD, self::REMOVE),
+ self::USAGE_STDLIB_RELEASES => array(self::MAJOR, self::MINOR, self::PATCH)); # stdlib/pending/list relies on this not to be changed
+
+ public static function getCode($str, $usage)
+ {
+ $code = array_search(strtolower($str), self::$map);
+ if (!$code)
+ {
+ throw new HttpException(400);
+ }
+
+ if (!isset(self::$usage[$usage]) || array_search($code, self::$usage[$usage]) === FALSE)
+ {
+ throw new HttpException(400);
+ }
+
+ return $code;
+ }
+
+ public static function getName($id)
+ {
+ if (isset(self::$map[$id]))
+ return self::$map[$id];
+ throw new HttpException(500, NULL, 'Unknown update type!');
+ }
+
+ public static function getUpdate($old, $new) {
+ # separate versions
+ $old_parts = array();
+ $new_parts = array();
+ if (!semver_parts($old, $old_parts) || !semver_parts($new, $new_parts)) {
+ throw new HttpException(500);
+ }
+
+ if (semver_compare($old, $new) > -1) { # ensure correct version order
+ throw new HttpException(500);
+ }
+
+ foreach (array('major' => self::MAJOR, 'minor' => self::MINOR, 'patch' => self::PATCH) AS $part => $type) {
+ if ((int)$new_parts[$part] > (int)$old_parts[$part])
+ return $type;
+ }
+
+ foreach (array('prerelease' => self::PRERELEASE_INCREASE, 'build' => self::BUILD_INCREASE) AS $part => $type) {
+ if (!empty($new_parts[$part]) && !empty($old_parts[$part])) { # optional parts
+ $new_fragments = explode('.', $new_parts[$part]);
+ $old_fragments = explode('.', $old_parts[$part]);
+
+ for ($index = 0; $index < min(count($new_fragments), count($old_fragments)); $index++) { # use the smaller amount of parts
+ $new_frag = $new_fragments[$index]; $old_frag = $old_fragments[$index];
+ if (ctype_digit($new_frag) && ctype_digit($old_frag)) {
+ if ((int)$new_frag != (int)$old_frag)
+ return $type;
+ continue;
+ }
+ # at least one is non-numeric: compare by characters (chars > numeric is still ensured)
+ else if ($new_frag < $old_frag || $new_frag > $old_frag)
+ return $type;
+ }
+
+ if (count($new_fragments) != count($old_fragments))
+ return $type;
+ }
+ else if ($type == self::PRERELEASE_INCREASE && empty($new_parts[$part]) && !empty($old_parts[$part])) # no prerelease > any prerelease
+ return $type;
+ else if ($type == self::BUILD_INCREASE && !empty($new_parts[$part]) && empty($old_parts[$part])) # any build > no build
+ return $type;
+ }
+
+ throw new HttpException(500);
+ }
+
+ public static function bumpVersion($base, $type) {
+ $parts = array();
+ if (!semver_parts($base, $parts)) { # split into parts
+ throw new HttpException(500);
+ }
+
+ $reset = array('minor' => 0, 'patch' => 0, 'prerelease' => NULL, 'build' => NULL);
+ switch ($type) {
+ case self::MAJOR: $field = 'major';
+ break;
+ case self::MINOR: $field = 'minor';
+ unset($reset['minor']); # must not reset minor field
+ break;
+ case self::PATCH: $field = 'patch';
+ unset($reset['minor']); # must not reset minor field
+ unset($reset['patch']); # must not reset patch field
+ break;
+ default:
+ throw new HttpException(500); # bumping other parts is not supported
+ break;
+ }
+
+ $parts[$field]++;
+ foreach ($reset AS $field => $value) { # reset lower parts to default value
+ $parts[$field] = $value;
+ }
+
+ return semver_string($parts);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/User.php b/User.php
index 00898f3..e08bf52 100644
--- a/User.php
+++ b/User.php
@@ -8,9 +8,10 @@ class User
const PRIVILEGE_NONE = 0;
const PRIVILEGE_USER_MANAGE = 2;
const PRIVILEGE_REVIEW = 4;
- const PRIVILEGE_DEFAULT_INCLUDE = 8;
+ const PRIVILEGE_STDLIB = 8;
const PRIVILEGE_ADMIN = 16;
const PRIVILEGE_REGISTRATION = 32;
+ const PRIVILEGE_STDLIB_ADMIN = 64;
public static function privilegeToArray($privilege) {
$arr = array();
@@ -24,9 +25,12 @@ public static function privilegeToArray($privilege) {
if (($privilege & self::PRIVILEGE_REVIEW) == self::PRIVILEGE_REVIEW) {
$arr[] = 'review';
}
- if (($privilege & self::PRIVILEGE_DEFAULT_INCLUDE) == self::PRIVILEGE_DEFAULT_INCLUDE) {
+ if (($privilege & self::PRIVILEGE_STDLIB) == self::PRIVILEGE_STDLIB) {
$arr[] = 'stdlib';
}
+ if (($privilege & self::PRIVILEGE_STDLIB_ADMIN) == self::PRIVILEGE_STDLIB_ADMIN) {
+ $arr[] = 'stdlib-admin';
+ }
if (($privilege & self::PRIVILEGE_ADMIN) == self::PRIVILEGE_ADMIN) {
$arr[] = 'admin';
}
@@ -51,7 +55,9 @@ public static function privilegeFromArray($arr) {
break;
case 'review': $privilege |= self::PRIVILEGE_REVIEW;
break;
- case 'stdlib': $privilege |= self::PRIVILEGE_DEFAULT_INCLUDE;
+ case 'stdlib': $privilege |= self::PRIVILEGE_STDLIB;
+ break;
+ case 'stdlib-admin': $privilege |= self::PRIVILEGE_STDLIB_ADMIN;
break;
case 'admin': $privilege |= self::PRIVILEGE_ADMIN;
break;
@@ -69,66 +75,66 @@ public static function hasPrivilegeById($id, $privilege)
{
$db_connection = db_ensure_connection();
- $db_query = "SELECT privileges FROM " . DB_TABLE_USERS . " WHERE id = UNHEX('" . mysql_real_escape_string($id, $db_connection) . "')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT privileges FROM " . DB_TABLE_USERS . " WHERE id = UNHEX('" . $db_connection->real_escape_string($id) . "')";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) != 1)
+ if ($db_result->num_rows != 1)
{
throw new HttpException(404, NULL, "User not found");
}
- $data = mysql_fetch_object($db_result);
- return (((int)$data->privileges) & $privilege) == $privilege;
+ $data = $db_result->fetch_assoc();
+ return (((int)$data['privileges']) & $privilege) == $privilege;
}
public static function hasPrivilege($name, $privilege)
{
$db_connection = db_ensure_connection();
- $db_query = "SELECT privileges FROM " . DB_TABLE_USERS . " WHERE name = '" . mysql_real_escape_string($name, $db_connection) . "'";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT privileges FROM " . DB_TABLE_USERS . " WHERE name = '" . $db_connection->real_escape_string($name) . "'";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) != 1)
+ if ($db_result->num_rows != 1)
{
throw new HttpException(404, NULL, "User not found");
}
- $data = mysql_fetch_object($db_result);
- return (((int)$data->privileges) & $privilege) == $privilege;
+ $data = $db_result->fetch_assoc();
+ return (((int)$data['privileges']) & $privilege) == $privilege;
}
public static function existsName($name)
{
$db_connection = db_ensure_connection();
- $db_query = "SELECT id FROM " . DB_TABLE_USERS . " WHERE name = '" . mysql_real_escape_string($name, $db_connection) . "'";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT id FROM " . DB_TABLE_USERS . " WHERE name = '" . $db_connection->real_escape_string($name) . "'";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- return mysql_num_rows($db_result) == 1;
+ return $db_result->num_rows == 1;
}
public static function existsMail($mail)
{
$db_connection = db_ensure_connection();
- $db_query = "SELECT id FROM " . DB_TABLE_USERS . " WHERE mail = '" . mysql_real_escape_string($mail, $db_connection) . "'";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT id FROM " . DB_TABLE_USERS . " WHERE mail = '" . $db_connection->real_escape_string($mail) . "'";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- return mysql_num_rows($db_result) == 1;
+ return $db_result->num_rows == 1;
}
public static function validateLogin($user, $pw)
@@ -136,22 +142,22 @@ public static function validateLogin($user, $pw)
$db_connection = db_ensure_connection();
$pw = hash("sha256", $pw);
- $escaped_user = mysql_real_escape_string($user, $db_connection);
+ $escaped_user = $db_connection->real_escape_string($user);
$db_query = "SELECT pw FROM " . DB_TABLE_USERS . " WHERE name = '$escaped_user'";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) != 1)
+ if ($db_result->num_rows != 1)
{
throw new HttpException(403, NULL, "User not found");
}
- $data = mysql_fetch_object($db_result);
- if ($data->pw != $pw)
+ $data = $db_result->fetch_assoc();
+ if ($data['pw'] != $pw)
{
throw new HttpException(403, NULL, "Invalid credentials were specified.");
}
@@ -166,16 +172,16 @@ public static function getName($id)
{
$db_connection = db_ensure_connection();
- $db_query = "SELECT name FROM " . DB_TABLE_USERS . " WHERE id = UNHEX('" . mysql_real_escape_string($id, $db_connection) . "')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT name FROM " . DB_TABLE_USERS . " WHERE id = UNHEX('" . $db_connection->real_escape_string($id) . "')";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- while ($data = mysql_fetch_object($db_result))
+ while ($data = $db_result->fetch_assoc())
{
- return $data->name;
+ return $data['name'];
}
throw new HttpException(404, NULL, "User not found");
}
@@ -184,14 +190,14 @@ public static function getID($name)
{
$db_connection = db_ensure_connection();
- $db_query = "SELECT HEX(id) AS id FROM " . DB_TABLE_USERS . " WHERE name = '" . mysql_real_escape_string($name, $db_connection) . "'";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT HEX(id) AS id FROM " . DB_TABLE_USERS . " WHERE name = '" . $db_connection->real_escape_string($name) . "'";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- while ($data = mysql_fetch_assoc($db_result))
+ while ($data = $db_result->fetch_assoc())
{
return $data["id"];
}
@@ -202,14 +208,14 @@ public static function getPrivileges($id)
{
$db_connection = db_ensure_connection();
- $db_query = "SELECT privileges FROM " . DB_TABLE_USERS . " WHERE id = UNHEX('" . mysql_real_escape_string($id, $db_connection) . "')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT privileges FROM " . DB_TABLE_USERS . " WHERE id = UNHEX('" . $db_connection->real_escape_string($id) . "')";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- while ($data = mysql_fetch_assoc($db_result))
+ while ($data = $db_result->fetch_assoc())
{
return $data["privileges"];
}
@@ -218,12 +224,12 @@ public static function getPrivileges($id)
public static function create($name, $mail, $pw) {
$db_connection = db_ensure_connection();
- $name = mysql_real_escape_string($name, $db_connection);
- $mail = mysql_real_escape_string($mail, $db_connection);
+ $name = $db_connection->real_escape_string($name);
+ $mail = $db_connection->real_escape_string($mail);
$pw = hash('sha256', $pw);
$db_query = 'INSERT INTO ' . DB_TABLE_USERS . ' (`id`, `name`, `mail`, `pw`) VALUES (UNHEX(REPLACE(UUID(), "-", "")), "' . $name . '", "' . $mail . '", "' . $pw . '")';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
diff --git a/config/README.md b/config/README.md
new file mode 100644
index 0000000..a242335
--- /dev/null
+++ b/config/README.md
@@ -0,0 +1,6 @@
+# Configuration
+Almost all configuration options available for ALD can be changed in the files in this folder.
+They should be self-explaining or, in most cases, documented.
+
+## Profiles
+The `profiles/` folder contains usage-ready configuration sets, which are used for example for unit testing on travis-ci.org.
\ No newline at end of file
diff --git a/config/database.php b/config/database.php
index 42eefba..c2c82ad 100644
--- a/config/database.php
+++ b/config/database.php
@@ -8,6 +8,13 @@
# the names of the database tables
define('DB_TABLE_ITEMS', 'data'); #
define('DB_TABLE_USERS', 'users'); #
+ define('DB_TABLE_STDLIB', 'stdlib'); #
+ define('DB_TABLE_STDLIB_RELEASES', 'stdlib_releases'); #
+ define('DB_TABLE_STDLIB_PENDING', 'stdlib_pending'); #
+
+ define('DB_TABLE_CANDIDATES', 'candidates'); #
+ define('DB_TABLE_CANDIDATE_VOTING', 'candidate_voting'); #
+
define('DB_TABLE_REGISTRATION', 'registration'); #
define('DB_TABLE_TYPES', 'types'); #
diff --git a/config/profiles/travis/database.php b/config/profiles/travis/database.php
new file mode 100644
index 0000000..2b8d784
--- /dev/null
+++ b/config/profiles/travis/database.php
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/config/profiles/travis/rating.php b/config/profiles/travis/rating.php
new file mode 100644
index 0000000..2028d33
--- /dev/null
+++ b/config/profiles/travis/rating.php
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/config/profiles/travis/registration.php b/config/profiles/travis/registration.php
new file mode 100644
index 0000000..10d210f
--- /dev/null
+++ b/config/profiles/travis/registration.php
@@ -0,0 +1,79 @@
+]{2,25}$/i');
+ # If a user name does not fit this RegEx, the request to initiate a registration is ans-
+ # wered with a '403 - Forbidden' status code.
+ #
+ # Notes:
+ # + When the maximum length is increased, the database fields must be adjusted accordingly.
+ # This affects the 'DB_TABLE_USERS' database table and the 'DB_TABLE_REGISTRATION' table.
+
+ # a list of user names which must not be registered
+ define('FORBIDDEN_USER_NAMES', "");
+ # This is a list of user names which fit the USER_NAME_REGEX, but must still not be registered.
+ # Separate the names by NUL bytes ('\0').
+ #
+ # Notes:
+ # + remember that the '\0' escape sequence requires double quotes
+
+ # a list of user names which can not be registered by the public
+ define('RESERVED_USER_NAMES', "");
+ # The only difference between this and FORBIDDEN_USER_NAMES is that the names
+ # in this list may be registered by users which have the PRIVILEGE_REGISTRATION
+ # but not by anyone else.
+ #
+ # Notes:
+ # + as above, separate the names by '\0' and use double quotes
+
+ # defines whether registration is open to public or not
+ define('PUBLIC_REGISTRATION', true);
+ # If this is set to `true`, any internet user can register himself to the site.
+ # Otherwise, only users who are already registered and have the PRIVILEGE_REGISTRATION
+ # privilege can start a registration. In this case, such a user initiates a registration
+ # with some name, mail and password. The user to be registered receives the mail and can
+ # then complete the registration.
+ #
+ # Notes:
+ # + the password should in this case be included in the mail
+ # + the REGISTRATION_TIMEOUT should be configured in a way which allows cooperation e.g. between different timezones.
+
+ # the subject for the mail sent to the registering user
+ define('REGISTRATION_MAIL_SUBJECT', 'Confirm your registration');
+
+ # the template for the mail to be sent to the registering user
+ define('REGISTRATION_MAIL_TEMPLATE', 'Hi {$NAME},
+
+Welcome to the libba.net!
+You have requested registration with the following data:
+
+ Name: {$NAME}
+ Mail: {$MAIL}
+ (Password not included for security)
+
+To complete your registration, go to libba.net/register/{$ID} and follow the steps described there.
+If you do not follow this step, your registration will expire after some time. You can then start a new registration.
+
+If you did not request registration, you may safely ignore this mail.');
+ # When registering, a mail is sent to the new user to validate he's a human and he owns the specified email address.
+ # This template can contain the following variables:
+ # * {$NAME} - the name to be registered
+ # * {$MAIL} - the mail address the mail is sent to
+ # * {$PASSWORD} - the password specified
+ # * {$ID} - the ID of the registration session, required to complete the registration
+
+ # the sender mail address to send registration verification mails from
+ define('REGISTRATION_MAIL_SENDER', '');
+?>
\ No newline at end of file
diff --git a/config/profiles/travis/stdlib.php b/config/profiles/travis/stdlib.php
new file mode 100644
index 0000000..f15027a
--- /dev/null
+++ b/config/profiles/travis/stdlib.php
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/config/profiles/travis/suspensions.php b/config/profiles/travis/suspensions.php
new file mode 100644
index 0000000..8250834
--- /dev/null
+++ b/config/profiles/travis/suspensions.php
@@ -0,0 +1,21 @@
+this page.');
+ # The following variables are available:
+ # * {$USER} - the user's name
+ # * {$ID} - the user's ID
+ # * {$MAIL} - the new mail address
+ # * {$SUSPENSION} - the ID of the created suspension
+?>
\ No newline at end of file
diff --git a/config/profiles/travis/upload.php b/config/profiles/travis/upload.php
new file mode 100644
index 0000000..b1c2289
--- /dev/null
+++ b/config/profiles/travis/upload.php
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/config/stdlib.php b/config/stdlib.php
new file mode 100644
index 0000000..f15027a
--- /dev/null
+++ b/config/stdlib.php
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/db.php b/db.php
index a373016..3b8b9f7 100644
--- a/db.php
+++ b/db.php
@@ -8,30 +8,13 @@ function db_ensure_connection()
if (!$connection)
{
- $connection = mysql_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD);
- if (!$connection)
- {
- throw new HttpException(500);
- }
- if (!mysql_select_db(DB_NAME, $connection))
+ $connection = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
+ if ($connection->connect_error || mysqli_connect_error() || $connection->error)
{
throw new HttpException(500);
}
+ $connection->set_charset('latin1');
}
return $connection;
}
-
- function db_get_enum_column_values($table, $column, &$values)
- {
- $db_connection = db_ensure_connection();
- $db_query = "SHOW COLUMNS IN $table WHERE Field = '" . mysql_real_escape_String($column) . "'";
- $db_result = mysql_query($db_query, $db_connection);
- if (!$db_result)
- {
- return false;
- }
- $data = mysql_fetch_assoc($db_result);
- $values = explode("','",substr($data["Type"],6,-2));
- return true;
- }
?>
\ No newline at end of file
diff --git a/items/ItemType.php b/items/ItemType.php
index 88070bd..00bd1fb 100644
--- a/items/ItemType.php
+++ b/items/ItemType.php
@@ -8,36 +8,36 @@ class ItemType
public static function getCode($name)
{
$db_connection = db_ensure_connection();
- $db_query = 'SELECT code FROM ' . DB_TABLE_TYPES . ' WHERE name = \'' . mysql_real_escape_string($name, $db_connection) . '\'';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = 'SELECT code FROM ' . DB_TABLE_TYPES . ' WHERE name = \'' . $db_connection->real_escape_string($name) . '\'';
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
- throw new HttpException(500, NULL, 'Could not read item type code: ' . mysql_error());
+ throw new HttpException(500, NULL, 'Could not read item type code: ' . $db_connection->error);
}
- if (mysql_num_rows($db_result) < 1)
+ if ($db_result->num_rows < 1)
{
throw new HttpException(400, NULL, "Item type '$name' is not supported!");
}
- $row = mysql_fetch_array($db_result);
+ $row = $db_result->fetch_assoc();
return $row['code'];
}
public static function getName($code)
{
$db_connection = db_ensure_connection();
- $db_query = 'SELECT name FROM ' . DB_TABLE_TYPES . ' WHERE code = \'' . mysql_real_escape_string($code, $db_connection) . '\'';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = 'SELECT name FROM ' . DB_TABLE_TYPES . ' WHERE code = \'' . $db_connection->real_escape_string($code) . '\'';
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
- throw new HttpException(500, NULL, 'Could not read item type name: ' . mysql_error());
+ throw new HttpException(500, NULL, 'Could not read item type name: ' . $db_connection->error);
}
- if (mysql_num_rows($db_result) < 1)
+ if ($db_result->num_rows < 1)
{
throw new HttpException(500, NULL, "Item type '$code' is unknown!");
}
- $row = mysql_fetch_array($db_result);
+ $row = $db_result->fetch_assoc();
return $row['name'];
}
@@ -45,7 +45,7 @@ public static function getAllNames()
{
$db_connection = db_ensure_connection();
$db_query = 'SELECT name FROM ' . DB_TABLE_TYPES;
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE)
{
throw new HttpException(500, NULL, 'Could not read supported item types!');
diff --git a/items/add.php b/items/add.php
index b754c4b..c8d6da7 100644
--- a/items/add.php
+++ b/items/add.php
@@ -56,10 +56,10 @@
###########################################################
# escape data to prevent SQL injection
- $escaped_name = mysql_real_escape_string($pack_name, $db_connection);
- $escaped_version = mysql_real_escape_string($pack_version, $db_connection);
- $escaped_description = mysql_real_escape_string($pack_description, $db_connection);
- $escaped_tags = mysql_real_escape_string($pack_tags, $db_connection);
+ $escaped_name = $db_connection->real_escape_string($pack_name);
+ $escaped_version = $db_connection->real_escape_string($pack_version);
+ $escaped_description = $db_connection->real_escape_string($pack_description);
+ $escaped_tags = $db_connection->real_escape_string($pack_tags);
# check if item type is supported and read the code
$escaped_type = ItemType::getCode($pack_type); # unsupported types throw an exception
@@ -93,7 +93,7 @@
# add the database entry
$db_query = "INSERT INTO " . DB_TABLE_ITEMS . " (id, name, type, version, user, description, tags)
VALUES (UNHEX('$pack_id'), '$escaped_name', '$escaped_type', '$escaped_version', UNHEX('" . User::getID($user) . "'), '$escaped_description', '$escaped_tags')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
unlink(UPLOAD_FOLDER . $pack_id . '.zip');
diff --git a/items/describe.php b/items/describe.php
index cc1f3a5..e56c549 100644
--- a/items/describe.php
+++ b/items/describe.php
@@ -26,7 +26,7 @@
}
else
{
- $id = mysql_real_escape_string($_GET["id"], $db_connection);
+ $id = $db_connection->real_escape_string($_GET["id"], $db_connection);
}
$file = UPLOAD_FOLDER . $id . '.zip';
@@ -37,7 +37,7 @@
}
$db_query = "UPDATE " . DB_TABLE_ITEMS . " Set downloads = downloads + 1 WHERE id = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
@@ -57,16 +57,16 @@
. " WHERE `" . DB_TABLE_ITEMS . "`.`user` = `" . DB_TABLE_USERS . "`.`id` AND `" . DB_TABLE_RATINGS . "`.`item` = `" . DB_TABLE_ITEMS . "`.`id`" # table combination
. " AND `" . DB_TABLE_ITEMS . "`.`id` = UNHEX('$id') AND `reviewed` != '-1'"; # extra criteria
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) != 1)
+ if ($db_result->num_rows != 1)
{
throw new HttpException(404);
}
- $db_entry = mysql_fetch_assoc($db_result);
+ $db_entry = $db_result->fetch_assoc();
$data = read_package($file);
@@ -76,7 +76,6 @@
$output["downloads"] = (int)$db_entry["downloads"];
$output['user'] = array('name' => $db_entry['userName'], 'id' => $db_entry['userID']);
$output["reviewed"] = $db_entry["reviewed"] == 1;
- $output["default"] = $db_entry["default_include"] == 1;
$tag_list = array();
foreach ($data["tags"] AS $tag)
{
diff --git a/items/list.php b/items/list.php
index a8a910a..3791036 100644
--- a/items/list.php
+++ b/items/list.php
@@ -2,12 +2,17 @@
require_once("../modules/HttpException/HttpException.php");
require_once("../db.php");
require_once("../util.php");
+ require_once('../SortHelper.php');
+ require_once('../FilterHelper.php');
require_once("../User.php");
require_once("../Assert.php");
require_once("../modules/semver/semver.php");
require_once('ItemType.php');
require_once('../sql2array.php');
+ # this complicated query ensures items without any ratings are considered to be rated 0
+ define('SQL_QUERY_RATING', '(SELECT CASE WHEN ' . DB_TABLE_ITEMS . '.id IN (SELECT item FROM ' . DB_TABLE_RATINGS . ') THEN (SELECT ROUND(AVG(rating), 1) FROM ' . DB_TABLE_RATINGS . ' WHERE ' . DB_TABLE_RATINGS . '.item = ' . DB_TABLE_ITEMS . '.id) ELSE 0 END)');
+
try
{
Assert::RequestMethod(Assert::REQUEST_METHOD_GET); # only allow GET requests
@@ -19,69 +24,31 @@
$db_connection = db_ensure_connection();
# retrieve conditions for returned data from GET parameters
- $db_cond = "";
$db_having = '';
$db_join = '';
+ $db_join_on = '';
$db_limit = "";
+ $db_order = '';
- if (isset($_GET["type"]))
- {
- $db_cond = "AND type = '" . ItemType::getCode($_GET["type"]) . "'";
- }
- if (isset($_GET["user"]))
- {
- $db_cond .= " AND user = UNHEX('" . User::getID($_GET["user"]) . "')";
- }
- if (isset($_GET["name"]))
- {
- $db_cond .= " AND " . DB_TABLE_ITEMS . ".name = '" . mysql_real_escape_string($_GET["name"], $db_connection) . "'";
- }
- if (isset($_GET["tags"]))
- {
- $db_cond .= " AND tags REGEXP '(^|;)" . mysql_real_escape_string($_GET["tags"], $db_connection) . "($|;)'";
- }
+ $filter = new FilterHelper($db_connection, DB_TABLE_ITEMS);
- # items in or not in the stdlib
- # ================================ #
- if (isset($_GET["stdlib"]) && in_array(strtolower($_GET["stdlib"]), array("no", "false", "-1")))
- {
- $db_cond .= " AND default_include = '0'";
- }
- else if (isset($_GET["stdlib"]) && in_array(strtolower($_GET["stdlib"]), array("yes", "true", "+1", "1")))
- {
- $db_cond .= " AND default_include = '1'";
- }
- /* else {} */ # default (use "both" or "0") - leave empty so both match
- # ================================ #
+ $filter->add(array('name' => 'type', 'type' => 'custom', 'coerce' => array('ItemType', 'getCode')));
+ $filter->add(array('name' => 'user', 'type' => 'binary')); # WARN: changes parameter to receive ID instead of name
+ $filter->add(array('name' => 'name'));
+ $filter->add(array('name' => 'reviewed', 'type' => 'switch')); # reviewed and unreviewed items
- # reviewed and unreviewed items
- # ================================ #
- if (isset($_GET["reviewed"]) && in_array(strtolower($_GET["reviewed"]), array("no", "false", "-1")))
- {
- $db_cond .= " AND reviewed = '0'";
- }
- else if (isset($_GET["reviewed"]) && in_array(strtolower($_GET["reviewed"]), array("both", "0")))
- {
- $db_cond .= " AND (reviewed = '0' OR reviewed = '1')";
- }
- else # default (use "yes", "true", "+1" or "1")
- {
- $db_cond .= " AND reviewed = '1'";
- }
- # ================================ #
-
- # filter for download count
- if (isset($_GET['downloads'])) {
- $db_cond .= ' AND `downloads` = ' . (int)mysql_real_escape_string($_GET['downloads']);
- } else {
- if (isset($_GET['downloads-min'])) {
- $db_cond .= ' AND `downloads` >= ' . (int)mysql_real_escape_string($_GET['downloads-min']);
- }
- if (isset($_GET['downloads-max'])) {
- $db_cond .= ' AND `downloads` <= ' . (int)mysql_real_escape_string($_GET['downloads-max']);
- }
+ $filter->add(array('name' => 'downloads', 'type' => 'int')); # filter for download count
+ $filter->add(array('name' => 'downloads-min', 'db-name' => 'downloads', 'type' => 'int', 'operator' => '>='));
+ $filter->add(array('name' => 'downloads-max', 'db-name' => 'downloads', 'type' => 'int', 'operator' => '<='));
+
+ $filter->add(array('name' => 'tags', 'operator' => 'REGEXP', 'type' => 'custom', 'coerce' => 'coerce_regex'));
+ function coerce_regex($value, $db_connection) {
+ return '"(^|;)' . $db_connection->real_escape_string($value) . '($|;)"';
}
+ $db_cond = $filter->evaluate($_GET);
+
+ # special filtering (post-MySQL), thus not handled by FilterHelper
if (isset($_GET["version"]))
{
$version = strtolower($_GET["version"]);
@@ -91,23 +58,35 @@
}
}
- # enable rating filters if necessary
- if ($get_rating = isset($_GET['rating']) || isset($_GET['rating-min']) || isset($_GET['rating-max'])) {
- $db_join = 'LEFT JOIN ' . DB_TABLE_RATINGS . ' ON item = id';
+ # retrieve sorting parameters
+ $sort_by_rating = false;
+ if (isset($_GET['sort'])) {
+ $sort_list = SortHelper::getListFromParam($_GET['sort']);
+ $db_order = SortHelper::getOrderClause($sort_list, array('name' => '`name`', 'version' => '`position`', 'uploaded' => '`uploaded`', 'downloads' => '`downloads`', 'rating' => SQL_QUERY_RATING));
+ $sort_by_rating = array_key_exists('rating', $sort_list);
+ if (array_key_exists('version', $sort_list)) {
+ SortHelper::PrepareSemverSorting(DB_TABLE_ITEMS, 'version', $db_cond);
+ $db_join .= ($db_join ? ', ' : 'LEFT JOIN (') . '`semver_index`';
+ $db_join_on .= ($db_join_on ? ' AND ' : ' ON (') . '`' . DB_TABLE_ITEMS . '`.`version` = `semver_index`.`version`';
+ }
+ }
+
+ # enable rating filters if necessary (filter with HAVING instead of WHERE, not currently supported by FilterHelper)
+ if ($get_rating = isset($_GET['rating']) || isset($_GET['rating-min']) || isset($_GET['rating-max']) || $sort_by_rating) {
+ $db_join .= ($db_join ? ', ' : 'LEFT JOIN (') . DB_TABLE_RATINGS;
+ $db_join_on .= ($db_join_on ? ' AND ' : ' ON (') . 'item = id';
- # this complicated query ensures items without any ratings are considered to be rated 0
- $sub_query = '(SELECT CASE WHEN ' . DB_TABLE_ITEMS . '.id IN (SELECT item FROM ' . DB_TABLE_RATINGS . ') THEN (SELECT ROUND(AVG(rating), 1) FROM ' . DB_TABLE_RATINGS . ' WHERE ' . DB_TABLE_RATINGS . '.item = ' . DB_TABLE_ITEMS . '.id) ELSE 0 END)';
if (isset($_GET['rating'])) {
$db_having .= ($db_having) ? ' AND ' : 'HAVING ';
- $db_having .= mysql_real_escape_string($_GET['rating'], $db_connection) . ' = ' . $sub_query;
+ $db_having .= $db_connection->real_escape_string($_GET['rating']) . ' = ' . SQL_QUERY_RATING;
} else {
if (isset($_GET['rating-min'])) {
$db_having .= ($db_having) ? ' AND ' : 'HAVING ';
- $db_having .= mysql_real_escape_string($_GET['rating-min'], $db_connection) . ' <= ' . $sub_query;
+ $db_having .= $db_connection->real_escape_string($_GET['rating-min']) . ' <= ' . SQL_QUERY_RATING;
}
if (isset($_GET['rating-max'])) {
$db_having .= ($db_having) ? ' AND ' : 'HAVING ';
- $db_having .= mysql_real_escape_string($_GET['rating-max'], $db_connection) . ' >= ' . $sub_query;
+ $db_having .= $db_connection->real_escape_string($_GET['rating-max']) . ' >= ' . SQL_QUERY_RATING;
}
}
}
@@ -115,7 +94,7 @@
# retrieve data limits
if (isset($_GET["count"]) && strtolower($_GET["count"]) != "all" && !isset($version)) # if version ("latest" or "first") is set, the data is shortened after being filtered
{
- $db_limit = "LIMIT " . mysql_real_escape_string($_GET["count"], $db_connection);
+ $db_limit = "LIMIT " . $db_connection->real_escape_string($_GET["count"]);
}
if (isset($_GET["start"]) && !isset($version)) # if version ("latest" or "first") is set, the data is shortened after being filtered
{
@@ -123,22 +102,23 @@
{
$db_limit = "LIMIT 18446744073709551615"; # Source: http://dev.mysql.com/doc/refman/5.5/en/select.html
}
- $db_limit .= " OFFSET " . mysql_real_escape_string($_GET["start"], $db_connection);
+ $db_limit .= " OFFSET " . $db_connection->real_escape_string($_GET["start"]);
}
+ $db_join_on .= $db_join_on ? ')' : ''; # clause braces if necessary
+ $db_join .= $db_join ? ')' : ''; # clause braces if necessary
# query data
- $db_query = "SELECT DISTINCT " . DB_TABLE_ITEMS . ".name, type, HEX(" . DB_TABLE_ITEMS . ".id) AS id, version, HEX(" . DB_TABLE_ITEMS . ".user) AS userID, " . DB_TABLE_USERS . ".name AS userName"
- . " FROM " . DB_TABLE_ITEMS . ' ' . $db_join . ', ' . DB_TABLE_USERS
- . " WHERE " . DB_TABLE_ITEMS . ".user = " . DB_TABLE_USERS . ".id $db_cond $db_having $db_limit";
-
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT DISTINCT " . DB_TABLE_ITEMS . ".name, HEX(" . DB_TABLE_ITEMS . ".id) AS id, " . DB_TABLE_ITEMS . '.version'
+ . " FROM " . DB_TABLE_ITEMS . ' ' . $db_join . $db_join_on
+ . " $db_cond $db_having $db_order $db_limit";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
# parse data to array
- $data = sql2array($db_result, 'cleanup_item');
+ $data = sql2array($db_result);
if (isset($version))
{
@@ -190,7 +170,7 @@
$content = "";
foreach ($data AS $item)
{
- $content .= '';
+ $content .= '';
}
$content .= "";
}
@@ -209,13 +189,3 @@
handleHttpException(new HttpException(500, NULL, $e->getMessage()));
}
?>
- $item["userName"], "id" => $item["userID"]);
- unset($item["userName"]);
- unset($item["userID"]);
-
- $item['type'] = ItemType::getName($item['type']);
- return $item;
-}
-?>
diff --git a/items/modify.php b/items/modify.php
index ba1379a..2637774 100644
--- a/items/modify.php
+++ b/items/modify.php
@@ -21,7 +21,7 @@
}
else
{
- $id = mysql_real_escape_string($_GET["id"], $db_connection);
+ $id = $db_connection->real_escape_string($_GET["id"]);
}
if (!empty($_POST["user"]))
@@ -38,11 +38,11 @@
}
$db_query = "UPDATE " . DB_TABLE_ITEMS . " Set user = UNHEX('" . User::getID($_POST["user"]) . "') WHERE id = UNHEX('$id')";
- if (!mysql_query($db_query, $db_connection))
+ if (!$db_connection->query($db_query))
{
throw new HttpException(500);
}
- if (mysql_affected_rows() != 1)
+ if ($db_connection->affected_rows != 1)
{
throw new HttpException(404);
}
@@ -58,33 +58,12 @@
throw new HttpException(400);
}
- $db_query = "UPDATE " . DB_TABLE_ITEMS . " Set reviewed = '" . mysql_real_escape_string($_POST["reviewed"]) . "' WHERE id = UNHEX('$id')";
- if (!mysql_query($db_query, $db_connection))
+ $db_query = "UPDATE " . DB_TABLE_ITEMS . " Set reviewed = '" . $db_connection->real_escape_string($_POST["reviewed"]) . "' WHERE id = UNHEX('$id')";
+ if (!$db_connection->query($db_query))
{
throw new HttpException(500);
}
- if (mysql_affected_rows() != 1)
- {
- throw new HttpException(404);
- }
- }
- if (isset($_POST["default"]))
- {
- if (!User::hasPrivilege($_SERVER["PHP_AUTH_USER"], User::PRIVILEGE_DEFAULT_INCLUDE))
- {
- throw new HttpException(403);
- }
- if (!in_array((int)$_POST["default"], array(0, 1)))
- {
- throw new HttpException(400);
- }
-
- $db_query = "UPDATE " . DB_TABLE_ITEMS . " Set default_include = '" . mysql_real_escape_string($_POST["default"]) . "' WHERE id = UNHEX('$id')";
- if (!mysql_query($db_query, $db_connection))
- {
- throw new HttpException(500);
- }
- if (mysql_affected_rows() != 1)
+ if ($db_connection->affected_rows != 1)
{
throw new HttpException(404);
}
diff --git a/items/rating.php b/items/rating.php
index dbbc0c0..bb463e5 100644
--- a/items/rating.php
+++ b/items/rating.php
@@ -23,7 +23,7 @@
}
else
{
- $id = mysql_real_escape_string($_GET["id"], $db_connection);
+ $id = $db_connection->real_escape_string($_GET["id"]);
}
$request_method = strtoupper($_SERVER['REQUEST_METHOD']);
@@ -31,7 +31,7 @@
Assert::PostParameters("rating");
user_basic_auth("Only registered users can rate items");
- $rating = (int)mysql_real_escape_string($_POST["rating"], $db_connection);
+ $rating = (int)$db_connection->real_escape_string($_POST["rating"]);
if ($rating < 0 || $rating > MAX_RATING)
{
throw new HttpException(400);
@@ -40,13 +40,13 @@
# check if user already voted
$user_id = User::getID($_SERVER["PHP_AUTH_USER"]);
$db_query = "SELECT * FROM " . DB_TABLE_RATINGS . " WHERE user = UNHEX('$user_id') AND item = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) > 0)
+ if ($db_result->num_rows > 0)
{
if (!CAN_UPDATE_RATING) {
throw new HttpException(409, NULL, 'The specified user already rated this item!');
@@ -58,7 +58,7 @@
$db_query = "INSERT INTO " . DB_TABLE_RATINGS . " (user, item, rating) VALUES (UNHEX('$user_id'), UNHEX('$id'), '$rating')"; # insert
}
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
@@ -71,7 +71,7 @@
$content_type = get_preferred_mimetype(array("application/json", "text/xml", "application/xml", "application/x-ald-package"), "application/json");
$db_query = 'SELECT name AS user, rating FROM ' . DB_TABLE_RATINGS . ', ' . DB_TABLE_USERS . ' WHERE item = UNHEX("' . $id . '") AND `user` = `id`';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result) {
throw new HttpException(500);
}
diff --git a/items/tags.php b/items/tags.php
index f48d8c5..ec48f09 100644
--- a/items/tags.php
+++ b/items/tags.php
@@ -14,13 +14,13 @@
$db_connection = db_ensure_connection();
$db_query = 'SELECT DISTINCT tags FROM ' . DB_TABLE_ITEMS;
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result) {
throw new HttpException(500);
}
$tags = array();
- while ($row = mysql_fetch_array($db_result)) {
+ while ($row = $db_result->fetch_assoc()) {
$new_tags = explode(';', $row['tags']);
foreach ($new_tags AS $tag) {
$tags[$tag] = true; # keep tags as keys for simplicity, value is meaningless
diff --git a/modules/semver b/modules/semver
index c9aa299..fd56d92 160000
--- a/modules/semver
+++ b/modules/semver
@@ -1 +1 @@
-Subproject commit c9aa29950d39f652320f4dfc2013074fd15df1ca
+Subproject commit fd56d9265cf87e091e8acc25051cd6edd958d685
diff --git a/sql2array.php b/sql2array.php
index 1aa6b77..08b3239 100644
--- a/sql2array.php
+++ b/sql2array.php
@@ -2,7 +2,7 @@
function sql2array($db_result, $item_handler = NULL) {
$arr = array();
$i = 0;
- while ($db_row = mysql_fetch_assoc($db_result)) {
+ while ($db_row = $db_result->fetch_assoc()) {
$key = $i;
$arr[$key] = $item_handler !== NULL ? (is_array($item_handler) ? call_user_func($item_handler, $db_row, $key) : $item_handler($db_row, $key)) : $db_row;
$i++;
diff --git a/stdlib/.htaccess b/stdlib/.htaccess
new file mode 100644
index 0000000..2d02193
--- /dev/null
+++ b/stdlib/.htaccess
@@ -0,0 +1,2 @@
+RewriteEngine On
+RewriteRule ^items$ items.php [L]
\ No newline at end of file
diff --git a/stdlib/Stdlib.php b/stdlib/Stdlib.php
new file mode 100644
index 0000000..7fe10e6
--- /dev/null
+++ b/stdlib/Stdlib.php
@@ -0,0 +1,145 @@
+real_escape_string($release);
+
+ $db_query = 'SELECT HEX(`item`) AS id, comment FROM ' . DB_TABLE_STDLIB . " WHERE `release` = '$release'";
+ $db_result = $db_connection->query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500);
+ }
+
+ return sql2array($db_result);
+ } else {
+ return self::GetItemsUnpublished($release, StdlibRelease::previousRelease($release, StdlibRelease::PUBLISHED_YES));
+ }
+ }
+
+ private static function GetItemsUnpublished($release, $base) {
+ $old_items = ($base !== NULL) ? self::GetItems($base) : array(); # catch $base = NULL in case there's no previous release
+
+ foreach ($old_items AS &$item) {
+ $item = array_merge($item, Item::get($item['id'], array('name', 'version'))); # get name + version
+ }
+
+ $pending = StdlibPending::GetEntries($release);
+
+ foreach ($pending AS &$entry) {
+ switch ($entry['update']) {
+ case UpdateType::REMOVE:
+ $index = searchSubArray($old_items, array('id' => $entry['id']));
+ if ($index === NULL)
+ throw new HttpException(500);
+ unset($old_items[$index]);
+ break;
+ case UpdateType::ADD:
+ unset($entry['update']);
+ $old_items[] = $entry;
+ break;
+ default:
+ unset($entry['update']);
+ $index = searchSubArray($old_items, array('name' => $entry['name']));
+ if ($index === NULL)
+ throw new HttpException(500);
+ $old_items[$index] = $entry;
+ break;
+ }
+ }
+
+ sort($old_items); # make array continuous
+ return $old_items;
+ }
+
+ public static function diff($old, $new) {
+ $old_items = $old !== NULL ? self::GetItems($old) : array();
+ foreach ($old_items AS &$item) {
+ $item = array_merge($item, Item::get($item['id'], array('name', 'version'))); # get name + version
+ }
+
+ $new_items = self::GetItems($new);
+ foreach ($new_items AS &$item) {
+ $item = array_merge($item, Item::get($item['id'], array('name', 'version'))); # get name + version
+ }
+
+ $diff = array();
+
+ foreach ($new_items AS &$item) {
+ $old_index = searchSubArray($old_items, array('name' => $item['name']));
+
+ if ($old_index === NULL) {
+ $diff[] = array('id' => $item['id'], 'comment' => $item['comment'], 'name' => $item['name'], 'version' => $item['version'], 'update' => UpdateType::ADD);
+ } else if ($old_items[$old_index]['version'] != $item['version']) {
+ $diff[] = array('id' => $item['id'], 'comment' => $item['comment'], 'name' => $item['name'], 'version' => $item['version'], 'update' => UpdateType::getUpdate($old_items[$old_index]['version'], $item['version']));
+ unset($old_items[$old_index]);
+ } else {
+ unset($old_items[$old_index]);
+ }
+ }
+ foreach ($old_items AS $item) {
+ $diff[] = array('id' => $item['id'], 'comment' => 'Removing ' . $item['name'] . ' v' . $item['version'], 'name' => $item['name'], 'version' => $item['version'], 'update' => UpdateType::REMOVE);
+ }
+
+ return $diff;
+ }
+
+ public static function writeEntry($release, $id, $comment) {
+ $db_connection = db_ensure_connection();
+ $release = $db_connection->real_escape_string($release);
+ $id = $db_connection->real_escape_string($id);
+ $comment = $db_connection->real_escape_string($comment);
+
+ $db_query = 'INSERT INTO ' . DB_TABLE_STDLIB . ' (`release`, `item`, `comment`) VALUES ("' . $release . '", UNHEX("' . $id . '"), "' . $comment . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE || $db_connection->affected_rows < 1) {
+ throw new HttpException(500);
+ }
+ }
+
+ public static function releaseHasItem($release, $id) {
+ $db_connection = db_ensure_connection();
+ $release = $db_connection->real_escape_string($release);
+ $id = $db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT * FROM ' . DB_TABLE_STDLIB . ' WHERE `release` = "' . $release . '" AND `item` = UNHEX("' . $id . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ return $db_result->num_rows > 0;
+ }
+
+ public static function cleanup() {
+ $db_connection = db_ensure_connection();
+
+ # ensure not 2x stdlib with same item and release
+ $db_query = 'SELECT `release`, `item` FROM ' . DB_TABLE_STDLIB . ' GROUP BY `release`, `item` HAVING COUNT(*) > 1';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+
+ while ($dup = $db_result->fetch_assoc()) {
+ $db_query = 'DELETE FROM ' . DB_TABLE_STDLIB . ' WHERE `release` = "' . $dup['release'] . '" AND `item` = "' . $dup['item'] . '" LIMIT 1';
+ $db_result2 = $db_connection->query($db_query);
+ if ($db_result2 === FALSE) {
+ throw new HttpException(500);
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/stdlib/StdlibPending.php b/stdlib/StdlibPending.php
new file mode 100644
index 0000000..3b2ecb3
--- /dev/null
+++ b/stdlib/StdlibPending.php
@@ -0,0 +1,189 @@
+query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500);
+ }
+
+ return sql2array($db_result);
+ }
+
+ public static function GetEntries($release) { # $release is not required to exist!
+ $base = StdlibRelease::getVersion(StdlibRelease::SPECIAL_VERSION_LATEST, StdlibRelease::PUBLISHED_YES);
+
+ if ($base !== NULL) {
+ $release_update = UpdateType::getUpdate($base, $release); # get release update type
+ $old_items = Stdlib::GetItems($base); # get items in base
+ } else { # in case there's no previous release
+ $release_update = UpdateType::MAJOR; # this way, the first release can hold any suggested change (though there should only be ADD changes)
+ $old_items = array(); # no release => no previous items
+ }
+
+ foreach ($old_items AS &$item) {
+ $item = array_merge($item, Item::get($item['id'], array('name', 'version'))); # get name + version
+ }
+
+ $libs = self::GetAllEntries(); # get all pending changes
+ $lib_version = array();
+
+ foreach ($libs AS $i => &$lib) {
+ $lib = array_merge($lib, Item::get($lib['id'], array('name', 'version'))); # get info on lib, especially name & version
+
+ # assign the corresponding update types, comparing to the $old_items array
+ #################################################
+ $old = searchSubArray($old_items, array('name' => $lib['name'])); # what version of this item is in the old release?
+ if ($old !== NULL) {
+ if (semver_compare($old_items[$old]['version'], $lib['version']) == 0) { # same version means removal
+ $update_type = UpdateType::REMOVE;
+ } else if (semver_compare($old_items[$old]['version'], $lib['version']) == 1) { # if any of them means a downgrade (old > new), delete the entry
+ if (!STDLIB_ALLOW_DOWNGRADE) {
+ throw new HttpException(500);
+ }
+ $update_type = UpdateType::getUpdate($lib['version'], $old_items[$old]['version']);
+ } else { # actually an update
+ $update_type = UpdateType::getUpdate($old_items[$old]['version'], $lib['version']); # retrieve update type
+ }
+ } else { # not in latest release - must be new
+ $update_type = UpdateType::ADD;
+ }
+ $lib['update'] = $update_type;
+ #################################################
+
+ # filter according to release update type
+ #################################################
+ $delayed = $lib['delay'] !== NULL && semver_compare($release, $lib['delay']) < 0;
+ $include = false;
+ switch ($release_update) {
+ case UpdateType::MAJOR: $include = !$delayed; # everything can go in a major release, just exclude delayed items
+ break;
+ case UpdateType::MINOR: $include = !$delayed && ($update_type == UpdateType::MINOR || $update_type == UpdateType::PATCH);
+ break;
+ case UpdateType::PATCH: $include = !$delayed && ($update_type == UpdateType::PATCH);
+ break;
+ }
+
+ if ($include) {
+ if (!isset($lib_version[$lib['name']]) || semver_compare($lib_version[$lib['name']], $lib['version']) < 0) { # if not duplicate, always take it || if duplicate: higher overwrites lower
+ $lib_version[$lib['name']] = $lib['version'];
+ }
+ } else { # item update type does not fit stdlib release update
+ unset($libs[$i]);
+ }
+ #################################################
+ }
+ return $libs;
+ }
+
+ public static function AddEntry($id, $comment)
+ {
+ $db_connection = db_ensure_connection();
+ $id = $db_connection->real_escape_string($id);
+ $comment = $db_connection->real_escape_string($comment);
+
+ $db_query = 'INSERT INTO ' . DB_TABLE_STDLIB_PENDING . ' (`item`, `comment`) VALUES (UNHEX("' . $id . '"), "' . $comment . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ }
+
+ public static function DeleteEntry($id)
+ {
+ $db_connection = db_ensure_connection();
+ $id = $db_connection->real_escape_string($id);
+
+ $db_query = 'DELETE FROM ' . DB_TABLE_STDLIB_PENDING . " WHERE `item` = UNHEX('$id')";
+ $db_result = $db_connection->query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500);
+ }
+ }
+
+ public static function IsPending($id) {
+ $db_connection = db_ensure_connection();
+ $id = $db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT * FROM ' . DB_TABLE_STDLIB_PENDING . ' WHERE `item` = UNHEX("' . $id . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ return $db_result->num_rows > 0;
+ }
+
+ public static function SetComment($id, $comment) {
+ $db_connection = db_ensure_connection();
+ $id = $db_connection->real_escape_string($id);
+ $comment = $db_connection->real_escape_string($comment);
+
+ $db_query = 'UPDATE ' . DB_TABLE_STDLIB_PENDING . ' SET `comment` = "' . $comment . '" WHERE `item` = UNHEX("' . $id . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE || $db_connection->affected_rows < 1) {
+ throw new HttpException(500);
+ }
+ }
+
+ public static function SetDelay($id, $delay = NULL) {
+ $db_connection = db_ensure_connection();
+ $id = $db_connection->real_escape_string($id);
+ $delay = ($delay !== NULL) ? '"' . $db_connection->real_escape_string($delay) . '"' : 'NULL';
+
+ $db_query = 'UPDATE ' . DB_TABLE_STDLIB_PENDING . ' SET `delay` = ' . $delay . ' WHERE `item` = UNHEX("' . $id . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE || $db_connection->affected_rows < 1) {
+ throw new HttpException(500);
+ }
+ }
+
+ public static function cleanup() {
+ $latest_release = StdlibRelease::getVersion(StdlibRelease::SPECIAL_VERSION_LATEST, StdlibRelease::PUBLISHED_YES);
+ if ($latest_release === NULL) {
+ return; # we can't do any cleanup right now. When the TODOs below are implemented, they can be executed regardless of this.
+ }
+
+ $live_items = array_map(array('StdlibPending', 'sanitize_items'), Stdlib::GetItems($latest_release));
+ $pending = array_map(array('StdlibPending', 'sanitize_items'), self::GetAllEntries());
+
+ # ensure there are no pending entries < live entries if downgrades not allowed
+ if (!STDLIB_ALLOW_DOWNGRADE) {
+ $pending_copy = $pending;
+
+ foreach ($live_items AS $item) {
+ while (($i = searchSubArray($pending_copy, array('name' => $item['name']))) !== NULL) {
+ if (semver_compare($item['version'], $pending_copy[$i]['version']) > 0) { # if the live version is > the pending (and no downgrades allowed, see above)
+ self::DeleteEntry($pending_copy[$i]['id']); # delete the pending downgrade
+ }
+ unset($pending_copy[$i]);
+ }
+ }
+ }
+
+ # todo: ensure not removal + addition of the same item pending >> delete both
+ # todo: ensure not removal + addition of different versions of the same item pending >> make upgrade
+ }
+
+ static function sanitize_items($item) {
+ return array_merge($item, Item::get($item['id'], array('name', 'version')));
+ }
+}
+?>
\ No newline at end of file
diff --git a/stdlib/candidates/.htaccess b/stdlib/candidates/.htaccess
new file mode 100644
index 0000000..bf32070
--- /dev/null
+++ b/stdlib/candidates/.htaccess
@@ -0,0 +1,15 @@
+Options -Multiviews
+
+RewriteEngine On
+
+RewriteRule ^list$ list.php [L]
+
+RewriteRule ^(approve|describe)/(\d+)$ $1.php?id=$2 [L]
+RewriteRule ^create/([0-9a-fA-F]{32})$ create.php?id=$1 [L]
+RewriteRule ^create/(.+)/(.+)$ create.php?name=$1&version=$2 [L]
+
+RewriteCond %{REQUEST_METHOD} =POST [NC]
+RewriteRule ^(reject|accept)/(\d+)$ voting.php?id=$2&mode=$1 [L]
+
+RewriteCond %{REQUEST_METHOD} =GET [NC]
+RewriteRule ^voting/(\d+)$ voting.php?id=$1 [L,QSA]
\ No newline at end of file
diff --git a/stdlib/candidates/Candidate.php b/stdlib/candidates/Candidate.php
new file mode 100644
index 0000000..b758a19
--- /dev/null
+++ b/stdlib/candidates/Candidate.php
@@ -0,0 +1,260 @@
+real_escape_string($item);
+ $user = $db_connection->real_escape_string($user);
+ $reason = $db_connection->real_escape_string($reason);
+ $deletion = $deletion ? '0' : 'NULL';
+
+ $db_query = 'INSERT INTO ' . DB_TABLE_CANDIDATES . ' (`item`, `user`, `reason`, `approval`) VALUES (UNHEX("' . $item . '"), UNHEX("' . $user . '"), "' . $reason . '", ' . $deletion . ')';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ return $db_connection->insert_id;
+ }
+
+ public static function describe($id) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT *, HEX(`item`) AS item, HEX(`user`) AS user FROM ' . DB_TABLE_CANDIDATES . ' WHERE `id` = ' . $id;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ if ($db_result->num_rows < 1) {
+ throw new HttpException(404);
+ }
+
+ return $db_result->fetch_assoc();
+ }
+
+ public static function exists($id) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT * FROM ' . DB_TABLE_CANDIDATES . ' WHERE `id` = ' . $id;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ return $db_result->num_rows > 0;
+ }
+
+ public static function existsItem($item) {
+ $db_connection = db_ensure_connection();
+ $item = $db_connection->real_escape_string($item);
+
+ $db_query = 'SELECT * FROM ' . DB_TABLE_CANDIDATES . ' WHERE `item` = UNHEX("' . $item . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ return $db_result->num_rows > 0;
+ }
+
+ public static function accepted($id) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT COUNT(*) AS count, `final`, `accept` FROM ' . DB_TABLE_CANDIDATE_VOTING . ' WHERE `candidate` = ' . $id . ' GROUP BY `final`, `accept`';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ $accept = array();
+ while ($row = $db_result->fetch_assoc()) {
+ if ($row['final'])
+ return (bool)$row['accept']; # final decisions overwrite everything else (there must only be one of them)
+ $accept[$row['accept']] = $row['count'];
+ }
+ if (CANDIDATE_ALWAYS_REQUIRE_FINAL)
+ return NULL; # if there had been a final, it would already have exited the loop
+
+ if ($accept[true] >= CANDIDATE_MIN_ACCEPTS && $accept[false] == 0 && !CANDIDATE_ACCEPT_REQUIRE_FINAL)
+ return true; # accepted based on the current (non-final) accepts
+ else if ($accept[false] >= CANDIDATE_MIN_REJECTS && $accept[true] == 0 && !CANDIDATE_REJECT_REQUIRE_FINAL)
+ return false; # rejected based on the current (no-final) rejects
+
+ return NULL; # must still be open
+ }
+
+ public static function getId($item) {
+ $db_connection = db_ensure_connection();
+ $item = $db_connection->real_escape_string($item);
+
+ $db_query = 'SELECT `id` FROM ' . DB_TABLE_CANDIDATES . ' WHERE `item` = UNHEX("' . $item . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ if ($db_result->num_rows < 1) {
+ throw new HttpException(404);
+ }
+
+ $t = $db_result->fetch_assoc();
+ return $t['id'];
+ }
+
+ public static function approve($id) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+
+ $db_query = 'UPDATE ' . DB_TABLE_CANDIDATES . ' SET `approval` = NOW() WHERE `approval` IS NULL AND `id` = ' . $id;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ if ($db_connection->affected_rows < 1) {
+ throw new HttpException(400);
+ }
+ }
+
+ public static function isApproved($id) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT (`approval` IS NOT NULL) AS approved FROM ' . DB_TABLE_CANDIDATES . ' WHERE `id` = ' . $id;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ if ($db_result->num_rows < 1) {
+ throw new HttpException(404);
+ }
+
+ $t = $db_result->fetch_assoc();
+ return $t['approved'];
+ }
+
+ public static function getUser($id) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT HEX(`user`) AS user FROM ' . DB_TABLE_CANDIDATES . ' WHERE `id` = ' . $id;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ if ($db_result->num_rows < 1) {
+ throw new HttpException(404);
+ }
+
+ $t = $db_result->fetch_assoc();
+ return $t['user'];
+ }
+
+ public static function getItem($id) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+
+ $db_query = 'SELECT HEX(`item`) AS item FROM ' . DB_TABLE_CANDIDATES . ' WHERE `id` = ' . $id;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ if ($db_result->num_rows < 1) {
+ throw new HttpException(404);
+ }
+
+ $t = $db_result->fetch_assoc();
+ return $t['item'];
+ }
+
+ public static function listCandidates($filters = array(), $sort = array()) {
+ if (!is_array($filters)) {
+ throw new Exception('Must provide a valid array as candidate filter');
+ }
+ if (!is_array($sort)) {
+ throw new Exception('Must provide a valid array for candidate sorting');
+ }
+ $db_connection = db_ensure_connection();
+ $db_sort = SortHelper::getOrderClause($sort, array('date' => '`date`', 'approval' => '`approval`'));
+
+ $filter = new FilterHelper($db_connection, DB_TABLE_CANDIDATES);
+
+ $filter->add(array('name' => 'item', 'type' => 'binary'));
+ $filter->add(array('name' => 'user', 'type' => 'binary'));
+
+ $filter->add(array('name' => 'created', 'db-name' => 'date'));
+ $filter->add(array('name' => 'created-before', 'db-name' => 'date', 'operator' => '<'));
+ $filter->add(array('name' => 'created-after', 'db-name' => 'date', 'operator' => '>'));
+
+ $filter->add(array('name' => 'approved', 'db-name' => 'approval', 'null' => false));
+
+ $filter->add(array('name' => 'owner', 'db-name' => 'user', 'type' => 'binary', 'db-table' => DB_TABLE_ITEMS, 'join-ref' => 'item', 'join-key' => 'id'));
+
+ $db_cond = $filter->evaluate($filters);
+ $db_join = $filter->evaluateJoins();
+
+ $db_query = 'SELECT ' . DB_TABLE_CANDIDATES . '.`id`, HEX(' . DB_TABLE_CANDIDATES. '.`item`) AS item FROM ' . DB_TABLE_CANDIDATES . $db_join . $db_cond . ' ' . $db_sort;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ return sql2array($db_result);
+ }
+
+ public static function listVotings($candidate, $sort = array()) {
+ $db_connection = db_ensure_connection();
+ $candidate = (int)$db_connection->real_escape_string($candidate);
+ $db_sort = SortHelper::getOrderClause($sort, array('date' => '`date`'));
+
+ $db_query = 'SELECT `candidate`, HEX(`user`) AS user, `accept`, `final`, `reason`, `date` FROM ' . DB_TABLE_CANDIDATE_VOTING . ' WHERE `candidate` = ' . $candidate . ' ' . $db_sort;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ return sql2array($db_result, array('Candidate', '_cleanup_voting'));
+ }
+ static function _cleanup_voting($item, $key) {
+ $item['accept'] = (bool)$item['accept'];
+ $item['final'] = (bool)$item['final'];
+ return $item;
+ }
+
+ public static function vote($candidate, $user, $accept, $reason, $final = false) {
+ $db_connection = db_ensure_connection();
+
+ $candidate = $db_connection->real_escape_string($candidate);
+ $user = $db_connection->real_escape_string($user);
+ $reason = $db_connection->real_escape_string($reason);
+ $accept = $accept ? 'TRUE' : 'FALSE';
+ $final = $final ? 'TRUE' : 'FALSE';
+
+ $db_query = 'INSERT INTO ' . DB_TABLE_CANDIDATE_VOTING . ' (`candidate`, `user`, `accept`, `final`, `reason`) VALUES (' . $candidate . ', UNHEX("' . $user . '"), ' . $accept . ', ' . $final . ', "' . $reason . '")';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+ }
+
+ public static function hasVoted($id, $user) {
+ $db_connection = db_ensure_connection();
+ $id = (int)$db_connection->real_escape_string($id);
+ $user = $db_connection->real_escape_string($user);
+
+ $db_query = 'SELECT * FROM ' . DB_TABLE_CANDIDATE_VOTING . ' WHERE `user` = UNHEX("' . $user . '") AND `candidate` = ' . $id;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500);
+ }
+
+ return $db_result->num_rows > 0;
+ }
+}
+?>
\ No newline at end of file
diff --git a/stdlib/candidates/approve.php b/stdlib/candidates/approve.php
new file mode 100644
index 0000000..e30903c
--- /dev/null
+++ b/stdlib/candidates/approve.php
@@ -0,0 +1,33 @@
+getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/candidates/create.php b/stdlib/candidates/create.php
new file mode 100644
index 0000000..20a9f7b
--- /dev/null
+++ b/stdlib/candidates/create.php
@@ -0,0 +1,84 @@
+ $candidate));
+ } else if ($content_type == 'text/xml' || $content_type == 'application/xml') {
+ $content = '' . htmlspecialchars($candidate, ENT_QUOTES) . '';
+ }
+ header('HTTP/1.1 200 ' . HttpException::getStatusMessage(200));
+ header('Content-type: ' . $content_type);
+ echo $content;
+ exit;
+
+} catch (HttpException $e) {
+ handleHttpException($e);
+} catch (Exception $e) {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/candidates/describe.php b/stdlib/candidates/describe.php
new file mode 100644
index 0000000..f9d6e4d
--- /dev/null
+++ b/stdlib/candidates/describe.php
@@ -0,0 +1,34 @@
+ $v) {
+ $content .= ' ald:' . $k . '="' . htmlspecialchars($v, ENT_QUOTES) . '"';
+ }
+ $content .= '/>';
+ }
+ header('HTTP/1.1 200 ' . HttpException::getStatusMessage(200));
+ header('Content-type: ' . $content_type);
+ echo $content;
+ exit;
+
+} catch (HttpException $e) {
+ handleHttpException($e);
+} catch (Exception $e) {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/candidates/list.php b/stdlib/candidates/list.php
new file mode 100644
index 0000000..264795e
--- /dev/null
+++ b/stdlib/candidates/list.php
@@ -0,0 +1,82 @@
+ true, 'open' => NULL, 'rejected' => false);
+
+ foreach ($candidates AS $candidate) {
+ if (isset($_GET['status'])) {
+ if (!in_array($_GET['status'], array_keys($status_map))) {
+ throw new HttpException(400);
+ }
+
+ $status = Candidate::accepted($candidate['id']);
+ if ($status_map[$_GET['status']] !== $status) {
+ continue;
+ }
+ }
+
+ if (isset($_GET['accepted-by']) || isset($_GET['rejected-by'])) {
+ $user = isset($_GET['accepted-by']) ? $_GET['accepted-by'] : $_GET['rejected-by'];
+ if (!Candidate::hasVoted($candidate['id'], $user)) {
+ continue;
+ }
+
+ $votings = Candidate::listVotings($candidate['id']);
+ if (searchSubArray($votings, array('user' => $user, 'accept' => isset($_GET['accepted-by']))) === NULL) {
+ continue;
+ }
+ }
+
+ if (isset($_GET['accepted']) || isset($_GET['accepted-min']) || isset($_GET['accepted-max']) || isset($_GET['rejected']) || isset($_GET['rejected-min']) || isset($_GET['rejected-max'])) {
+ $votings = Candidate::listVotings($candidate['id']);
+ $count = count($votings);
+
+ if (isset($_GET['accepted']) && $count != (int)$_GET['accepted']
+ || isset($_GET['accepted-min']) && $count < (int)$_GET['accepted-min']
+ || isset($_GET['accepted-max']) && $count > (int)$_GET['accepted-max']
+ || isset($_GET['rejected']) && $count != (int)$_GET['rejected']
+ || isset($_GET['rejected-min']) && $count < (int)$_GET['rejected-min']
+ || isset($_GET['rejected-max']) && $count > (int)$_GET['rejected-max']) {
+ continue;
+ }
+ }
+
+ $filtered_candidates[] = $candidate;
+ }
+ $candidates = $filtered_candidates;
+
+ if ($content_type == 'application/json') {
+ $content = json_encode($candidates);
+ } else if ($content_type == 'text/xml' || $content_type == 'application/xml') {
+ $content = '';
+ foreach ($candidates AS $candidate) {
+ $content .= '';
+ }
+ $content .= '';
+ }
+ header('HTTP/1.1 200 ' . HttpException::getStatusMessage(200));
+ header('Content-type: ' . $content_type);
+ echo $content;
+ exit;
+
+} catch (HttpException $e) {
+ handleHttpException($e);
+} catch (Exception $e) {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/candidates/voting.php b/stdlib/candidates/voting.php
new file mode 100644
index 0000000..7dc2f6b
--- /dev/null
+++ b/stdlib/candidates/voting.php
@@ -0,0 +1,73 @@
+';
+ foreach ($votings AS $voting) {
+ $content .= '';
+ }
+ $content .= '';
+ }
+ header('HTTP/1.1 200 ' . HttpException::getStatusMessage(200));
+ header('Content-type: ' . $content_type);
+ echo $content;
+ exit;
+ }
+
+} catch (HttpException $e) {
+ handleHttpException($e);
+} catch (Exception $e) {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/items.php b/stdlib/items.php
new file mode 100644
index 0000000..ad082f6
--- /dev/null
+++ b/stdlib/items.php
@@ -0,0 +1,82 @@
+add(array('name' => 'name', 'db-table' => DB_TABLE_ITEMS));
+ $filter->add(array('name' => 'user', 'type' => 'binary', 'db-table' => DB_TABLE_ITEMS));
+ $filter->add(array('name' => 'id', 'type' => 'binary', 'db-table' => DB_TABLE_ITEMS));
+
+ $db_cond = $filter->evaluate($_GET, ' AND ');
+
+ if (isset($_GET['sort'])) {
+ $sort_list = SortHelper::getListFromParam($_GET['sort']);
+ $db_sort = SortHelper::getOrderClause($sort_list, array('name' => '`name`', 'version' => '`position`'));
+ if (array_key_exists('version', $sort_list)) {
+ SortHelper::PrepareSemverSorting(DB_TABLE_ITEMS, 'version', $db_cond);
+ $db_join = ' LEFT JOIN `semver_index` ON (`' . DB_TABLE_ITEMS . '`.`version` = `semver_index`.`version`) ';
+ }
+ }
+
+ $db_query = 'SELECT name, `' . DB_TABLE_ITEMS . '`.`version`, HEX(`id`) AS id, GROUP_CONCAT(DISTINCT `release` SEPARATOR "\0") AS releases FROM ' . DB_TABLE_STDLIB . ', ' . DB_TABLE_ITEMS . $db_join . ' WHERE item = id ' . $db_cond . ' GROUP BY name, `' . DB_TABLE_ITEMS . '`.`version` ' . $db_sort;
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+
+ $data = sql2array($db_result, create_function('$item', '$item["releases"] = explode("\0", $item["releases"]); return $item;'));
+
+ $items = array();
+ foreach ($data AS $entry) {
+ $name = $entry['name'];
+ unset($entry['name']);
+
+ if (!isset($items[$name])) {
+ $items[$name] = array();
+ }
+ $items[$name][] = $entry;
+ }
+
+ if ($content_type == 'application/json') {
+ $content = json_encode($items);
+ } else if ($content_type == 'text/xml' || $content_type == 'application/xml') {
+ $content = '';
+ foreach ($items AS $name => $versions) {
+ $content .= '';
+ foreach ($versions AS $version) {
+ $content .= '';
+ foreach ($version['releases'] AS $release) {
+ $content .= '' . htmlspecialchars($release, ENT_QUOTES) . '';
+ }
+ $content .= '';
+ }
+ $content .= '';
+ }
+ $content .= '';
+ }
+
+ header('HTTP/1.1 200 ' . HttpException::getStatusMessage(200));
+ header('Content-type: ' . $content_type);
+ echo $content;
+ exit;
+
+} catch (HttpException $e) {
+ handleHttpException($e);
+} catch (Exception $e) {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/pending/.htaccess b/stdlib/pending/.htaccess
new file mode 100644
index 0000000..e2c0ea7
--- /dev/null
+++ b/stdlib/pending/.htaccess
@@ -0,0 +1,4 @@
+RewriteEngine On
+
+RewriteRule ^list$ list.php [L]
+RewriteRule ^(edit|delay)/([0-9a-fA-F]{32})$ $1.php?id=$2 [L]
diff --git a/stdlib/pending/delay.php b/stdlib/pending/delay.php
new file mode 100644
index 0000000..b44944b
--- /dev/null
+++ b/stdlib/pending/delay.php
@@ -0,0 +1,37 @@
+getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/pending/edit.php b/stdlib/pending/edit.php
new file mode 100644
index 0000000..d0eb356
--- /dev/null
+++ b/stdlib/pending/edit.php
@@ -0,0 +1,31 @@
+getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/pending/list.php b/stdlib/pending/list.php
new file mode 100644
index 0000000..028113a
--- /dev/null
+++ b/stdlib/pending/list.php
@@ -0,0 +1,65 @@
+ &$entry) {
+ if (isset($action) && $entry['update'] != $action) {
+ unset($data[$i]);
+ }
+ if (isset($_GET['name']) && $_GET['name'] != $entry['name']) {
+ unset($data[$i]);
+ }
+ $entry['update'] = UpdateType::getName($entry['update'], UpdateType::USAGE_STDLIB);
+ }
+ sort($data); # make array continous
+
+ if ($content_type == 'application/json') {
+ $content = json_encode($data);
+ } else if ($content_type == 'text/xml' || $content_type == 'application/xml') {
+ $content = '';
+ foreach ($data AS $entry) {
+ $content .= '';
+ }
+ $content .= '';
+ }
+
+ header('HTTP/1.1 200 ' . HttpException::getStatusMessage(200));
+ header('Content-Type: ' . $content_type);
+ echo $content;
+ exit;
+
+} catch (HttpException $e) {
+ handleHttpException($e);
+} catch (Exception $e) {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+}
+?>
\ No newline at end of file
diff --git a/stdlib/releases/.htaccess b/stdlib/releases/.htaccess
new file mode 100644
index 0000000..7890a18
--- /dev/null
+++ b/stdlib/releases/.htaccess
@@ -0,0 +1,9 @@
+Options -Multiviews
+
+RewriteEngine On
+
+RewriteRule ^list$ list.php [L]
+
+RewriteRule ^create/(major|minor|patch)$ create.php?type=$1 [L,QSA]
+
+RewriteRule ^(describe|delete|modify|publish)/(.+)$ $1.php?version=$2 [L]
\ No newline at end of file
diff --git a/stdlib/releases/StdlibRelease.php b/stdlib/releases/StdlibRelease.php
new file mode 100644
index 0000000..7271151
--- /dev/null
+++ b/stdlib/releases/StdlibRelease.php
@@ -0,0 +1,267 @@
+real_escape_string($release) . "'" . $db_cond;
+ $db_result = $db_connection->query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+ return $db_result->num_rows > 0;
+ }
+
+ const SPECIAL_VERSION_LATEST = "latest";
+ const SPECIAL_VERSION_FIRST = "first";
+
+ public static function getVersion($special_version, $published)
+ {
+ $special_version = strtolower($special_version);
+
+ if (in_array($special_version, array(self::SPECIAL_VERSION_LATEST, self::SPECIAL_VERSION_FIRST)))
+ {
+ $releases = self::ListReleases($published);
+ if (count($releases) > 0)
+ {
+ usort($releases, array("StdlibRelease", "semver_sort")); # sort following the semver rules
+ return $releases[$special_version == self::SPECIAL_VERSION_LATEST ? count($releases) - 1 : 0]; # latest / first release
+ }
+ }
+
+ return NULL;
+ }
+
+ public static function describe($release, $published)
+ {
+ # resolve special release versions
+ if (in_array(strtolower($release), array(self::SPECIAL_VERSION_LATEST, self::SPECIAL_VERSION_FIRST)))
+ {
+ $release = self::getVersion($release, $published);
+ if (!$release)
+ throw new HttpException(404);
+ }
+
+ $db_cond = ($t = self::get_publish_cond($published)) == NULL ? '' : " AND $t";
+ $db_connection = db_ensure_connection();
+
+ $db_query = "SELECT * FROM " . DB_TABLE_STDLIB_RELEASES . " WHERE `release` = '" . $db_connection->real_escape_string($release) . "'" . $db_cond;
+ $db_result = $db_connection->query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500);
+ }
+ if ($db_result->num_rows != 1)
+ {
+ throw new HttpException(404);
+ }
+ $t = $db_result->fetch_assoc();
+ $t['published'] = (bool)$t['published'];
+ return $t;
+ }
+
+ public static function create($release, $date = NULL, $description = '') {
+ $db_connection = db_ensure_connection();
+
+ $release = $db_connection->real_escape_string($release);
+ $description = $db_connection->real_escape_string($description);
+ $date = $date !== NULL ? '"' . $db_connection->real_escape_string($date) . '"' : 'NULL';
+
+ $db_query = 'INSERT INTO ' . DB_TABLE_STDLIB_RELEASES . ' (`release`, `description`, `date`) VALUES ("' . $release . '", "' . $description . '", ' . $date . ')';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE || $db_connection->affected_rows != 1) {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+ }
+
+ public static function delete($release)
+ {
+ $db_connection = db_ensure_connection();
+ $release = $db_connection->real_escape_string($release);
+
+ $db_query = "DELETE FROM " . DB_TABLE_STDLIB_RELEASES . " WHERE `release` = '$release' AND !`published`";
+ $db_result = $db_connection->query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+ else if ($db_connection->affected_rows < 1)
+ {
+ throw new HttpException(400, NULL, "Release doesn't exist or is already published.");
+ }
+ }
+
+ public static function update($release, $data)
+ {
+ if (self::exists($release, self::PUBLISHED_YES)) {
+ throw new HttpException(400, NULL, 'Cannot update already published release!');
+ }
+
+ $db_connection = db_ensure_connection();
+ $release = $db_connection->real_escape_string($release);
+
+ $db_query = "UPDATE " . DB_TABLE_STDLIB_RELEASES . " Set "
+ . implode(", ",
+ array_map(
+ create_function('$col, $val', 'return "`$col` = \'$val\'";'),
+ array_keys($data),
+ array_map(array($db_connection, 'real_escape_string'), array_values($data))
+ )
+ )
+ . " WHERE `release` = '$release' AND !`published`";
+
+ $db_result = $db_connection->query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+ else if ($db_connection->affected_rows != 1)
+ {
+ throw new HttpException(400, NULL, "Release '$release' doesn't exist or is already published.");
+ }
+ }
+
+ public static function previousRelease($release, $published) {
+ $releases = self::ListReleases(self::PUBLISHED_BOTH);
+ usort($releases, array('StdlibRelease', 'semver_sort'));
+ $index = array_search($release, $releases);
+ if ($index !== FALSE) {
+ while ($index >= 1) {
+ $prev_release = $releases[--$index];
+ if (self::exists($prev_release, $published))
+ return $prev_release;
+ }
+ }
+ return NULL;
+ }
+
+ public static function publishPending() {
+ $db_connection = db_ensure_connection();
+ $db_query = 'SELECT `release` FROM ' . DB_TABLE_STDLIB_RELEASES . ' WHERE !`published` AND `date` <= NOW()';
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE) {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+
+ $releases = array();
+ while ($release = $db_result->fetch_assoc()) { # sort by release
+ $releases[] = $release['release'];
+ }
+
+ usort($releases, array('StdlibRelease', 'semver_sort')); # sort following the semver rules
+ foreach ($releases AS $release) {
+ self::publish($release);
+ }
+ }
+
+ public static function publish($release) {
+ if (self::exists($release, self::PUBLISHED_YES)) {
+ throw new HttpException(400, NULL, 'Cannot publish already published release!');
+ }
+
+ $entries = Stdlib::GetItems($release);
+ foreach ($entries AS $entry) {
+ Stdlib::writeEntry($release, $entry['id'], $entry['comment']);
+ StdlibPending::DeleteEntry($entry['id']);
+ }
+
+ # removals are not covered by deletion above, so delete these entries here
+ $pending = StdlibPending::GetEntries($release);
+ foreach ($pending AS $entry) {
+ if ($entry['update'] == UpdateType::REMOVE) {
+ StdlibPending::DeleteEntry($entry['id']);
+ }
+ }
+
+ $release_data = self::describe($release, self::PUBLISHED_BOTH);
+ if ($release_data['date'] === NULL) {
+ self::update($release, array('date' => date('Y-m-d H:i:s')));
+ }
+ self::update($release, array('published' => true));
+ }
+
+ static function semver_sort($a, $b)
+ {
+ return semver_compare($a, $b);
+ }
+
+ public static function ListReleases($published, $sort = array())
+ {
+ # take publishing status into account
+ $db_cond = ($t = self::get_publish_cond($published)) == NULL ? '' : " WHERE $t";
+ $db_connection = db_ensure_connection();
+
+ # support sorting
+ $db_join = ' ';
+ $db_sort = SortHelper::getOrderClause($sort, array('date' => '`date`', 'release' => '`position`'));
+ if (array_key_exists('release', $sort)) { # sorting with semver needs special setup
+ SortHelper::PrepareSemverSorting(DB_TABLE_STDLIB_RELEASES, 'release', $db_cond);
+ $db_join = ' LEFT JOIN (`semver_index`) ON (`' . DB_TABLE_STDLIB_RELEASES . '`.`release` = `semver_index`.`version`) ';
+ }
+
+ # get all releases from DB
+ $db_query = "SELECT `release` FROM " . DB_TABLE_STDLIB_RELEASES . $db_join . $db_cond . $db_sort;
+ $db_result = $db_connection->query($db_query);
+ if (!$db_result)
+ {
+ throw new HttpException(500, NULL, $db_connection->error);
+ }
+
+ # fetch releases in array
+ return sql2array($db_result, create_function('$release', 'return $release[\'release\'];'));
+ }
+
+ public static function cleanup() {
+ # ensure everything that should be published is published
+ self::publishPending();
+
+ $latest_release = self::getVersion(self::SPECIAL_VERSION_LATEST, self::PUBLISHED_YES);
+
+ if ($latest_release !== NULL) {
+ # ensure there are no downgrade releases (only if there's actually a published release)
+ $releases = self::ListReleases(self::PUBLISHED_NO);
+ foreach ($releases AS $release) {
+ if (semver_compare($latest_release, $release) > -1) {
+ self::delete($release);
+ }
+ }
+ }
+ }
+
+ const PUBLISHED_YES = 1;
+ const PUBLISHED_NO = 2;
+ const PUBLISHED_BOTH = 3; # self::PUBLISHED_YES | self::PUBLISHED_NO
+
+ static function get_publish_cond($published)
+ {
+ switch ($published)
+ {
+ case self::PUBLISHED_YES:
+ return '`published`';
+ case self::PUBLISHED_NO:
+ return '!`published`';
+ case self::PUBLISHED_BOTH:
+ return NULL;
+ default:
+ throw new HttpException(400);
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/stdlib/releases/create.php b/stdlib/releases/create.php
new file mode 100644
index 0000000..45bdbfc
--- /dev/null
+++ b/stdlib/releases/create.php
@@ -0,0 +1,106 @@
+= the base for $release)
+ # only check for PUBLISHED_YES as otherwise, $release must be based on the latest release anyway.
+ if ($publish_status == StdlibRelease::PUBLISHED_YES && StdlibRelease::exists($release, StdlibRelease::PUBLISHED_BOTH))
+ {
+ throw new HttpException(409, NULL, "Release '$release' has already been created!");
+ }
+ }
+
+ $date = isset($_POST['date']) ? $_POST['date'] : NULL;
+ $description = isset($_POST['description']) ? $_POST['description'] : '';
+
+ if ($date !== NULL) {
+ if (!User::hasPrivilege($_SERVER["PHP_AUTH_USER"], User::PRIVILEGE_STDLIB_ADMIN)) {
+ throw new HttpException(403, NULL, 'Only stdlib admins can set the publication date for a release.');
+ }
+ }
+
+ StdlibRelease::create($release, $date, $description);
+
+ if ($content_type == "application/json")
+ {
+ $content = json_encode(array("release" => $release));
+ }
+ else if ($content_type == "text/xml" || $content_type == "application/xml")
+ {
+ $content = "" . htmlspecialchars($release, ENT_QUOTES) . '';
+ }
+ header("HTTP/1.1 200 " . HttpException::getStatusMessage(200));
+ header("Content-type: $content_type");
+ echo $content;
+ }
+ catch (HttpException $e)
+ {
+ handleHttpException($e);
+ }
+ catch (Exception $e)
+ {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+ }
+?>
\ No newline at end of file
diff --git a/stdlib/releases/delete.php b/stdlib/releases/delete.php
new file mode 100644
index 0000000..c71a128
--- /dev/null
+++ b/stdlib/releases/delete.php
@@ -0,0 +1,31 @@
+getMessage()));
+ }
+?>
\ No newline at end of file
diff --git a/stdlib/releases/describe.php b/stdlib/releases/describe.php
new file mode 100644
index 0000000..25a41bf
--- /dev/null
+++ b/stdlib/releases/describe.php
@@ -0,0 +1,88 @@
+';
+ foreach ($release['items'] AS $item) {
+ $content .= '';
+ }
+ $content .= '';
+ foreach ($release['changelog'] AS $item => $text) {
+ $content .= '';
+ }
+ $content .= '';
+ }
+
+ header("HTTP/1.1 200 " . HttpException::getStatusMessage(200));
+ header("Content-type: $content_type");
+ echo $content;
+ exit;
+ }
+ catch (HttpException $e)
+ {
+ handleHttpException($e);
+ }
+ catch (Exception $e)
+ {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+ }
+
+ function semver_sort($a, $b) {
+ return semver_compare($a, $b);
+ }
+?>
\ No newline at end of file
diff --git a/stdlib/releases/list.php b/stdlib/releases/list.php
new file mode 100644
index 0000000..d8fe118
--- /dev/null
+++ b/stdlib/releases/list.php
@@ -0,0 +1,66 @@
+";
+ foreach ($releases AS $release)
+ {
+ $content .= '';
+ }
+ $content .= "";
+ }
+
+ header("HTTP/1.1 200 " . HttpException::getStatusMessage(200));
+ header("Content-type: $content_type");
+ echo $content;
+ exit;
+ }
+ catch (HttpException $e)
+ {
+ handleHttpException($e);
+ }
+ catch (Exception $e)
+ {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+ }
+?>
\ No newline at end of file
diff --git a/stdlib/releases/modify.php b/stdlib/releases/modify.php
new file mode 100644
index 0000000..fde751d
--- /dev/null
+++ b/stdlib/releases/modify.php
@@ -0,0 +1,88 @@
+ "release", "description" => "description", "date" => "date") AS $key => $col)
+ {
+ if (!empty($_POST[$key]))
+ $data[$col] = $_POST[$key];
+ }
+
+ # verify release
+ if (isset($data["release"]))
+ {
+ if (!semver_validate($data["release"])) # check if valid semver
+ throw new HttpException(400, NULL, "Incorrect release version!");
+ if (!StdlibRelease::exists($data["release"], StdlibRelease::PUBLISHED_BOTH)) # check if not already existing
+ throw new HttpException(409, NULL, "Release '$data[release]' already exists!");
+
+ $latest = StdlibRelease::getVersion(StdlibRelease::SPECIAL_VERSION_LATEST, StdlibRelease::PUBLISHED_YES);
+ if ($latest !== NULL && semver_compare($latest, $data['release']) != -1) # check if not below latest published release
+ throw new HttpException(400, NULL, "Can't modify release version: Newer release $latest already published!");
+ }
+
+ # verify date
+ if (isset($data["date"]))
+ {
+ # check stdlib admin
+ if (!User::hasPrivilege($_SERVER["PHP_AUTH_USER"], User::PRIVILEGE_STDLIB_ADMIN)) {
+ throw new HttpException(403, NULL, 'Only stdlib admins can set the publication date for a release.');
+ }
+
+ # check valid date
+ $date = array();
+ if (!preg_match("/^(?\d{4})\-(?\d{2})\-(?\d{2})(T(?\d{2})(:(?\d{2})(:(?\d{2}))))?$/", $data["date"], $date))
+ {
+ throw new HttpException(400, NULL, "Invalid date format!");
+ }
+ if (!checkdate($date["month"], $date["day"], $date["year"]))
+ {
+ throw new HttpException(400, NULL, "Invalid date specified!");
+ }
+
+ # check not already over
+ $datetime = new DateTime($data["date"]);
+ $now = new DateTime();
+ if ($datetime <= $now)
+ {
+ throw new HttpException(400, NULL, "Specified date already over!");
+ }
+ }
+
+ if (count($data) > 0)
+ StdlibRelease::update($_GET["version"], $data);
+
+ header("HTTP/1.1 204 " . HttpException::getStatusMessage(204));
+ }
+ catch (HttpException $e)
+ {
+ handleHttpException($e);
+ }
+ catch (Exception $e)
+ {
+ handleHttpException(new HttpException(500, NULL, $e->getMessage()));
+ }
+?>
\ No newline at end of file
diff --git a/stdlib/releases/publish.php b/stdlib/releases/publish.php
new file mode 100644
index 0000000..a5c1adc
--- /dev/null
+++ b/stdlib/releases/publish.php
@@ -0,0 +1,37 @@
+getMessage()));
+ }
+?>
\ No newline at end of file
diff --git a/tests/UserTest.php b/tests/UserTest.php
new file mode 100644
index 0000000..4fd40b1
--- /dev/null
+++ b/tests/UserTest.php
@@ -0,0 +1,99 @@
+query($db_query);
+ }
+
+ public static function testPrivilegeArray() {
+ assertEquals(User::privilegeToArray(User::PRIVILEGE_NONE), array('none'), 'Failed to convert privilege PRIVILEGE_NONE to array');
+ assertEquals(User::privilegeToArray(User::PRIVILEGE_NONE|User::PRIVILEGE_REVIEW), array('review'), 'Failed to convert privilege PRIVILEGE_NONE|PRIVILEGE_REVIEW to array');
+
+ $arr = User::privilegeToArray(User::PRIVILEGE_ADMIN|User::PRIVILEGE_STDLIB_ADMIN|User::PRIVILEGE_REGISTRATION);
+ assertInternalType('array', $arr, 'Privilege conversion did not return an array');
+ assertCount(3, $arr, 'Invalid element count in privilege array');
+ assertContains('admin', $arr, 'Failed to convert privilege PRIVILEGE_ADMIN|PRIVILEGE_STDLIB_ADMIN|PRIVILEGE_REGISTRATION to array');
+ assertContains('stdlib-admin', $arr, 'Failed to convert privilege PRIVILEGE_ADMIN|PRIVILEGE_STDLIB_ADMIN|PRIVILEGE_REGISTRATION to array');
+ assertContains('registration', $arr, 'Failed to convert privilege PRIVILEGE_ADMIN|PRIVILEGE_STDLIB_ADMIN|PRIVILEGE_REGISTRATION to array');
+ }
+
+ public static function testArrayPrivilege() {
+ $arr = array('stdlib', 'review');
+ assertEquals(User::privilegeFromArray($arr), User::PRIVILEGE_STDLIB|User::PRIVILEGE_REVIEW, 'Failed to convert array [stdlib, review] to privilege');
+
+ $arr = array('admin');
+ assertEquals(User::privilegeFromArray($arr), User::PRIVILEGE_ADMIN, 'Failed to convert array [admin] to privilege');
+
+ $arr = array();
+ assertEquals(User::privilegeFromArray($arr), User::PRIVILEGE_NONE, 'Failed to convert array [] to privilege');
+ }
+}
+?>
\ No newline at end of file
diff --git a/tests/setup/db-mysql.sh b/tests/setup/db-mysql.sh
new file mode 100755
index 0000000..83f096c
--- /dev/null
+++ b/tests/setup/db-mysql.sh
@@ -0,0 +1,15 @@
+#! /usr/bin/env sh
+
+echo "Setting up MySQL for the tests..."
+
+# create the database
+echo " Creating the database..."
+mysql --default-character-set=utf8 -u root -e 'CREATE DATABASE IF NOT EXISTS `travis-test`;'
+
+# import the individual tables
+echo " Importing the DB tables..."
+for file in MySQL/*.sql;
+do
+ echo " $file"
+ mysql --default-character-set=utf8 -u root travis-test < "$file"
+done
diff --git a/tests/setup/profile.sh b/tests/setup/profile.sh
new file mode 100755
index 0000000..1b5fca3
--- /dev/null
+++ b/tests/setup/profile.sh
@@ -0,0 +1,17 @@
+#! /usr/bin/env sh
+
+# check if the specified profile is valid
+if [ ! -d "config/profiles/$1" ]; then
+ echo "$1 is not a valid config profile!"
+ exit 1
+fi
+
+echo "Using config profile \"$1\"..."
+
+# overwrite config with all profile-specific config files
+for file in config/profiles/$1/*.php;
+do
+ base=${file##*/}
+ cp -v -f $file config/$base | sed 's/^/ /'
+done
+echo "\n\n"
\ No newline at end of file
diff --git a/tests/users/SuspensionTest.php b/tests/users/SuspensionTest.php
new file mode 100644
index 0000000..f4c2f48
--- /dev/null
+++ b/tests/users/SuspensionTest.php
@@ -0,0 +1,101 @@
+query($db_query);
+
+ $db_query = 'DELETE FROM ' . DB_TABLE_SUSPENSIONS . ' WHERE `user` = UNHEX("' . self::$id . '")';
+ $db_connection->query($db_query);
+ }
+
+ public static function testBeforeSuspension() {
+ assertFalse(Suspension::isSuspended(self::USER_NAME));
+ assertFalse(Suspension::isSuspendedById(self::$id));
+ }
+
+ /**
+ * @depends testBeforeSuspension
+ */
+ public static function testCreate() {
+ self::$s1 = Suspension::create(self::USER_NAME, 'Suspended for no particular reason');
+ assertInternalType('int', self::$s1, 'Suspension creation did not return an integer');
+ assertTrue(Suspension::isSuspended(self::USER_NAME));
+ }
+
+ /**
+ * @depends testBeforeSuspension
+ */
+ public static function testCreateForId() {
+ self::$s2 = Suspension::createForId(self::$id, 'For testing reasons', self::EXPIRATION_DATE);
+ assertInternalType('int', self::$s2, 'Suspension creation (for ID) did not return an integer');
+ assertTrue(Suspension::isSuspendedById(self::$id));
+ }
+
+ /**
+ * @depends testCreate
+ * @depends testCreateForId
+ */
+ public static function testRetrieveList() {
+ $s = Suspension::getSuspensions(self::USER_NAME);
+
+ assertInternalType('array', $s, 'Suspension retrieval did not return an array');
+ assertCount(2, $s, 'Incorrect number of suspensions: ' . count($s));
+
+ assertEquals($s[0], Suspension::getSuspension($s[0]->id), 'Should equal individually retrieved suspension');
+ }
+
+ /**
+ * @depends testCreate
+ */
+ public static function testFirstSuspension() {
+ $s = Suspension::getSuspension(self::$s1);
+
+ assertTrue($s->restricted, 'Suspension should be restricted but is not.');
+ assertTrue($s->infinite, 'Suspension should be infinite but is not.');
+ assertEquals($s->expires, NULL, 'Expiration date should be NULL');
+ }
+
+ /**
+ * @depends testCreateForId
+ */
+ public static function testSecondSuspension() {
+ $s = Suspension::getSuspension(self::$s2);
+
+ assertTrue($s->restricted, 'Suspension should be restricted but is not.');
+ assertFalse($s->infinite, 'Suspension should be infinite but is not.');
+ assertEquals($s->expires, new DateTime(self::EXPIRATION_DATE), 'Expiration date not set properly');
+ }
+
+ /**
+ * @depends testRetrieveList
+ */
+ public static function testDelete() {
+ foreach (Suspension::getSuspensionsById(self::$id) AS $s) {
+ $s->delete();
+ }
+ assertFalse(Suspension::isSuspendedById(self::$id));
+ }
+}
+?>
\ No newline at end of file
diff --git a/users/Suspension.php b/users/Suspension.php
index 5b7fd11..ef82f2c 100644
--- a/users/Suspension.php
+++ b/users/Suspension.php
@@ -1,6 +1,8 @@
real_escape_string($user);
if ($expires !== NULL) {
- $expires = mysql_real_escape_string($expires, $db_connection);
+ $expires = $db_connection->real_escape_string($expires);
}
$restricted = $restricted ? '1' : '0';
- $reason = mysql_real_escape_string($reason, $db_connection);
+ $reason = $db_connection->real_escape_string($reason);
$db_query = 'INSERT INTO ' . DB_TABLE_SUSPENSIONS . ' (`user`, `expires`, `restricted`, `reason`) VALUES (UNHEX("' . $user . '"), ' . ($expires !== NULL ? '"' . $expires . '"' : 'NULL') . ', ' . $restricted . ', "' . $reason . '")';
- $db_result = mysql_query($db_query, $db_connection);
- if ($db_result === FALSE || mysql_affected_rows() < 1) {
+ $db_result = $db_connection->query($db_query);
+ if ($db_result === FALSE || $db_connection->affected_rows < 1) {
throw new HttpException(500);
}
- return mysql_insert_id($db_connection);
+ return $db_connection->insert_id;
}
public static function clear() {
@@ -39,7 +41,7 @@ public static function clear() {
$db_query = 'UPDATE ' . DB_TABLE_SUSPENSIONS . ' SET `active` = FALSE WHERE `active` AND' . $cond;
}
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
@@ -53,19 +55,48 @@ public static function isSuspendedById($id) {
return count(self::getSuspensionsById($id)) != 0;
}
- public static function getSuspensions($user, $active = true) {
- return self::getSuspensionsById(User::getID($user), $active);
+ public static function getSuspensions($user, $filters = array(), $sort = array()) {
+ return self::getSuspensionsById(User::getID($user), $filters, $sort);
}
- public static function getSuspensionsById($id, $active = true) {
+ public static function getSuspensionsById($id, $filters = array(), $sort = array()) {
$db_connection = db_ensure_connection();
- $id = mysql_real_escape_string($id, $db_connection);
- $db_query = 'SELECT *, HEX(`user`) AS user FROM ' . DB_TABLE_SUSPENSIONS . ' WHERE `user` = UNHEX("' . $id . '")'
- . ($active === NULL ? '' : ($active
- ? ' AND (`active` AND (`expires` IS NULL OR `expires` > NOW()))'
- : ' AND (NOT `active` OR (`expires` IS NOT NULL AND `expires` <= NOW()))'));
- $db_result = mysql_query($db_query, $db_connection);
+ if (!is_array($filters)) {
+ throw new HttpException(500, NULL, 'Must pass a valid array as suspension filter!');
+ }
+
+ $filter = new FilterHelper($db_connection, DB_TABLE_SUSPENSIONS);
+
+ $filter->add(array('db-name' => 'user', 'value' => $id, 'type' => 'binary'));
+
+ $filter->add(array('name' => 'expires'));
+ $filter->add(array('name' => 'expires-before', 'db-name' => 'expires', 'operator' => '<'));
+ $filter->add(array('name' => 'expires-after', 'db-name' => 'expires', 'operator' => '>'));
+
+ $filter->add(array('name' => 'created'));
+ $filter->add(array('name' => 'created-before', 'db-name' => 'created', 'operator' => '<'));
+ $filter->add(array('name' => 'created-after', 'db-name' => 'created', 'operator' => '>'));
+
+ $filter->add(array('name' => 'infinite', 'db-name' => 'expires', 'null' => true));
+ $filter->add(array('name' => 'restricted', 'type' => 'switch'));
+
+ $filter->add(array('name' => 'active', 'type' => 'switch', 'default' => 'true',
+ 'conditions' => array( # an array of conditions to be satisified
+ array('db-name' => 'active'), # the `active` field must be TRUE (see 'default' value of filter)
+ array( # a set of sub-conditions, to be combined using 'OR'
+ 'logic' => 'OR',
+ array('db-name' => 'expires', 'null' => true), # [if the filter is set,] 'expires' must either be NULL ...
+ array('db-name' => 'expires', 'operator' => '>', 'value' => 'NOW()', 'type' => 'expr') # ... or it must be > NOW()
+ )
+ )
+ ));
+
+ $db_cond = $filter->evaluate($filters);
+ $sort = SortHelper::getOrderClause($sort, array('created' => '`created`', 'expires' => '`expires`'));
+
+ $db_query = 'SELECT *, HEX(`user`) AS user FROM ' . DB_TABLE_SUSPENSIONS . $db_cond . $sort;
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
@@ -75,19 +106,19 @@ public static function getSuspensionsById($id, $active = true) {
public static function getSuspension($id) {
$db_connection = db_ensure_connection();
- $id = (int)mysql_real_escape_string($id, $db_connection);
+ $id = (int)$db_connection->real_escape_string($id);
$db_query = 'SELECT *, HEX(`user`) AS user FROM ' . DB_TABLE_SUSPENSIONS . ' WHERE `id` =' . $id;
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) != 1) {
+ if ($db_result->num_rows != 1) {
throw new HttpException(404);
}
- return self::_create_inst_(mysql_fetch_assoc($db_result));
+ return self::_create_inst_($db_result->fetch_assoc());
}
public static function _create_inst_($arr) {
@@ -110,10 +141,10 @@ private function __construct($id, $user, $created, $expires, $restricted, $reaso
public function delete() {
$db_connection = db_ensure_connection();
- $id = mysql_real_escape_string($this->id, $db_connection);
+ $id = $db_connection->real_escape_string($this->id);
$db_query = 'UPDATE ' . DB_TABLE_SUSPENSIONS . ' SET `active` = FALSE WHERE `id` = "' . $id . '"';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
diff --git a/users/describe.php b/users/describe.php
index 3fb9c4c..950aedb 100644
--- a/users/describe.php
+++ b/users/describe.php
@@ -24,19 +24,19 @@
}
else
{
- $id = mysql_real_escape_string($_GET["id"], $db_connection);
+ $id = $db_connection->real_escape_string($_GET["id"]);
}
$db_query = "SELECT name, mail, privileges, joined FROM " . DB_TABLE_USERS . " WHERE id = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) == 1)
+ if ($db_result->num_rows == 1)
{
- $user = mysql_fetch_assoc($db_result);
+ $user = $db_result->fetch_assoc();
$trusted_user = false;
if (isset($_SERVER["PHP_AUTH_USER"]) && isset($_SERVER["PHP_AUTH_PW"])) {
diff --git a/users/list.php b/users/list.php
index 00a8e7f..5c1b4da 100644
--- a/users/list.php
+++ b/users/list.php
@@ -2,6 +2,7 @@
require_once("../modules/HttpException/HttpException.php");
require_once("../db.php");
require_once("../util.php");
+ require_once('../SortHelper.php');
require_once('../sql2array.php');
require_once("../Assert.php");
require_once("../User.php");
@@ -18,11 +19,12 @@
# retrieve data limits
$db_limit = "";
+ $db_order = '';
$db_cond = '';
if (isset($_GET["count"]) && strtolower($_GET["count"]) != "all")
{
- $db_limit = "LIMIT " . mysql_real_escape_string($_GET["count"], $db_connection);
+ $db_limit = "LIMIT " . $db_connection->real_escape_string($_GET["count"]);
}
if (isset($_GET["start"]))
{
@@ -30,7 +32,11 @@
{
$db_limit = "LIMIT 18446744073709551615"; # Source: http://dev.mysql.com/doc/refman/5.5/en/select.html
}
- $db_limit .= " OFFSET " . mysql_real_escape_string($_GET["start"], $db_connection);
+ $db_limit .= " OFFSET " . $db_connection->real_escape_string($_GET["start"]);
+ }
+
+ if (isset($_GET['sort'])) {
+ $db_order = SortHelper::getOrderClause(SortHelper::getListFromParam($_GET['sort']), array('name' => '`name`', 'joined' => '`joined`'));
}
# retrieve filters
@@ -44,8 +50,8 @@
}
# query for data:
- $db_query = "SELECT name, HEX(id) AS id FROM " . DB_TABLE_USERS . " $db_cond $db_limit";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "SELECT name, HEX(id) AS id FROM " . DB_TABLE_USERS . " $db_cond $db_order $db_limit";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500);
diff --git a/users/modify.php b/users/modify.php
index 0f530f5..b159680 100644
--- a/users/modify.php
+++ b/users/modify.php
@@ -22,7 +22,7 @@
}
else
{
- $id = mysql_real_escape_string($_GET["id"], $db_connection);
+ $id = $db_connection->real_escape_string($_GET["id"]);
}
if ($id != User::getID($_SERVER["PHP_AUTH_USER"]))
@@ -37,13 +37,13 @@
throw new HttpException(409, NULL, "User name already taken");
}
- $db_query = "UPDATE " . DB_TABLE_USERS . " Set name = '" . mysql_real_escape_string($_POST["name"], $db_connection) . "' WHERE id = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_query = "UPDATE " . DB_TABLE_USERS . " Set name = '" . $db_connection->real_escape_string($_POST["name"]) . "' WHERE id = UNHEX('$id')";
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500, NULL, "Failed to set user name.");
}
- if (mysql_affected_rows($db_connection) != 1)
+ if ($db_connection->affected_rows != 1)
{
throw new HttpException(404, NULL, "User with this ID was not found.");
}
@@ -55,7 +55,7 @@
throw new HttpException(409, NULL, "Mail address already taken");
}
- $mail = mysql_real_escape_string($_POST["mail"], $db_connection);
+ $mail = $db_connection->real_escape_string($_POST["mail"]);
$suspension = Suspension::createForId($id, 'Suspended for validation of modified email address', NULL, false);
$mail_text = str_replace(array('{$USER}', '{$ID}', '{$MAIL}', '{$SUSPENSION}'), array(User::getName($id), $id, $mail, $suspension), MAIL_CHANGE_TEMPLATE);
@@ -68,12 +68,12 @@
}
$db_query = "UPDATE " . DB_TABLE_USERS . " Set mail = '$mail' WHERE id = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500, NULL, "Failed to set user mail address.");
}
- if (mysql_affected_rows($db_connection) != 1)
+ if ($db_connection->affected_rows != 1)
{
throw new HttpException(404, NULL, "User with this ID was not found.");
}
@@ -83,12 +83,12 @@
$pw = hash("sha256", $_POST["password"]);
$db_query = "UPDATE " . DB_TABLE_USERS . " Set pw = '$pw' WHERE id = UNHEX('$id')";
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if (!$db_result)
{
throw new HttpException(500, NULL, "Failed to set user password.");
}
- if (mysql_affected_rows($db_connection) != 1)
+ if ($db_connection->affected_rows != 1)
{
throw new HttpException(404, NULL, "User with this ID was not found.");
}
diff --git a/users/registration/Registration.php b/users/registration/Registration.php
index 6658d3d..5af2364 100644
--- a/users/registration/Registration.php
+++ b/users/registration/Registration.php
@@ -11,15 +11,15 @@ public static function create($name, $mail, $password) {
$db_connection = db_ensure_connection();
- $name = mysql_real_escape_string($name, $db_connection);
- $mail = mysql_real_escape_string($mail, $db_connection);
- $password = mysql_real_escape_string($password, $db_connection);
+ $name = $db_connection->real_escape_string($name);
+ $mail = $db_connection->real_escape_string($mail);
+ $password = $db_connection->real_escape_string($password);
$id = mt_rand();
$token = self::createToken();
$db_query = 'INSERT INTO ' . DB_TABLE_REGISTRATION . ' (`id`, `token`, `name`, `mail`, `password`) VALUES ("' . $id . '", "' . $token . '", "' . $name . '", "' . $mail . '", "' . $password . '")';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
@@ -31,7 +31,7 @@ public static function clear() {
$db_connection = db_ensure_connection();
$db_query = 'DELETE FROM ' . DB_TABLE_REGISTRATION . ' WHERE `created` + INTERVAL ' . REGISTRATION_TIMEOUT . ' <= NOW()';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
@@ -39,40 +39,40 @@ public static function clear() {
public static function existsPending($name, $mail) {
$db_connection = db_ensure_connection();
- $name = mysql_real_escape_string($name, $db_connection);
- $mail = mysql_real_escape_string($mail, $db_connection);
+ $name = $db_connection->real_escape_string($name);
+ $mail = $db_connection->real_escape_string($mail);
$db_query = 'SELECT * FROM ' . DB_TABLE_REGISTRATION . ' WHERE `name` = "' . $name . '" OR `mail` = "' . $mail . '"';
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
- return mysql_num_rows($db_result) > 0;
+ return $db_result->num_rows > 0;
}
public static function get($id) {
$db_connection = db_ensure_connection();
- $id = (int)mysql_real_escape_string($id, $db_connection);
+ $id = (int)$db_connection->real_escape_string($id);
$db_query = 'SELECT * FROM ' . DB_TABLE_REGISTRATION . ' WHERE `id` = ' . $id;
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
- if (mysql_num_rows($db_result) < 1) {
+ if ($db_result->num_rows < 1) {
throw new HttpException(404);
}
- return mysql_fetch_assoc($db_result);
+ return $db_result->fetch_assoc();
}
public static function delete($id) {
$db_connection = db_ensure_connection();
- $id = (int)mysql_real_escape_string($id, $db_connection);
+ $id = (int)$db_connection->real_escape_string($id);
$db_query = 'DELETE FROM ' . DB_TABLE_REGISTRATION . ' WHERE `id` = ' . $id;
- $db_result = mysql_query($db_query, $db_connection);
+ $db_result = $db_connection->query($db_query);
if ($db_result === FALSE) {
throw new HttpException(500);
}
diff --git a/users/suspensions/list.php b/users/suspensions/list.php
index ea76854..a14a2f4 100644
--- a/users/suspensions/list.php
+++ b/users/suspensions/list.php
@@ -2,6 +2,8 @@
require_once('../../Assert.php');
require_once('../../modules/HttpException/HttpException.php');
require_once('../../util.php');
+require_once('../../SortHelper.php');
+require_once('../../FilterHelper.php');
require_once('../../User.php');
require_once('../Suspension.php');
@@ -27,16 +29,10 @@
# validate accept header of request
$content_type = get_preferred_mimetype(array('application/json', 'text/xml', 'application/xml', 'application/x-ald-package'), 'application/json');
- $active = true;
- if (isset($_GET['active'])) {
- if (in_array($_GET['active'], array('no', -1, 'false'))) {
- $active = false;
- } else if (in_array($_GET['active'], array('both', '0'))) {
- $active = NULL;
- }
- }
+ $filters = FilterHelper::FromParams(array('active', 'created', 'created-after', 'created-before', 'expires', 'expires-after', 'expires-before', 'infinite', 'restricted'));
+ $sort_list = SortHelper::getListFromParam(isset($_GET['sort']) ? $_GET['sort'] : '');
- $suspensions = Suspension::getSuspensionsById($id, $active);
+ $suspensions = Suspension::getSuspensionsById($id, $filters, $sort_list);
# cleanup the suspension entries
foreach ($suspensions AS $suspension) {
$suspension->created = $suspension->created->format(TIMESTAMP_FORMAT);