Explorar el Código

v2 initial public repo push

causefx hace 8 años
padre
commit
fb3bda393b
Se han modificado 100 ficheros con 17381 adiciones y 0 borrados
  1. 6 0
      api/composer.json
  2. 143 0
      api/composer.lock
  3. 6 0
      api/config/default.php
  4. 1498 0
      api/functions.php
  5. 488 0
      api/index.php
  6. 59 0
      api/pages/dependencies.php
  7. 32 0
      api/pages/lockscreen.php
  8. 100 0
      api/pages/login.php
  9. 109 0
      api/pages/settings-settings-logs.php
  10. 84 0
      api/pages/settings-tab-editor-categories.php
  11. 83 0
      api/pages/settings-tab-editor-tabs.php
  12. 60 0
      api/pages/settings-user-manage-groups.php
  13. 48 0
      api/pages/settings-user-manage-users.php
  14. 193 0
      api/pages/settings.php
  15. 363 0
      api/pages/wizard.php
  16. 7 0
      api/vendor/autoload.php
  17. 445 0
      api/vendor/composer/ClassLoader.php
  18. 21 0
      api/vendor/composer/LICENSE
  19. 64 0
      api/vendor/composer/autoload_classmap.php
  20. 10 0
      api/vendor/composer/autoload_files.php
  21. 9 0
      api/vendor/composer/autoload_namespaces.php
  22. 10 0
      api/vendor/composer/autoload_psr4.php
  23. 70 0
      api/vendor/composer/autoload_real.php
  24. 94 0
      api/vendor/composer/autoload_static.php
  25. 131 0
      api/vendor/composer/installed.json
  26. 32 0
      api/vendor/dibi/dibi/composer.json
  27. 31 0
      api/vendor/dibi/dibi/contributing.md
  28. 161 0
      api/vendor/dibi/dibi/examples/connecting-to-databases.php
  29. BIN
      api/vendor/dibi/dibi/examples/data/arrow.png
  30. BIN
      api/vendor/dibi/dibi/examples/data/dibi-powered.gif
  31. BIN
      api/vendor/dibi/dibi/examples/data/sample.dump.sql.gz
  32. BIN
      api/vendor/dibi/dibi/examples/data/sample.mdb
  33. BIN
      api/vendor/dibi/dibi/examples/data/sample.s3db
  34. 64 0
      api/vendor/dibi/dibi/examples/data/style.css
  35. 49 0
      api/vendor/dibi/dibi/examples/database-reflection.php
  36. 32 0
      api/vendor/dibi/dibi/examples/dumping-sql-and-result-set.php
  37. 94 0
      api/vendor/dibi/dibi/examples/fetching-examples.php
  38. 18 0
      api/vendor/dibi/dibi/examples/importing-dump-from-file.php
  39. 61 0
      api/vendor/dibi/dibi/examples/query-language-and-conditions.php
  40. 87 0
      api/vendor/dibi/dibi/examples/query-language-basic-examples.php
  41. 50 0
      api/vendor/dibi/dibi/examples/result-set-data-types.php
  42. 33 0
      api/vendor/dibi/dibi/examples/tracy-and-exceptions.php
  43. 40 0
      api/vendor/dibi/dibi/examples/tracy.php
  44. 29 0
      api/vendor/dibi/dibi/examples/using-datetime.php
  45. 33 0
      api/vendor/dibi/dibi/examples/using-extension-methods.php
  46. 77 0
      api/vendor/dibi/dibi/examples/using-fluent-syntax.php
  47. 28 0
      api/vendor/dibi/dibi/examples/using-limit-and-offset.php
  48. 37 0
      api/vendor/dibi/dibi/examples/using-logger.php
  49. 43 0
      api/vendor/dibi/dibi/examples/using-profiler.php
  50. 54 0
      api/vendor/dibi/dibi/examples/using-substitutions.php
  51. 34 0
      api/vendor/dibi/dibi/examples/using-transactions.php
  52. 55 0
      api/vendor/dibi/dibi/license.md
  53. 133 0
      api/vendor/dibi/dibi/readme.md
  54. 71 0
      api/vendor/dibi/dibi/src/Dibi/Bridges/Nette/DibiExtension22.php
  55. 12 0
      api/vendor/dibi/dibi/src/Dibi/Bridges/Nette/config.sample.neon
  56. 657 0
      api/vendor/dibi/dibi/src/Dibi/Connection.php
  57. 308 0
      api/vendor/dibi/dibi/src/Dibi/DataSource.php
  58. 74 0
      api/vendor/dibi/dibi/src/Dibi/DateTime.php
  59. 839 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/FirebirdDriver.php
  60. 409 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/MsSqlDriver.php
  61. 215 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/MsSqlReflector.php
  62. 502 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/MySqlDriver.php
  63. 134 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/MySqlReflector.php
  64. 516 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/MySqliDriver.php
  65. 519 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/OdbcDriver.php
  66. 540 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/OracleDriver.php
  67. 578 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/PdoDriver.php
  68. 743 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/PostgreDriver.php
  69. 497 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/Sqlite3Driver.php
  70. 153 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/SqliteReflector.php
  71. 439 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/SqlsrvDriver.php
  72. 135 0
      api/vendor/dibi/dibi/src/Dibi/Drivers/SqlsrvReflector.php
  73. 97 0
      api/vendor/dibi/dibi/src/Dibi/Event.php
  74. 32 0
      api/vendor/dibi/dibi/src/Dibi/Expression.php
  75. 521 0
      api/vendor/dibi/dibi/src/Dibi/Fluent.php
  76. 64 0
      api/vendor/dibi/dibi/src/Dibi/HashMap.php
  77. 302 0
      api/vendor/dibi/dibi/src/Dibi/Helpers.php
  78. 35 0
      api/vendor/dibi/dibi/src/Dibi/Literal.php
  79. 75 0
      api/vendor/dibi/dibi/src/Dibi/Loggers/FileLogger.php
  80. 94 0
      api/vendor/dibi/dibi/src/Dibi/Loggers/FirePhpLogger.php
  81. 165 0
      api/vendor/dibi/dibi/src/Dibi/Reflection/Column.php
  82. 114 0
      api/vendor/dibi/dibi/src/Dibi/Reflection/Database.php
  83. 53 0
      api/vendor/dibi/dibi/src/Dibi/Reflection/ForeignKey.php
  84. 69 0
      api/vendor/dibi/dibi/src/Dibi/Reflection/Index.php
  85. 105 0
      api/vendor/dibi/dibi/src/Dibi/Reflection/Result.php
  86. 200 0
      api/vendor/dibi/dibi/src/Dibi/Reflection/Table.php
  87. 619 0
      api/vendor/dibi/dibi/src/Dibi/Result.php
  88. 107 0
      api/vendor/dibi/dibi/src/Dibi/ResultIterator.php
  89. 93 0
      api/vendor/dibi/dibi/src/Dibi/Row.php
  90. 151 0
      api/vendor/dibi/dibi/src/Dibi/Strict.php
  91. 619 0
      api/vendor/dibi/dibi/src/Dibi/Translator.php
  92. 32 0
      api/vendor/dibi/dibi/src/Dibi/Type.php
  93. 450 0
      api/vendor/dibi/dibi/src/Dibi/dibi.php
  94. 153 0
      api/vendor/dibi/dibi/src/Dibi/exceptions.php
  95. 242 0
      api/vendor/dibi/dibi/src/Dibi/interfaces.php
  96. 140 0
      api/vendor/dibi/dibi/src/loader.php
  97. 2 0
      api/vendor/lcobucci/jwt/.gitignore
  98. 56 0
      api/vendor/lcobucci/jwt/.scrutinizer.yml
  99. 15 0
      api/vendor/lcobucci/jwt/.travis.yml
  100. 27 0
      api/vendor/lcobucci/jwt/LICENSE

+ 6 - 0
api/composer.json

@@ -0,0 +1,6 @@
+{
+    "require": {
+        "dibi/dibi": "^3.1",
+        "lcobucci/jwt": "^3.2"
+    }
+}

+ 143 - 0
api/composer.lock

@@ -0,0 +1,143 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "c704916881881be88b068aaf0d0be395",
+    "packages": [
+        {
+            "name": "dibi/dibi",
+            "version": "v3.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dg/dibi.git",
+                "reference": "fa35c0cf7de1286ce1cf9fbdba058c1d54c849f8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dg/dibi/zipball/fa35c0cf7de1286ce1cf9fbdba058c1d54c849f8",
+                "reference": "fa35c0cf7de1286ce1cf9fbdba058c1d54c849f8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.4"
+            },
+            "replace": {
+                "dg/dibi": "*"
+            },
+            "require-dev": {
+                "nette/tester": "~1.7",
+                "tracy/tracy": "~2.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ],
+                "files": [
+                    "src/loader.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0",
+                "GPL-3.0"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                }
+            ],
+            "description": "Dibi is Database Abstraction Library for PHP",
+            "homepage": "https://dibiphp.com",
+            "keywords": [
+                "access",
+                "database",
+                "dbal",
+                "mssql",
+                "mysql",
+                "odbc",
+                "oracle",
+                "pdo",
+                "postgresql",
+                "sqlite",
+                "sqlsrv"
+            ],
+            "time": "2017-09-25T15:57:54+00:00"
+        },
+        {
+            "name": "lcobucci/jwt",
+            "version": "3.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/lcobucci/jwt.git",
+                "reference": "0b5930be73582369e10c4d4bb7a12bac927a203c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0b5930be73582369e10c4d4bb7a12bac927a203c",
+                "reference": "0b5930be73582369e10c4d4bb7a12bac927a203c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-openssl": "*",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "mdanter/ecc": "~0.3.1",
+                "mikey179/vfsstream": "~1.5",
+                "phpmd/phpmd": "~2.2",
+                "phpunit/php-invoker": "~1.1",
+                "phpunit/phpunit": "~4.5",
+                "squizlabs/php_codesniffer": "~2.3"
+            },
+            "suggest": {
+                "mdanter/ecc": "Required to use Elliptic Curves based algorithms."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Lcobucci\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Luís Otávio Cobucci Oblonczyk",
+                    "email": "lcobucci@gmail.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to work with JSON Web Token and JSON Web Signature",
+            "keywords": [
+                "JWS",
+                "jwt"
+            ],
+            "time": "2017-09-01T08:23:26+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}

+ 6 - 0
api/config/default.php

@@ -0,0 +1,6 @@
+<?php
+return array(
+    'branch' => 'v2-master',
+    'that' => 'this',
+    'organizrAPI' => 'none'
+);

+ 1498 - 0
api/functions.php

@@ -0,0 +1,1498 @@
+<?php
+// ===================================
+// Organizr Version
+$GLOBALS['installedVersion'] = '2.0.0-alpha';
+// ===================================
+//Set GLOBALS from config file
+$GLOBALS['userConfigPath'] = __DIR__.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php';
+$GLOBALS['defaultConfigPath'] = __DIR__.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'default.php';
+$GLOBALS['currentTime'] = gmdate("Y-m-d\TH:i:s\Z");
+//Add in default and custom settings
+configLazy();
+//Define Logs and files after db location is set
+if(isset($GLOBALS['dbLocation'])){
+    $GLOBALS['organizrLog'] = $GLOBALS['dbLocation'].'organizrLog.json';
+    $GLOBALS['organizrLoginLog'] = $GLOBALS['dbLocation'].'organizrLoginLog.json';
+}
+//Set UTC timeZone
+date_default_timezone_set("UTC");
+// Autoload frameworks
+require_once(__DIR__ . '/vendor/autoload.php');
+//framework uses
+use Lcobucci\JWT\Builder;
+use Lcobucci\JWT\Signer\Hmac\Sha256;
+use Lcobucci\JWT\ValidationData;
+use Lcobucci\JWT\Parser;
+//Validate Token if set and set guest if not - sets GLOBALS
+getOrganizrUserToken();
+//include all pages files
+foreach (glob(__DIR__.DIRECTORY_SEPARATOR.'pages' . DIRECTORY_SEPARATOR . "*.php") as $filename){
+    require_once $filename;
+}
+function jwtParse($token){
+    try {
+        $result = array();
+        $result['valid'] = false;
+        //Check Token with JWT
+        //Set key
+        if(!isset($GLOBALS['organizrHash'])){
+            return null;
+        }
+        $key = $GLOBALS['organizrHash'];
+        //HSA256 Encyption
+        $signer = new Sha256();
+        $jwttoken = (new Parser())->parse((string) $token); // Parses from a string
+        $jwttoken->getHeaders(); // Retrieves the token header
+        $jwttoken->getClaims(); // Retrieves the token claims
+        //Start Validation
+        if($jwttoken->verify($signer, $key)){
+            $data = new ValidationData(); // It will use the current time to validate (iat, nbf and exp)
+            $data->setIssuer('Organizr');
+            $data->setAudience('Organizr');
+            if($jwttoken->validate($data)){
+                $result['valid'] = true;
+                $result['username'] = $jwttoken->getClaim('username');
+                $result['group'] = $jwttoken->getClaim('group');
+                $result['groupID'] = $jwttoken->getClaim('groupID');
+                $result['email'] = $jwttoken->getClaim('email');
+                $result['image'] = $jwttoken->getClaim('image');
+                $result['tokenExpire'] = $jwttoken->getClaim('exp');
+                $result['tokenDate'] = $jwttoken->getClaim('iat');
+                $result['token'] = $jwttoken->getClaim('exp');
+            }
+        }
+        if($result['valid'] == true){ return $result; }else{ return false; }
+    } catch(\RunException $e) {
+        return false;
+    } catch(\OutOfBoundsException $e) {
+        return false;
+    } catch(\RunTimeException $e) {
+        return false;
+    } catch(\InvalidArgumentException $e) {
+        return false;
+    }
+}
+function createToken($username,$email,$image,$group,$groupID,$key,$days = 1){
+    //Create JWT
+    //Set key
+    //HSA256 Encyption
+    $signer = new Sha256();
+    //Start Builder
+    $jwttoken = (new Builder())->setIssuer('Organizr') // Configures the issuer (iss claim)
+                                ->setAudience('Organizr') // Configures the audience (aud claim)
+                                ->setId('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
+                                ->setIssuedAt(time()) // Configures the time that the token was issue (iat claim)
+                                ->setExpiration(time() + (86400 * $days)) // Configures the expiration time of the token (exp claim)
+                                ->set('username', $username) // Configures a new claim, called "username"
+                                ->set('group', $group) // Configures a new claim, called "group"
+                                ->set('groupID', $groupID) // Configures a new claim, called "groupID"
+                                ->set('email', $email) // Configures a new claim, called "email"
+                                ->set('image', $image) // Configures a new claim, called "image"
+                                ->sign($signer, $key) // creates a signature using "testing" as key
+                                ->getToken(); // Retrieves the generated token
+    $jwttoken->getHeaders(); // Retrieves the token headers
+    $jwttoken->getClaims(); // Retrieves the token claims
+    coookie('set','organizrToken',$jwttoken,$days);
+    return $jwttoken;
+}
+function prettyPrint($v) {
+	$trace = debug_backtrace()[0];
+	echo '<pre style="white-space: pre; text-overflow: ellipsis; overflow: hidden; background-color: #f2f2f2; border: 2px solid black; border-radius: 5px; padding: 5px; margin: 5px;">'.$trace['file'].':'.$trace['line'].' '.gettype($v)."\n\n".print_r($v, 1).'</pre><br/>';
+}
+
+// Create config file in the return syntax
+function createConfig($array, $path = null, $nest = 0) {
+    $path = ($path) ? $path : $GLOBALS['userConfigPath'];
+	// Define Initial Value
+	$output = array();
+
+	// Sort Items
+	ksort($array);
+
+	// Update the current config version
+	if (!$nest) {
+		// Inject Current Version
+		$output[] = "\t'configVersion' => '".(isset($array['apply_CONFIG_VERSION'])?$array['apply_CONFIG_VERSION']:$GLOBALS['installedVersion'])."'";
+	}
+	unset($array['configVersion']);
+	unset($array['apply_CONFIG_VERSION']);
+
+	// Process Settings
+	foreach ($array as $k => $v) {
+		$allowCommit = true;
+		switch (gettype($v)) {
+			case 'boolean':
+				$item = ($v?true:false);
+				break;
+			case 'integer':
+			case 'double':
+			case 'integer':
+			case 'NULL':
+				$item = $v;
+				break;
+			case 'string':
+				$item = "'".str_replace(array('\\',"'"),array('\\\\',"\'"),$v)."'";
+				break;
+			case 'array':
+				$item = createConfig($v, false, $nest+1);
+				break;
+			default:
+				$allowCommit = false;
+		}
+
+		if($allowCommit) {
+			$output[] = str_repeat("\t",$nest+1)."'$k' => $item";
+		}
+	}
+
+	// Build output
+	$output = (!$nest?"<?php\nreturn ":'')."array(\n".implode(",\n",$output)."\n".str_repeat("\t",$nest).')'.(!$nest?';':'');
+
+	if (!$nest && $path) {
+		$pathDigest = pathinfo($path);
+
+		@mkdir($pathDigest['dirname'], 0770, true);
+
+		if (file_exists($path)) {
+			rename($path, $pathDigest['dirname'].'/'.$pathDigest['filename'].'.bak.php');
+		}
+
+		$file = fopen($path, 'w');
+		fwrite($file, $output);
+		fclose($file);
+		if (file_exists($path)) {
+			return true;
+		}
+		//writeLog("error", "config was unable to write");
+		return false;
+	} else {
+  		//writeLog("success", "config was updated with new values");
+		return $output;
+	}
+}
+// Commit new values to the configuration
+function updateConfig($new, $current = false) {
+	// Get config if not supplied
+	if ($current === false) {
+		$current = loadConfig();
+	} else if (is_string($current) && is_file($current)) {
+		$current = loadConfig($current);
+	}
+	// Inject Parts
+	foreach ($new as $k => $v) {
+		$current[$k] = $v;
+	}
+	// Return Create
+	return createConfig($current);
+}
+function configLazy() {
+	// Load config or default
+	if (file_exists($GLOBALS['userConfigPath'])) {
+		$config = fillDefaultConfig(loadConfig($GLOBALS['userConfigPath']));
+	} else {
+		$config = loadConfig($GLOBALS['defaultConfigPath']);
+	}
+	if (is_array($config)) {
+		defineConfig($config);
+	}
+	return $config;
+}
+function loadConfig($path = null){
+    $path = ($path) ? $path : $GLOBALS['userConfigPath'];
+    if (!is_file($path)) {
+        return null;
+    } else {
+        return (array) call_user_func(function() use($path) {
+            return include($path);
+        });
+    }
+}
+function fillDefaultConfig($array) {
+    $path = $GLOBALS['defaultConfigPath'];
+	if (is_string($path)) {
+		$loadedDefaults = loadConfig($path);
+	} else {
+		$loadedDefaults = $path;
+	}
+	return (is_array($loadedDefaults) ? fillDefaultConfig_recurse($array, $loadedDefaults) : false);
+}
+function fillDefaultConfig_recurse($current, $defaults) {
+	foreach($defaults as $k => $v) {
+		if (!isset($current[$k])) {
+			$current[$k] = $v;
+		} else if (is_array($current[$k]) && is_array($v)) {
+			$current[$k] = fillDefaultConfig_recurse($current[$k], $v);
+		}
+	}
+	return $current;
+}
+function defineConfig($array, $anyCase = true, $nest_prefix = false) {
+	foreach($array as $k => $v) {
+		if (is_scalar($v) && !defined($nest_prefix.$k)) {
+            $GLOBALS[$nest_prefix.$k] = $v;
+		} else if (is_array($v)) {
+			defineConfig($v, $anyCase, $nest_prefix.$k.'_');
+		}
+	}
+}
+function cleanDirectory($path){
+	$path = str_replace(array('/', '\\'), '/', $path);
+    if(substr($path, -1) != '/'){
+        $path = $path . '/';
+    }
+    if($path[0] != '/' && $path[1] != ':'){
+        $path = '/' . $path;
+    }
+    return $path;
+}
+function wizardConfig($array){
+    foreach ($array['data'] as $items) {
+        foreach ($items as $key => $value) {
+            if($key == 'name'){
+                $newKey = $value;
+            }
+            if($key == 'value'){
+                $newValue = $value;
+            }
+            if(isset($newKey) && isset($newValue)){
+                $$newKey = $newValue;
+            }
+        }
+    }
+    $location = cleanDirectory($location);
+    $dbName = $dbName.'.db';
+    $configVersion = $GLOBALS['installedVersion'];
+    $configArray = array(
+        'dbName' => $dbName,
+        'dbLocation' => $location,
+        'license' => $license,
+        'organizrHash' => $hashKey,
+        'organizrAPI' => $api,
+        'registrationPassword' => $registrationPassword,
+    );
+    /*
+    file_put_contents('config'.DIRECTORY_SEPARATOR.'config.php',
+"<?php
+return array(
+    \"configVersion\" => \"$configVersion\",
+    \"dbName\" => \"$dbName\",
+    \"dbLocation\" => \"$location\",
+    \"license\" => \"$license\",
+    \"organizrHash\" => \"$hashKey\",
+    \"organizrAPI\" => \"$api\",
+    \"registrationPassword\" => \"$registrationPassword\"
+);");
+*/
+    //Create Config
+    if(createConfig($configArray)){
+        //Call DB Create
+        if(createDB($location,$dbName)){
+            //Add in first user
+            if(createFirstAdmin($location,$dbName,$username,$password,$email)){
+                if(createToken($username,$email,gravatar($email),'Admin',0,$hashKey,1)){
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+function gravatar($email = '') {
+    $email = md5(strtolower(trim($email)));
+    $gravurl = "https://www.gravatar.com/avatar/$email?s=100&d=mm";
+    return $gravurl;
+}
+function login($array){
+    //Grab username and Password from login form
+    foreach ($array['data'] as $items) {
+        foreach ($items as $key => $value) {
+            if($key == 'name'){
+                $newKey = $value;
+            }
+            if($key == 'value'){
+                $newValue = $value;
+            }
+            if(isset($newKey) && isset($newValue)){
+                $$newKey = $newValue;
+            }
+        }
+    }
+    $username = strtolower($username);
+    $days = (isset($remember)) ? 7 : 1;
+    try {
+    	$database = new Dibi\Connection([
+    		'driver' => 'sqlite3',
+    		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+    	]);
+        $result = $database->fetch('SELECT * FROM users WHERE username = ? COLLATE NOCASE OR email = ? COLLATE NOCASE',$username,$username);
+        if(password_verify($password, $result['password'])){
+            if(createToken($result['username'],$result['email'],$result['image'],$result['group'],$result['group_id'],$GLOBALS['organizrHash'],$days)){
+                writeLoginLog($username, 'success');
+                writeLog('success', 'Login Function - A User has logged in', $username);
+                return true;
+            }
+        }else{
+            writeLoginLog($username, 'error');
+            writeLog('error', 'Login Function - Wrong Password', $username);
+            return 'mismatch';
+        }
+    } catch (Dibi\Exception $e) {
+    	return 'error';
+    }
+}
+function createDB($path,$filename) {
+    if(file_exists($path.$filename)){
+        unlink($path.$filename);
+    }
+    try {
+    	$createDB = new Dibi\Connection([
+    		'driver' => 'sqlite3',
+    		'database' => $path.$filename,
+    	]);
+        // Create Users
+    	$users = $createDB->query('CREATE TABLE `users` (
+    		`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+    		`username`	TEXT UNIQUE,
+    		`password`	TEXT,
+    		`email`	TEXT,
+    		`plex_token`	TEXT,
+            `group`	TEXT,
+            `group_id`	INTEGER,
+    		`image`	TEXT,
+            `register_date` DATE,
+    		`auth_service`	TEXT DEFAULT \'internal\'
+    	);');
+        $groups = $createDB->query('CREATE TABLE `groups` (
+    		`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+    		`group`	TEXT UNIQUE,
+            `group_id`	INTEGER,
+    		`image`	TEXT,
+            `default` INTEGER
+    	);');
+        $categories = $createDB->query('CREATE TABLE `categories` (
+    		`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+            `order`	INTEGER,
+    		`category`	TEXT UNIQUE,
+            `category_id`	INTEGER,
+    		`image`	TEXT,
+            `default` INTEGER
+    	);');
+    	// Create Tabs
+    	$tabs = $createDB->query('CREATE TABLE `tabs` (
+    		`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+    		`order`	INTEGER,
+    		`category_id`	INTEGER,
+    		`name`	TEXT,
+            `url`	TEXT,
+    		`url_local`	TEXT,
+    		`default`	INTEGER,
+    		`enabled`	INTEGER,
+    		`group_id`	INTEGER,
+    		`image`	TEXT,
+    		`type`	INTEGER,
+    		`splash`	INTEGER,
+    		`ping`		INTEGER,
+    		`ping_url`	TEXT
+    	);');
+    	// Create Options
+    	$options = $createDB->query('CREATE TABLE `options` (
+    		`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+    		`users_id`	INTEGER UNIQUE,
+    		`title`	TEXT UNIQUE,
+    		`topbar`	TEXT,
+    		`bottombar`	TEXT,
+    		`sidebar`	TEXT,
+    		`hoverbg`	TEXT,
+    		`topbartext`	TEXT,
+    		`activetabBG`	TEXT,
+    		`activetabicon`	TEXT,
+    		`activetabtext`	TEXT,
+    		`inactiveicon`	TEXT,
+    		`inactivetext`	TEXT,
+    		`loading`	TEXT,
+    		`hovertext`	TEXT
+    	);');
+    	// Create Invites
+    	$invites = $createDB->query('CREATE TABLE `invites` (
+    		`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+    		`code`	TEXT UNIQUE,
+    		`date`	TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    		`email`	TEXT,
+    		`username`	TEXT,
+    		`dateused`	TIMESTAMP,
+    		`usedby`	TEXT,
+    		`ip`	TEXT,
+    		`valid`	TEXT,
+            `type` TEXT
+    	);');
+        return true;
+    } catch (Dibi\Exception $e) {
+        return false;
+    }
+}
+// Upgrade Database
+function updateDB($path,$filename,$oldVerNum = false) {
+    try {
+        $connect = new Dibi\Connection([
+            'driver' => 'sqlite3',
+            'database' => $path.$filename,
+        ]);
+        // Cache current DB
+    	$cache = array();
+    	foreach($connect->query('SELECT name FROM sqlite_master WHERE type="table";') as $table) {
+    		foreach($connect->query('SELECT * FROM '.$table['name'].';') as $key => $row) {
+    			foreach($row as $k => $v) {
+    				if (is_string($k)) {
+    					$cache[$table['name']][$key][$k] = $v;
+    				}
+    			}
+    		}
+    	}
+        // Remove Current Database
+        /*
+        $pathDigest = pathinfo($path.$filename);
+        if (file_exists($path.$filename)) {
+            rename($path.$filename, $pathDigest['dirname'].'/'.$pathDigest['filename'].'['.date('Y-m-d_H-i-s').']'.($oldVerNum?'['.$oldVerNum.']':'').'.bak.db');
+        }
+
+        // Create New Database
+        $success = createSQLiteDB($path.$filename);
+
+        // Restore Items
+        if ($success) {
+            foreach($cache as $table => $tableData) {
+                if ($tableData) {
+                    $queryBase = 'INSERT INTO '.$table.' (`'.implode('`,`',array_keys(current($tableData))).'`) values ';
+                    $insertValues = array();
+                    reset($tableData);
+                    foreach($tableData as $key => $value) {
+                        $insertValues[] = '('.implode(',',array_map(function($d) {
+                            return (isset($d)?$GLOBALS['file_db']->quote($d):'null');
+                        }, $value)).')';
+                    }
+                    $GLOBALS['file_db']->query($queryBase.implode(',',$insertValues).';');
+                }
+            }
+      //writeLog("success", "database values have been updated");
+            return true;
+        } else {
+      //writeLog("error", "database values unable to be updated");
+            return false;
+        }
+        */
+        return $cache;
+    } catch (Dibi\Exception $e) {
+        return $e;
+    }
+}
+function createFirstAdmin($path,$filename,$username,$password,$email) {
+    try {
+    	$createDB = new Dibi\Connection([
+    		'driver' => 'sqlite3',
+    		'database' => $path.$filename,
+    	]);
+        $userInfo = [
+            'username' => $username,
+            'password'  => password_hash($password, PASSWORD_BCRYPT),
+            'email' => $email,
+            'group' => 'Admin',
+            'group_id' => 0,
+            'image' => gravatar($email),
+            'register_date' => $GLOBALS['currentTime'],
+        ];
+        $groupInfo0 = [
+            'group' => 'Admin',
+            'group_id' => 0,
+            'default' => false,
+            'image' => 'plugins/images/groups/admin.png',
+        ];
+        $groupInfo1 = [
+            'group' => 'Co-Admin',
+            'group_id' => 1,
+            'default' => false,
+            'image' => 'plugins/images/groups/coadmin.png',
+        ];
+        $groupInfo2 = [
+            'group' => 'Super User',
+            'group_id' => 2,
+            'default' => false,
+            'image' => 'plugins/images/groups/superuser.png',
+        ];
+        $groupInfo3 = [
+            'group' => 'Power User',
+            'group_id' => 3,
+            'default' => false,
+            'image' => 'plugins/images/groups/poweruser.png',
+        ];
+        $groupInfo4 = [
+            'group' => 'User',
+            'group_id' => 4,
+            'default' => true,
+            'image' => 'plugins/images/groups/user.png',
+        ];
+        $groupInfoGuest = [
+            'group' => 'Guest',
+            'group_id' => 999,
+            'default' => false,
+            'image' => 'plugins/images/groups/guest.png',
+        ];
+        $settingsInfo = [
+            'order' => 1,
+            'category_id' => 0,
+            'name' => 'Settings',
+            'url' => 'api/?v1/settings/page',
+            'default' => false,
+            'enabled' => true,
+            'group_id' => 1,
+            'image' => 'fontawesome::cog',
+            'type' => 0
+        ];
+        $homepageInfo = [
+            'order' => 2,
+            'category_id' => 0,
+            'name' => 'Homepage',
+            'url' => 'api/?v1/homepage/page',
+            'default' => false,
+            'enabled' => false,
+            'group_id' => 4,
+            'image' => 'fontawesome::home',
+            'type' => 0
+        ];
+        $unsortedInfo = [
+            'order' => 1,
+            'category' => 'Unsorted',
+            'category_id' => 0,
+			'image' => 'plugins/images/categories/unsorted.png',
+            'default' => true
+        ];
+        $createDB->query('INSERT INTO [users]', $userInfo);
+        $createDB->query('INSERT INTO [groups]', $groupInfo0);
+        $createDB->query('INSERT INTO [groups]', $groupInfo1);
+        $createDB->query('INSERT INTO [groups]', $groupInfo2);
+        $createDB->query('INSERT INTO [groups]', $groupInfo3);
+        $createDB->query('INSERT INTO [groups]', $groupInfo4);
+        $createDB->query('INSERT INTO [groups]', $groupInfoGuest);
+        $createDB->query('INSERT INTO [tabs]', $settingsInfo);
+        $createDB->query('INSERT INTO [tabs]', $homepageInfo);
+        $createDB->query('INSERT INTO [categories]', $unsortedInfo);
+        return true;
+    } catch (Dibi\Exception $e) {
+        return false;
+    }
+}
+function register($array){
+    //Grab username and Password from login form
+    foreach ($array['data'] as $items) {
+        foreach ($items as $key => $value) {
+            if($key == 'name'){
+                $newKey = $value;
+            }
+            if($key == 'value'){
+                $newValue = $value;
+            }
+            if(isset($newKey) && isset($newValue)){
+                $$newKey = $newValue;
+            }
+        }
+    }
+    if($registrationPassword == $GLOBALS['registrationPassword']){
+        $defaults = defaultUserGroup();
+        writeLog('success', 'Registration Function - Registration Password Verified', $username);
+        if(createUser($username,$password,$defaults,$email)){
+            writeLog('success', 'Registration Function - A User has registered', $username);
+            if(createToken($username,$email,gravatar($email),$defaults['group'],$defaults['group_id'],$GLOBALS['organizrHash'],1)){
+                writeLoginLog($username, 'success');
+                writeLog('success', 'Login Function - A User has logged in', $username);
+                return true;
+            }
+        }else{
+            writeLog('error', 'Registration Function - An error occured', $username);
+            return 'username taken';
+        }
+    }else{
+        writeLog('warning', 'Registration Function - Wrong Password', $username);
+        return 'mismatch';
+    }
+}
+function defaultUserGroup(){
+    try {
+    	$connect = new Dibi\Connection([
+    		'driver' => 'sqlite3',
+    		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+    	]);
+        $all = $connect->fetch('SELECT * FROM groups WHERE `default` = 1');
+        return $all;
+    } catch (Dibi\Exception $e) {
+        return false;
+    }
+}
+function defaulTabCategory(){
+    try {
+    	$connect = new Dibi\Connection([
+    		'driver' => 'sqlite3',
+    		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+    	]);
+        $all = $connect->fetch('SELECT * FROM categories WHERE `default` = 1');
+        return $all;
+    } catch (Dibi\Exception $e) {
+        return false;
+    }
+}
+function getGuest(){
+    if(isset($GLOBALS['dbLocation'])){
+        try {
+        	$connect = new Dibi\Connection([
+        		'driver' => 'sqlite3',
+        		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+        	]);
+            $all = $connect->fetch('SELECT * FROM groups WHERE `group` = "Guest"');
+            return $all;
+        } catch (Dibi\Exception $e) {
+            return false;
+        }
+    }else{
+        return array(
+            'group' => 'Guest',
+            'group_id' => 999,
+            'image' => 'plugins/images/groups/guest.png'
+        );
+    }
+}
+function adminEditGroup($array){
+    switch ($array['data']['action']) {
+        case 'changeDefaultGroup':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('UPDATE groups SET `default` = 0');
+                $connect->query('
+                	UPDATE groups SET', [
+                		'default' => 1
+                	], '
+                	WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'Group Management Function -  Changed Default Group from ['.$array['data']['oldGroupName'].'] to ['.$array['data']['newGroupName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'deleteUserGroup':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('DELETE FROM groups WHERE id = ?', $array['data']['id']);
+                    writeLog('success', 'Group Management Function -  Deleted Group ['.$array['data']['groupName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'addUserGroup':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $newGroup = [
+                    'group' => $array['data']['newGroupName'],
+                    'group_id' => $array['data']['newGroupID'],
+                    'default' => false,
+                    'image' => $array['data']['newGroupImage'],
+                ];
+                $connect->query('INSERT INTO [groups]', $newGroup);
+                    writeLog('success', 'Group Management Function -  Added Group ['.$array['data']['newGroupName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'editUserGroup':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('
+                	UPDATE groups SET', [
+                        'group' => $array['data']['groupName'],
+                		'image' => $array['data']['groupImage'],
+                	], '
+                	WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'Group Management Function -  Edited Group Info for ['.$array['data']['oldGroupName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        default:
+            # code...
+            break;
+    }
+}
+function adminEditUser($array){
+    switch ($array['data']['action']) {
+        case 'changeGroup':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('
+                	UPDATE users SET', [
+                		'group' => $array['data']['newGroupName'],
+                		'group_id' => $array['data']['newGroupID'],
+                	], '
+                	WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'User Management Function - User: '.$array['data']['username'].'\'s group was changed from ['.$array['data']['oldGroup'].'] to ['.$array['data']['newGroupName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                writeLog('error', 'User Management Function - Error - User: '.$array['data']['username'].'\'s group was changed from ['.$array['data']['oldGroup'].'] to ['.$array['data']['newGroupName'].']', $GLOBALS['organizrUser']['username']);
+                return false;
+            }
+            break;
+        case 'addNewUser':
+            $defaults = defaultUserGroup();
+            if(createUser($array['data']['username'],$array['data']['password'],$defaults,$array['data']['email'])){
+                writeLog('success', 'Create User Function - Acount created for ['.$array['data']['username'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            }else{
+                writeLog('error', 'Registration Function - An error occured', $GLOBALS['organizrUser']['username']);
+                return 'username taken';
+            }
+            break;
+        case 'deleteUser':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('DELETE FROM users WHERE id = ?', $array['data']['id']);
+                    writeLog('success', 'User Management Function -  Deleted User ['.$array['data']['username'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        default:
+            # code...
+            break;
+    }
+}
+function editTabs($array){
+    switch ($array['data']['action']) {
+        case 'changeGroup':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('
+                	UPDATE tabs SET', [
+                		'group_id' => $array['data']['newGroupID'],
+                	], '
+                	WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'Tab Editor Function - Tab: '.$array['data']['tab'].'\'s group was changed to ['.$array['data']['newGroupName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'changeCategory':
+                try {
+                    $connect = new Dibi\Connection([
+                        'driver' => 'sqlite3',
+                        'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                    ]);
+                    $connect->query('
+                        UPDATE tabs SET', [
+                            'category_id' => $array['data']['newCategoryID'],
+                        ], '
+                        WHERE id=?', $array['data']['id']);
+                        writeLog('success', 'Tab Editor Function - Tab: '.$array['data']['tab'].'\'s category was changed to ['.$array['data']['newCategoryName'].']', $GLOBALS['organizrUser']['username']);
+                    return true;
+                } catch (Dibi\Exception $e) {
+                    return false;
+                }
+                break;
+        case 'changeType':
+                try {
+                    $connect = new Dibi\Connection([
+                        'driver' => 'sqlite3',
+                        'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                    ]);
+                    $connect->query('
+                        UPDATE tabs SET', [
+                            'type' => $array['data']['newTypeID'],
+                        ], '
+                        WHERE id=?', $array['data']['id']);
+                        writeLog('success', 'Tab Editor Function - Tab: '.$array['data']['tab'].'\'s type was changed to ['.$array['data']['newTypeName'].']', $GLOBALS['organizrUser']['username']);
+                    return true;
+                } catch (Dibi\Exception $e) {
+                    return false;
+                }
+                break;
+        case 'changeEnabled':
+                try {
+                    $connect = new Dibi\Connection([
+                        'driver' => 'sqlite3',
+                        'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                    ]);
+                    $connect->query('
+                        UPDATE tabs SET', [
+                            'enabled' => $array['data']['tabEnabled'],
+                        ], '
+                        WHERE id=?', $array['data']['id']);
+                        writeLog('success', 'Tab Editor Function - Tab: '.$array['data']['tab'].'\'s enable status was changed to ['.$array['data']['tabEnabledWord'].']', $GLOBALS['organizrUser']['username']);
+                    return true;
+                } catch (Dibi\Exception $e) {
+                    return false;
+                }
+                break;
+        case 'changeSplash':
+                try {
+                    $connect = new Dibi\Connection([
+                        'driver' => 'sqlite3',
+                        'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                    ]);
+                    $connect->query('
+                        UPDATE tabs SET', [
+                            'splash' => $array['data']['tabSplash'],
+                        ], '
+                        WHERE id=?', $array['data']['id']);
+                        writeLog('success', 'Tab Editor Function - Tab: '.$array['data']['tab'].'\'s splash status was changed to ['.$array['data']['tabSplashWord'].']', $GLOBALS['organizrUser']['username']);
+                    return true;
+                } catch (Dibi\Exception $e) {
+                    return false;
+                }
+                break;
+        case 'changeDefault':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('UPDATE tabs SET `default` = 0');
+                $connect->query('
+                    UPDATE tabs SET', [
+                        'default' => 1
+                    ], '
+                    WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'Tab Editor Function -  Changed Default Tab to ['.$array['data']['tab'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'deleteTab':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('DELETE FROM tabs WHERE id = ?', $array['data']['id']);
+                    writeLog('success', 'Tab Editor Function -  Deleted Tab ['.$array['data']['tab'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'editTab':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('
+                    UPDATE tabs SET', [
+                        'name' => $array['data']['tabName'],
+                        'url' => $array['data']['tabURL'],
+                        'image' => $array['data']['tabImage'],
+                    ], '
+                    WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'Tab Editor Function -  Edited Tab Info for ['.$array['data']['tabName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+        case 'changeOrder':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                foreach ($array['data']['tabs']['tab'] as $key => $value) {
+                    if($value['order'] != $value['originalOrder']){
+                        $connect->query('
+                            UPDATE tabs SET', [
+                                'order' => $value['order'],
+                            ], '
+                            WHERE id=?', $value['id']);
+                            writeLog('success', 'Tab Editor Function - '.$value['name'].' Order Changed From '.$value['order'].' to '.$value['originalOrder'], $GLOBALS['organizrUser']['username']);
+                    }
+                }
+                writeLog('success', 'Tab Editor Function - Tab Order Changed', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'addNewTab':
+            try {
+                $default = defaulTabCategory()['category_id'];
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $newTab = [
+                    'order' => $array['data']['tabOrder'],
+                    'category_id' => $default,
+                    'name' => $array['data']['tabName'],
+                    'url' => $array['data']['tabURL'],
+                    'default' => $array['data']['tabDefault'],
+                    'enabled' => 1,
+                    'group_id' => $array['data']['tabGroupID'],
+                    'image' => $array['data']['tabImage'],
+                    'type' => $array['data']['tabType']
+                ];
+                $connect->query('INSERT INTO [tabs]', $newTab);
+                writeLog('success', 'Tab Editor Function - Created Tab for: '.$array['data']['tabName'], $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'deleteTab':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('DELETE FROM tabs WHERE id = ?', $array['data']['id']);
+                    writeLog('success', 'Tab Editor Function -  Deleted Tab ['.$array['data']['name'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        default:
+            # code...
+            break;
+    }
+}
+function editCategories($array){
+    switch ($array['data']['action']) {
+        case 'changeDefault':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('UPDATE categories SET `default` = 0');
+                $connect->query('
+                	UPDATE categories SET', [
+                		'default' => 1
+                	], '
+                	WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'Category Editor Function -  Changed Default Category from ['.$array['data']['oldCategoryName'].'] to ['.$array['data']['newCategoryName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'deleteCategory':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('DELETE FROM categories WHERE id = ?', $array['data']['id']);
+                    writeLog('success', 'Category Editor Function -  Deleted Category ['.$array['data']['category'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'addNewCategory':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $newCategory = [
+                    'category' => $array['data']['categoryName'],
+                    'order' => $array['data']['categoryOrder'],
+                    'category_id' => $array['data']['categoryID'],
+                    'default' => false,
+                    'image' => $array['data']['categoryImage'],
+                ];
+                $connect->query('INSERT INTO [categories]', $newCategory);
+                    writeLog('success', 'Category Editor Function -  Added Category ['.$array['data']['categoryName'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return $e;
+            }
+            break;
+        case 'editCategory':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                $connect->query('
+                	UPDATE categories SET', [
+                        'category' => $array['data']['name'],
+                		'image' => $array['data']['image'],
+                	], '
+                	WHERE id=?', $array['data']['id']);
+                    writeLog('success', 'Category Editor Function -  Edited Category Info for ['.$array['data']['name'].']', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        case 'changeOrder':
+            try {
+                $connect = new Dibi\Connection([
+                    'driver' => 'sqlite3',
+                    'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+                ]);
+                foreach ($array['data']['categories']['category'] as $key => $value) {
+                    if($value['order'] != $value['originalOrder']){
+                        $connect->query('
+                            UPDATE categories SET', [
+                                'order' => $value['order'],
+                            ], '
+                            WHERE id=?', $value['id']);
+                            writeLog('success', 'Category Editor Function - '.$value['name'].' Order Changed From '.$value['order'].' to '.$value['originalOrder'], $GLOBALS['organizrUser']['username']);
+                    }
+                }
+                writeLog('success', 'Category Editor Function - Category Order Changed', $GLOBALS['organizrUser']['username']);
+                return true;
+            } catch (Dibi\Exception $e) {
+                return false;
+            }
+            break;
+        default:
+            # code...
+            break;
+    }
+}
+function editUser($array){
+    return $array;
+}
+function allUsers(){
+    try {
+    	$connect = new Dibi\Connection([
+    		'driver' => 'sqlite3',
+    		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+    	]);
+        $users = $connect->fetchAll('SELECT * FROM users');
+        $groups = $connect->fetchAll('SELECT * FROM groups ORDER BY group_id ASC');
+        foreach ($users as $k => $v) {
+            //clear password from array
+            unset($users[$k]['password']);
+        }
+        $all['users'] = $users;
+        $all['groups'] = $groups;
+        return $all;
+    } catch (Dibi\Exception $e) {
+        return false;
+    }
+}
+function usernameTaken($username,$email){
+    try {
+    	$connect = new Dibi\Connection([
+    		'driver' => 'sqlite3',
+    		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+    	]);
+        $all = $connect->fetch('SELECT * FROM users WHERE username = ? COLLATE NOCASE OR email = ? COLLATE NOCASE',$username,$email);
+        return ($all) ? true : false;
+    } catch (Dibi\Exception $e) {
+        return false;
+    }
+}
+function createUser($username,$password,$defaults,$email=null) {
+    $email = ($email) ? $email : random_ascii_string(10).'@placeholder.eml';
+    try {
+        if(!usernameTaken($username,$email)){
+            $createDB = new Dibi\Connection([
+        		'driver' => 'sqlite3',
+        		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+        	]);
+            $userInfo = [
+                'username' => $username,
+                'password'  => password_hash($password, PASSWORD_BCRYPT),
+                'email' => $email,
+                'group' => $defaults['group'],
+                'group_id' => $defaults['group_id'],
+                'image' => gravatar($email),
+                'register_date' => $GLOBALS['currentTime'],
+            ];
+            $createDB->query('INSERT INTO [users]', $userInfo);
+            return true;
+        }else{
+            return false;
+        }
+
+    } catch (Dibi\Exception $e) {
+        return false;
+    }
+}
+//Cookie Function
+function coookie($type, $name, $value = '', $days = -1, $http = true){
+	if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https"){
+		$Secure = true;
+ 	   	$HTTPOnly = true;
+	}elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
+		$Secure = true;
+ 	   	$HTTPOnly = true;
+	} else {
+		$Secure = false;
+ 	   	$HTTPOnly = false;
+   }
+   if(!$http){ $HTTPOnly = false; }
+	$Path = '/';
+	$Domain = $_SERVER['HTTP_HOST'];
+	$Port = strpos($Domain, ':');
+	if ($Port !== false)  $Domain = substr($Domain, 0, $Port);
+	$Port = strpos($Domain, ':');
+	$check = substr_count($Domain, '.');
+	if($check >= 3){
+		if(is_numeric($Domain[0])){
+			$Domain = '';
+		}else{
+			$Domain = '.'.explode('.',$Domain)[1].'.'.explode('.',$Domain)[2].'.'.explode('.',$Domain)[3];
+		}
+	}elseif($check == 2){
+		$Domain = '.'.explode('.',$Domain)[1].'.'.explode('.',$Domain)[2];
+	}elseif($check == 1){
+		$Domain = '.' . $Domain;
+	}else{
+		$Domain = '';
+	}
+	if($type = 'set'){
+		$_COOKIE[$name] = $value;
+		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
+							. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
+							. (empty($Path) ? '' : '; path=' . $Path)
+							. (empty($Domain) ? '' : '; domain=' . $Domain)
+							. (!$Secure ? '' : '; secure')
+							. (!$HTTPOnly ? '' : '; HttpOnly'), false);
+	}elseif($type = 'delete'){
+		unset($_COOKIE[$name]);
+		header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
+							. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
+							. (empty($Path) ? '' : '; path=' . $Path)
+							. (empty($Domain) ? '' : '; domain=' . $Domain)
+							. (!$Secure ? '' : '; secure')
+							. (!$HTTPOnly ? '' : '; HttpOnly'), false);
+	}
+}
+function validateToken($token,$global=false){
+    //validate script
+    $userInfo = jwtParse($token);
+    $validated = $userInfo ? true : false;
+    if($validated == true){
+        if($global == true){
+            $GLOBALS['organizrUser'] = array(
+                "token"=>$token,
+                "tokenDate"=>$userInfo['tokenDate'],
+                "tokenExpire"=>$userInfo['tokenExpire'],
+                "username"=>$userInfo['username'],
+                "group"=>$userInfo['group'],
+                "groupID"=>$userInfo['groupID'],
+                "email"=>$userInfo['email'],
+                "image"=>$userInfo['image'],
+                "loggedin"=>true
+            );
+        }
+    }else{
+        //delete cookie & reload page
+        coookie('delete','organizrToken');
+        $GLOBALS['organizrUser'] = false;
+    }
+}
+function logout(){
+    coookie('delete','organizrToken');
+    $GLOBALS['organizrUser'] = false;
+    return true;
+}
+function getOrganizrUserToken(){
+    if(isset($_COOKIE['organizrToken'])){
+        //get token form cookie and validate
+        validateToken($_COOKIE['organizrToken'],true);
+    }else{
+        $GLOBALS['organizrUser'] = array(
+            "token"=>null,
+            "tokenDate"=>null,
+            "tokenExpire"=>null,
+            "username"=>"Guest",
+            "group"=>getGuest()['group'],
+            "groupID"=>getGuest()['group_id'],
+            "email"=>null,
+            "image"=>getGuest()['image'],
+            "loggedin"=>false
+        );
+    }
+}
+function qualifyRequest($accessLevelNeeded){
+    if(getUserLevel() <= $accessLevelNeeded){
+        return true;
+    }else{
+        return false;
+    }
+}
+function getUserLevel(){
+    $requesterToken = isset(getallheaders()['Token']) ? getallheaders()['Token'] : false;
+    //check token or API key
+    //If API key, return 0 for admin
+    if(strlen($requesterToken) == 20 && $requesterToken == $GLOBALS['organizrAPI']){
+        //DO API CHECK
+        return 0;
+    }elseif(isset($GLOBALS['organizrUser'])){
+        return $GLOBALS['organizrUser']['groupID'];
+    }
+    //all else fails?  return guest id
+    return 999;
+}
+function getOS(){
+	if(PHP_SHLIB_SUFFIX == "dll"){
+		return "win";
+	}else{
+		return "*nix";
+	}
+}
+function organizrStatus(){
+    $status = array();
+    $dependenciesActive = array();
+    $dependenciesInactive = array();
+    $extensions = array("PDO_SQLITE", "PDO", "SQLITE3", "zip", "cURL", "openssl", "simplexml", "json", "session");
+    $functions = array("hash", "fopen", "fsockopen", "fwrite", "fclose", "readfile");
+    foreach($extensions as $check){
+        if(extension_loaded($check)){
+            array_push($dependenciesActive,$check);
+        }else{
+            array_push($dependenciesInactive,$check);
+        }
+    }
+    foreach($functions as $check){
+        if(function_exists($check)){
+            array_push($dependenciesActive,$check);
+        }else{
+            array_push($dependenciesInactive,$check);
+        }
+    }
+    if(!file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
+        $status['status'] = "wizard";//wizard - ok for test
+    }
+    if(count($dependenciesInactive)>0 || !is_writable(dirname(__DIR__,2))){
+        $status['status'] = "dependencies";
+    }
+    $status['status'] = (!empty($status['status'])) ? $status['status'] : $status['status'] = "ok";
+    $status['writable'] = is_writable(dirname(__DIR__,2)) ? 'yes' : 'no';
+    $status['dependenciesActive'] = $dependenciesActive;
+    $status['dependenciesInactive'] = $dependenciesInactive;
+    $status['version'] = $GLOBALS['installedVersion'];
+    $status['os'] = getOS();
+    $status['php'] = phpversion();
+    return $status;
+}
+function allTabs(){
+    if(file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
+        try {
+        	$connect = new Dibi\Connection([
+        		'driver' => 'sqlite3',
+        		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+        	]);
+            $all['tabs'] = $connect->fetchAll('SELECT * FROM tabs ORDER BY `order` ASC');
+            $all['categories'] = $connect->fetchAll('SELECT * FROM categories ORDER BY `order` ASC');
+            $all['groups'] = $connect->fetchAll('SELECT * FROM groups ORDER BY `group_id` ASC');
+            return $all;
+        } catch (Dibi\Exception $e) {
+            return false;
+        }
+    }
+}
+function loadTabs(){
+    if(file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
+        try {
+        	$connect = new Dibi\Connection([
+        		'driver' => 'sqlite3',
+        		'database' => $GLOBALS['dbLocation'].$GLOBALS['dbName'],
+        	]);
+            $tabs = $connect->fetchAll('SELECT * FROM tabs WHERE `group_id` >= ? AND `enabled` = 1 ORDER BY `order` DESC',$GLOBALS['organizrUser']['groupID']);
+            $categories = $connect->fetchAll('SELECT * FROM categories ORDER BY `order` ASC');
+            $all['tabs'] = $tabs;
+            foreach ($tabs as $k => $v) {
+                $v['access_url'] = isset($v['url_local']) && $_SERVER['SERVER_ADDR'] == userIP() ? $v['url_local'] : $v['url'];
+            }
+            $count = array_map(function($element){
+                return $element['category_id'];
+            }, $tabs);
+            $count = (array_count_values($count));
+            foreach ($categories as $k => $v) {
+                $v['count'] = isset($count[$v['category_id']]) ?  $count[$v['category_id']] : 0;
+            }
+            $all['categories'] = $categories;
+            return $all;
+        } catch (Dibi\Exception $e) {
+            return false;
+        }
+    }
+}
+if(!function_exists('getallheaders')){
+    function getallheaders(){
+        $headers = array ();
+        foreach ($_SERVER as $name => $value){
+            if (substr($name, 0, 5) == 'HTTP_'){
+                $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
+            }
+        }
+        return $headers;
+    }
+}
+function writeLoginLog($username, $authType) {
+    if(file_exists($GLOBALS['organizrLoginLog'])) {
+        $getLog = str_replace("\r\ndate", "date", file_get_contents($GLOBALS['organizrLoginLog']));
+        $gotLog = json_decode($getLog, true);
+    }
+    $logEntryFirst = array('logType' => 'login_log', 'auth' => array(array('date' => date("Y-m-d H:i:s"), 'utc_date' => $GLOBALS['currentTime'], 'username' => $username, 'ip' => userIP(), 'auth_type' => $authType)));
+    $logEntry = array('date' => date("Y-m-d H:i:s"), 'utc_date' => $GLOBALS['currentTime'], 'username' => $username, 'ip' => userIP(), 'auth_type' => $authType);
+    if(isset($gotLog)) {
+        array_push($gotLog["auth"], $logEntry);
+        $writeFailLog = str_replace("date", "\r\ndate", json_encode($gotLog));
+    } else {
+        $writeFailLog = str_replace("date", "\r\ndate", json_encode($logEntryFirst));
+    }
+    file_put_contents($GLOBALS['organizrLoginLog'], $writeFailLog);
+};
+function writeLog($type='error', $message, $username=null) {
+    $username = ($username) ? $username : $GLOBALS['organizrUser']['username'];
+    if(file_exists($GLOBALS['organizrLog'])) {
+        $getLog = str_replace("\r\ndate", "date", file_get_contents($GLOBALS['organizrLog']));
+        $gotLog = json_decode($getLog, true);
+    }
+    $logEntryFirst = array('logType' => 'organizr_log', 'log_items' => array(array('date' => date("Y-m-d H:i:s"), 'utc_date' => $GLOBALS['currentTime'], 'type' => $type, 'username' => $username, 'ip' => userIP(), 'message' => $message)));
+    $logEntry = array('date' => date("Y-m-d H:i:s"), 'utc_date' => $GLOBALS['currentTime'], 'type' => $type, 'username' => $username, 'ip' => userIP(), 'message' => $message);
+    if(isset($gotLog)) {
+        array_push($gotLog["log_items"], $logEntry);
+        $writeFailLog = str_replace("date", "\r\ndate", json_encode($gotLog));
+    } else {
+        $writeFailLog = str_replace("date", "\r\ndate", json_encode($logEntryFirst));
+    }
+    file_put_contents($GLOBALS['organizrLog'], $writeFailLog);
+};
+function getLog($type,$reverse=true){
+    switch ($type) {
+        case 'login':
+        case 'loginLog':
+            $file = $GLOBALS['organizrLoginLog'];
+            $parent = 'auth';
+            break;
+        case 'org':
+        case 'organizrLog':
+            $file = $GLOBALS['organizrLog'];
+            $parent = 'log_items';
+        default:
+            break;
+    }
+    if(!file_exists($file)){
+        return false;
+    }
+    $getLog = str_replace("\r\ndate", "date", file_get_contents($file));
+    $gotLog = json_decode($getLog, true);
+    return ($reverse) ? array_reverse($gotLog[$parent]) : $gotLog[$parent];
+}
+function random_ascii_string($len){
+	$string = "";
+	$max = strlen($this->ascii)-1;
+	while($len-->0) { $string .= $this->ascii[mt_rand(0, $max)]; }
+	return $string;
+}
+function encrypt($password, $key = null) {
+    $key = (isset($GLOBALS['organizrHash'])) ? $GLOBALS['organizrHash'] : $key;
+    return openssl_encrypt($password, 'AES-256-CBC', $key, 0, fillString($key,16));
+}
+function decrypt($password, $key = null) {
+    $key = (isset($GLOBALS['organizrHash'])) ? $GLOBALS['organizrHash'] : $key;
+    return openssl_decrypt($password, 'AES-256-CBC', $key, 0, fillString($key,16));
+}
+function fillString($string, $length){
+    $filler = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*';
+    if(strlen($string) < $length){
+        $diff = $length - strlen($string);
+        $filler = substr($filler,0,$diff);
+        return $string.$filler;
+    }elseif(strlen($string) > $length){
+        return substr($string,0,$length);
+    }else{
+        return $string;
+    }
+    return $diff;
+}
+function userIP() {
+    if (isset($_SERVER['HTTP_CLIENT_IP']))
+        $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
+    else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
+        $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
+    else if(isset($_SERVER['HTTP_X_FORWARDED']))
+        $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
+    else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
+        $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
+    else if(isset($_SERVER['HTTP_FORWARDED']))
+        $ipaddress = $_SERVER['HTTP_FORWARDED'];
+    else if(isset($_SERVER['REMOTE_ADDR']))
+        $ipaddress = $_SERVER['REMOTE_ADDR'];
+    else
+        $ipaddress = 'UNKNOWN';
+    if (strpos($ipaddress, ',') !== false) {
+        list($first, $last) = explode(",", $ipaddress);
+        return $first;
+    }else{
+        return $ipaddress;
+    }
+
+}
+function arrayIP($string){
+    if (strpos($string, ',') !== false) {
+        $result = explode(",", $string);
+    }else{
+        $result = array($string);
+    }
+    foreach($result as &$ip){
+        $ip = is_numeric(substr($ip, 0, 1)) ? $ip : gethostbyname($ip);
+    }
+    return $result;
+}
+function auth(){
+    $debug = false; //CAREFUL WHEN SETTING TO TRUE AS THIS OPENS AUTH UP
+    $ban = isset($_GET['ban']) ? strtoupper($_GET['ban']) : "";
+    $whitelist = isset($_GET['whitelist']) ? $_GET['whitelist'] : false;
+    $blacklist = isset($_GET['blacklist']) ? $_GET['blacklist'] : false;
+    $group = isset($_GET['group']) ? $_GET['group'] : 0;
+    $currentIP = userIP();
+    $currentUser = $GLOBALS['organizrUser']['username'];
+    if ($whitelist) {
+        if(in_array($currentIP, arrayIP($whitelist))) {
+           !$debug ? exit(http_response_code(200)) : die("$currentIP Whitelist Authorized");
+    	}
+    }
+    if ($blacklist) {
+        if(in_array($currentIP, arrayIP($blacklist))) {
+           !$debug ? exit(http_response_code(401)) : die("$currentIP Blacklisted");
+    	}
+    }
+    if($group !== null){
+        if(qualifyRequest($group)){
+            !$debug ? exit(http_response_code(200)) : die("$currentUser on $currentIP Authorized");
+        }else{
+            !$debug ? exit(http_response_code(401)) : die("$currentUser on $currentIP Not Authorized");
+        }
+    }else{
+        !$debug ? exit(http_response_code(401)) : die("Not Authorized Due To No Parameters Set");
+    }
+}

+ 488 - 0
api/index.php

@@ -0,0 +1,488 @@
+<?php
+$generationTime = -microtime(true);
+//include functions
+require_once 'functions.php';
+//Set result array
+$result = array();
+//Get request method
+$method = $_SERVER['REQUEST_METHOD'];
+reset($_GET);
+$function = (key($_GET) ? str_replace("/","_",key($_GET)) : false);
+//Exit if $function is blank
+if($function === false){
+    $result['status'] = "error";
+    $result['statusText'] = "No API Path Supplied";
+    exit(json_encode($result));
+}
+$result['request'] = key($_GET);
+switch ($function) {
+    case 'v1_settings_page':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = $pageSettings;
+                    writeLog('success', 'Admin Function -  Accessed Settings Page', $GLOBALS['organizrUser']['username']);
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                    writeLog('error', 'Admin Function -  Tried to access Settings Page', $GLOBALS['organizrUser']['username']);
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_settings_settings_logs':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = $pageSettingsSettingsLogs;
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_settings_tab_editor_tabs':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = $pageSettingsTabEditorTabs;
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            case 'POST':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = editTabs($_POST);
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_settings_tab_editor_categories':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = $pageSettingsTabEditorCategories;
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            case 'POST':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = editCategories($_POST);
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_settings_user_manage_users':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = $pageSettingsUserManageUsers;
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            case 'POST':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = adminEditUser($_POST);
+                }elseif(qualifyRequest(998)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = editUser($_POST);
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_settings_user_manage_groups':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = $pageSettingsUserManageGroups;
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            case 'POST':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = adminEditGroup($_POST);
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_wizard_page':
+        switch ($method) {
+            case 'GET':
+                if(!file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = $pageWizard;
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'Wizard has already been run';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_dependencies_page':
+        switch ($method) {
+            case 'GET':
+                $result['status'] = 'success';
+                $result['statusText'] = 'success';
+                $result['data'] = $pageDependencies;
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_wizard_config':
+        switch ($method) {
+            case 'POST':
+                if(!file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = wizardConfig($_POST);
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'Wizard has already been run';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_login':
+        switch ($method) {
+            case 'POST':
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = login($_POST);
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_register':
+        switch ($method) {
+            case 'POST':
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = register($_POST);
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_login_page':
+        switch ($method) {
+            case 'GET':
+                $result['status'] = 'success';
+                $result['statusText'] = 'success';
+                $result['data'] = $pageLogin;
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_lockscreen':
+        switch ($method) {
+            case 'GET':
+                $result['status'] = 'success';
+                $result['statusText'] = 'success';
+                $result['data'] = $pageLockScreen;
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_login_log':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = getLog('loginLog');
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+	case 'v1_organizr_log':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = getLog('org');
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_user_list':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = allUsers();
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_tab_list':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = allTabs();
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_user_edit':
+        switch ($method) {
+            case 'POST':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = adminEditUser($_POST);
+                }elseif(qualifyRequest(998)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = editUser($_POST);
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_logout':
+        switch ($method) {
+            case 'GET':
+                $result['status'] = 'success';
+                $result['statusText'] = 'success';
+                $result['data'] = logout();
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_launch_organizr':
+        switch ($method) {
+            case 'GET':
+                $status = array();
+                $result['status'] = 'success';
+                $result['statusText'] = 'success';
+                $status['status'] = organizrStatus();
+                $status['user'] = $GLOBALS['organizrUser'];
+                $status['categories'] = loadTabs()['categories'];
+                $status['tabs'] = loadTabs()['tabs'];
+                $result['data'] = $status;
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+	case 'v1_auth':
+        switch ($method) {
+            case 'GET':
+                auth();
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    case 'v1_plugin':
+        switch ($method) {
+            case 'GET':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = 'plugin admin';
+                }elseif(qualifyRequest(998)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = 'plugin logged in';
+                }elseif(qualifyRequest(999)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = 'plugin guest';
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            case 'POST':
+                if(qualifyRequest(1)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = 'plugin admin';
+                }elseif(qualifyRequest(998)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = 'plugin logged in';
+                }elseif(qualifyRequest(999)){
+                    $result['status'] = 'success';
+                    $result['statusText'] = 'success';
+                    $result['data'] = 'plugin guest';
+                }else{
+                    $result['status'] = 'error';
+                    $result['statusText'] = 'API/Token invalid or not set';
+                    $result['data'] = null;
+                }
+                break;
+            default:
+                $result['status'] = 'error';
+                $result['statusText'] = 'The function requested is not defined for method: '.$method;
+                break;
+        }
+        break;
+    default:
+        //No Function Available
+        $result['status'] = 'error';
+        $result['statusText'] = 'function requested is not defined';
+        break;
+}
+//Set Default Result
+if(!$result){
+    $result['status'] = "error";
+    $result['error'] = "An error has occurred";
+}
+$result['generationDate'] = $GLOBALS['currentTime'];
+$generationTime += microtime(true);
+$result['generationTime'] = (sprintf('%f', $generationTime)*1000).'ms';
+//return JSON array
+exit(json_encode($result, JSON_HEX_QUOT | JSON_HEX_TAG));

+ 59 - 0
api/pages/dependencies.php

@@ -0,0 +1,59 @@
+<?php
+
+$pageDependencies = '
+<script>
+</script>
+<div class="container-fluid">
+    <div class="row bg-title">
+        <div class="col-lg-3 col-md-4 col-sm-4 col-xs-12">
+            <h4 class="page-title" lang="en">Organizr Dependency Check</h4>
+        </div>
+        <!-- /.col-lg-12 -->
+    </div>
+    <!--.row-->
+    <div class="row">
+        <div class="col-lg-8">
+            <div class="panel panel-danger">
+                <div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">Dependencies Missing</span>
+                    <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a> <a href="#" data-perform="panel-dismiss"><i class="ti-close"></i></a> </div>
+                </div>
+                <div class="panel-wrapper collapse in" aria-expanded="true">
+                    <div class="panel-body">
+                        <ul class="common-list" id="depenency-info"></ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+		<div class="col-lg-4">
+            <div class="panel panel-info">
+                <div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">Web Folder</span>
+                    <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a> <a href="#" data-perform="panel-dismiss"><i class="ti-close"></i></a> </div>
+                </div>
+                <div class="panel-wrapper collapse in" aria-expanded="true">
+                    <table class="table table-hover">
+                        <tbody>
+                            <tr>
+                                <td>'.dirname(__DIR__,2).'</td>
+                            </tr>
+                            <tr>
+                                <td id="web-folder" lang="en">Loading...</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+        <div class="col-lg-4">
+            <div class="panel panel-info">
+                <div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">Browser Information</span>
+                    <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a> <a href="#" data-perform="panel-dismiss"><i class="ti-close"></i></a> </div>
+                </div>
+                <div class="panel-wrapper collapse in" id="browser-info" aria-expanded="true"></div>
+            </div>
+        </div>
+
+    </div>
+    <!--./row-->
+</div>
+<!-- /.container-fluid -->
+';

+ 32 - 0
api/pages/lockscreen.php

@@ -0,0 +1,32 @@
+<?php
+
+$pageLockScreen = '
+<script>
+</script>
+<section id="lockScreen" class="lock-screen" oncontextmenu="return false;">
+  <div class="login-box">
+    <div class="white-box">
+      <form class="form-horizontal form-material" id="form-lockscreen">
+        <div class="form-group">
+          <div class="col-xs-12 text-center">
+            <div class="user-thumb text-center"> <img alt="thumbnail" class="img-circle" width="100" src="'.$GLOBALS['organizrUser']['image'].'">
+              <h3>'.$GLOBALS['organizrUser']['username'].'</h3>
+            </div>
+          </div>
+        </div>
+        <div class="form-group ">
+          <div class="col-xs-12">
+            <input name="username" class="form-control" type="hidden" value="'.$GLOBALS['organizrUser']['username'].'">
+            <input name="password" class="form-control" type="password" required="" placeholder="password" lang="en">
+          </div>
+        </div>
+        <div class="form-group text-center">
+          <div class="col-xs-12">
+            <button class="btn btn-info btn-lg btn-block text-uppercase waves-effect waves-light" type="submit" lang="en">Unlock</button>
+          </div>
+        </div>
+      </form>
+    </div>
+  </div>
+</section>
+';

+ 100 - 0
api/pages/login.php

@@ -0,0 +1,100 @@
+<?php
+
+$pageLogin = '
+<script>
+</script>
+<section id="wrapper" class="login-register">
+  <div class="login-box login-sidebar">
+    <div class="white-box">
+      <form class="form-horizontal form-material" id="loginform" onsubmit="return false;">
+        <a href="javascript:void(0)" class="text-center db"><img style="max-width: 350px;" src="https://sonflix.com/images/newsonflixlogo.png" alt="Home" /></a>
+
+        <div class="form-group m-t-40">
+          <div class="col-xs-12">
+            <input class="form-control" name="username" type="text" required="" placeholder="Username" autofocus>
+          </div>
+        </div>
+        <div class="form-group">
+          <div class="col-xs-12">
+            <input class="form-control" name="password" type="password" required="" placeholder="Password" lang="en">
+          </div>
+        </div>
+        <div class="form-group">
+          <div class="col-md-12">
+            <div class="checkbox checkbox-primary pull-left p-t-0">
+              <input id="checkbox-login" name="remember" type="checkbox" checked>
+              <label for="checkbox-signup" lang="en"> Remember Me </label>
+            </div>
+            <a href="javascript:void(0)" id="to-recover" class="text-dark pull-right"><i class="fa fa-lock m-r-5"></i> <span lang="en">Forgot pwd?</span></a> </div>
+        </div>
+        <div class="form-group text-center m-t-20">
+          <div class="col-xs-12">
+            <button class="btn btn-info btn-lg btn-block text-uppercase waves-effect waves-light login-button" type="submit" lang="en">Login</button>
+          </div>
+        </div>
+
+        <div class="form-group m-b-0">
+          <div class="col-sm-12 text-center">
+            <p><span lang="en">Don\'t have an account? </span><a href="#" class="text-primary m-l-5 to-register"><b lang="en">Sign Up</b></a></p>
+          </div>
+        </div>
+      </form>
+      <form class="form-horizontal form-material hidden" id="registerForm" onsubmit="return false;">
+        <div class="form-group m-t-40">
+          <div class="col-xs-12">
+            <input class="form-control" type="text" name="registrationPassword" required="" placeholder="Registration Password" lang="en" autofocus>
+          </div>
+        </div>
+        <div class="form-group">
+          <div class="col-xs-12">
+            <input class="form-control" name="username" type="text" required="" placeholder="Username" lang="en">
+          </div>
+        </div>
+        <div class="form-group">
+          <div class="col-xs-12">
+            <input class="form-control" name="email" type="text" required="" placeholder="Email" lang="en">
+          </div>
+        </div>
+        <div class="form-group">
+          <div class="col-xs-12">
+            <input class="form-control" name="password" type="password" required="" placeholder="Password" lang="en">
+          </div>
+        </div>
+        <div class="form-group text-center m-t-20">
+          <div class="col-xs-12">
+            <button class="btn btn-info btn-lg btn-block text-uppercase waves-effect waves-light register-button" type="submit" lang="en">Register</button>
+          </div>
+        </div>
+        <div class="form-group text-center m-t-20">
+          <div class="col-xs-12">
+            <button id="leave-registration" class="btn btn-primary btn-lg btn-block text-uppercase waves-effect waves-light" type="button" lang="en">Go Back</button>
+          </div>
+        </div>
+      </form>
+      <form class="form-horizontal" id="recoverform" action="">
+        <div class="form-group ">
+          <div class="col-xs-12">
+            <h3 lang="en">Recover Password</h3>
+            <p class="text-muted" lang="en">Enter your Email and instructions will be sent to you! </p>
+          </div>
+        </div>
+        <div class="form-group ">
+          <div class="col-xs-12">
+            <input class="form-control" type="text" required="" placeholder="Email" lang="en">
+          </div>
+        </div>
+        <div class="form-group text-center m-t-20">
+          <div class="col-xs-12">
+            <button class="btn btn-primary btn-lg btn-block text-uppercase waves-effect waves-light" type="submit" lang="en">Reset</button>
+          </div>
+        </div>
+        <div class="form-group text-center m-t-20">
+          <div class="col-xs-12">
+            <button id="leave-recover" class="btn btn-primary btn-lg btn-block text-uppercase waves-effect waves-light" type="button" lang="en">Go Back</button>
+          </div>
+        </div>
+      </form>
+    </div>
+  </div>
+</section>
+';

+ 109 - 0
api/pages/settings-settings-logs.php

@@ -0,0 +1,109 @@
+<?php
+
+$pageSettingsSettingsLogs = '
+<script>
+$(document).on("click", ".swapLog", function(e) {
+    var log = $(this).attr(\'data-name\')+\'Div\';
+    $(\'.logTable\').addClass(\'hidden\');
+    $(\'.\'+log).addClass(\'show\').removeClass(\'hidden\');
+	$(\'.swapLog\').removeClass(\'active\');
+	$(this).addClass(\'active\');
+});
+</script>
+<div class="btn-group m-b-20">
+    <button type="button" class="btn btn-default btn-outline waves-effect bg-theme-dark swapLog active" data-name="loginLog" lang="en">Login Log</button>
+    <button type="button" class="btn btn-default btn-outline waves-effect bg-theme-dark swapLog" data-name="orgLog" lang="en">Organizr Log</button>
+</div>
+<div class="white-box bg-theme-dark logTable loginLogDiv">
+    <h3 class="box-title m-b-0" lang="en">Login Logs</h3>
+    <div class="table-responsive">
+        <table id="loginLogTable" class="table table-striped">
+            <thead>
+                <tr>
+                    <th lang="en">Date</th>
+                    <th lang="en">Username</th>
+                    <th lang="en">IP Address</th>
+                    <th lang="en">Type</th>
+                </tr>
+            </thead>
+            <tbody></tbody>
+        </table>
+    </div>
+</div>
+<div class="white-box bg-theme-dark logTable orgLogDiv hidden">
+    <h3 class="box-title m-b-0" lang="en">Organizr Logs</h3>
+    <div class="table-responsive">
+        <table id="organizrLogTable" class="table table-striped">
+            <thead>
+                <tr>
+                    <th lang="en">Date</th>
+                    <th lang="en">Username</th>
+                    <th lang="en">IP Address</th>
+                    <th lang="en">Message</th>
+                    <th lang="en">Type</th>
+                </tr>
+            </thead>
+            <tbody></tbody>
+        </table>
+    </div>
+</div>
+<!-- /.container-fluid -->
+<script>
+//$.fn.dataTable.moment(\'DD-MMM-Y HH:mm:ss\');
+$("#loginLogTable").DataTable( {
+        "ajax": "api/?v1/login_log",
+        "columns": [
+            { data: \'utc_date\',
+                render: function ( data, type, row ) {
+                    if ( type === \'display\' || type === \'filter\' ) {
+                        var m = moment.tz(data, activeInfo.timezone);
+                        return moment(m).format(\'LLL\');
+                    }
+                    return data;
+                }
+            },
+            { "data": "username" },
+            { "data": "ip" },
+            { data: \'auth_type\',
+                render: function ( data, type, row ) {
+                    if ( type === \'display\' || type === \'filter\' ) {
+                        return logIcon(data);
+                    }
+                    return logIcon(data);
+                }
+            }
+        ],
+        "order": [[ 0, \'desc\' ]],
+} );
+$("#organizrLogTable").DataTable( {
+        "ajax": "api/?v1/organizr_log",
+            "columns": [
+            { data: \'utc_date\',
+                render: function ( data, type, row ) {
+                    // If display or filter data is requested, format the date
+                    if ( type === \'display\' || type === \'filter\' ) {
+                        var m = moment.tz(data, activeInfo.timezone);
+                        return moment(m).format(\'LLL\');
+                    }
+
+                // Otherwise the data type requested (`type`) is type detection or
+                // sorting data, for which we want to use the integer, so just return
+                // that, unaltered
+                return data;}
+                },
+            { "data": "username" },
+            { "data": "ip" },
+            { "data": "message" },
+            { data: \'type\',
+                render: function ( data, type, row ) {
+                    if ( type === \'display\' || type === \'filter\' ) {
+                        return logIcon(data);
+                    }
+                    return logIcon(data);
+                }
+            }
+        ],
+        "order": [[ 0, \'desc\' ]],
+} );
+</script>
+';

+ 84 - 0
api/pages/settings-tab-editor-categories.php

@@ -0,0 +1,84 @@
+<?php
+
+$pageSettingsTabEditorCategories = '
+<script>
+buildCategoryEditor();
+$( \'#categoryEditorTable\' ).sortable({
+    stop: function () {
+        var inputs = $(\'input.order\');
+        var nbElems = inputs.length;
+        $(\'input.order\').each(function(idx) {
+            $(this).val(idx + 1);
+        });
+        submitCategoryOrder();
+    }
+});
+</script>
+<div class="panel bg-theme-dark panel-info">
+    <div class="panel-heading">
+        <span lang="en">Category Editor</span>
+        <button type="button" class="btn btn-success btn-circle pull-right popup-with-form m-r-5" href="#new-category-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+    </div>
+    <div class="table-responsive">
+        <form id="submit-categories-form" onsubmit="return false;">
+            <table class="table table-hover manage-u-table">
+                <thead>
+                    <tr>
+                        <th width="70" class="text-center">#</th>
+                        <th lang="en">NAME</th>
+                        <th lang="en" style="text-align:center">TABS</th>
+                        <th lang="en" style="text-align:center">DEFAULT</th>
+                        <th lang="en" style="text-align:center">EDIT</th>
+                        <th lang="en" style="text-align:center">DELETE</th>
+                    </tr>
+                </thead>
+                <tbody id="categoryEditorTable"></tbody>
+            </table>
+        </form>
+    </div>
+</div>
+<form id="new-category-form" class="mfp-hide white-popup-block mfp-with-anim">
+    <h1 lang="en">Add New Category</h1>
+    <fieldset style="border:0;">
+        <div class="form-group">
+            <label class="control-label" for="new-category-form-inputNameNew" lang="en">Category Name</label>
+            <input type="text" class="form-control" id="new-category-form-inputNameNew" name="name" required="" autofocus>
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="new-category-form-inputImageNew" lang="en">Category Image</label>
+            <input type="text" class="form-control" id="new-category-form-inputImageNew" name="image"  required="">
+        </div>
+    </fieldset>
+    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Category</span></button>
+    <div class="clearfix"></div>
+</form>
+<form id="edit-category-form" class="mfp-hide white-popup-block mfp-with-anim">
+    <input type="hidden" name="id" value="">
+    <h1 lang="en">Edit Category</h1>
+    <fieldset style="border:0;">
+        <div class="form-group">
+            <label class="control-label" for="edit-category-form-inputName" lang="en">Category Name</label>
+            <input type="text" class="form-control" id="edit-category-form-inputName" name="name" required="" autofocus>
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="edit-category-form-inputImage" lang="en">Category Image</label>
+            <div class="panel panel-info">
+                <div class="panel-heading">
+                    <span lang="en">Image Legend</span>
+                    <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-plus"></i></a></div>
+                </div>
+                <div class="panel-wrapper collapse" aria-expanded="false">
+                    <div class="panel-body">
+                        <p lang="en">You may use an image or icon in thie field</p>
+                        <p lang="en">For images, use the following format:</p><code>url::path/to/image</code>
+                        <p lang="en">For icons, use the following format:</p><code>icon-type::icon-name</code> i.e. <code>fontawesome::home</code>
+                    </div>
+                </div>
+            </div>
+            <input type="text" class="form-control" id="edit-category-form-inputImage" name="image"  required="">
+        </div>
+    </fieldset>
+    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Edit Group</span></button>
+    <div class="clearfix"></div>
+</form>
+';

+ 83 - 0
api/pages/settings-tab-editor-tabs.php

@@ -0,0 +1,83 @@
+<?php
+
+$pageSettingsTabEditorTabs = '
+<script>
+buildTabEditor();
+$( \'#tabEditorTable\' ).sortable({
+    stop: function () {
+        var inputs = $(\'input.order\');
+        var nbElems = inputs.length;
+        $(\'input.order\').each(function(idx) {
+            $(this).val(idx + 1);
+        });
+        submitTabOrder();
+    }
+});
+</script>
+<div class="panel bg-theme-dark panel-info">
+    <div class="panel-heading">
+        <span lang="en">Tab Editor</span>
+        <button type="button" class="btn btn-success btn-circle pull-right popup-with-form m-r-5" href="#new-tab-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+    </div>
+    <div class="table-responsive">
+        <form id="submit-tabs-form" onsubmit="return false;">
+            <table class="table table-hover manage-u-table">
+                <thead>
+                    <tr>
+                        <th width="70" class="text-center">#</th>
+                        <th lang="en">NAME</th>
+                        <th lang="en">CATEGORY</th>
+                        <th lang="en">GROUP</th>
+                        <th lang="en">TYPE</th>
+                        <th lang="en style="text-align:center"">DEFAULT</th>
+                        <th lang="en" style="text-align:center">ACTIVE</th>
+                        <th lang="en" style="text-align:center">SPLASH</th>
+                        <th lang="en" style="text-align:center">EDIT</th>
+                        <th lang="en" style="text-align:center">DELETE</th>
+                    </tr>
+                </thead>
+                <tbody id="tabEditorTable"></tbody>
+            </table>
+        </form>
+    </div>
+</div>
+<form id="new-tab-form" class="mfp-hide white-popup-block mfp-with-anim">
+    <h1 lang="en">Add New Tab</h1>
+    <fieldset style="border:0;">
+        <div class="form-group">
+            <label class="control-label" for="new-tab-form-inputNameNew" lang="en">Tab Name</label>
+            <input type="text" class="form-control" id="new-tab-form-inputNameNew" name="tabName" required="" autofocus>
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="new-tab-form-inputURLNew" lang="en">Tab URL</label>
+            <input type="text" class="form-control" id="new-tab-form-inputURLNew" name="tabURL"  required="">
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="new-tab-form-inputImageNew" lang="en">Tab Image</label>
+            <input type="text" class="form-control" id="new-tab-form-inputImageNew" name="tabImage"  required="">
+        </div>
+    </fieldset>
+    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewTab" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Tab</span></button>
+    <div class="clearfix"></div>
+</form>
+<form id="edit-tab-form" class="mfp-hide white-popup-block mfp-with-anim">
+    <input type="hidden" name="id" value="x">
+    <h1 lang="en">Edit Tab</h1>
+    <fieldset style="border:0;">
+        <div class="form-group">
+            <label class="control-label" for="edit-tab-form-inputName" lang="en">Tab Name</label>
+            <input type="text" class="form-control" id="edit-tab-form-inputName" name="tabName" required="" autofocus>
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="edit-tab-form-inputURL" lang="en">Tab URL</label>
+            <input type="text" class="form-control" id="edit-tab-form-inputURL" name="tabURL"  required="">
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="edit-tab-form-inputImage" lang="en">Tab Image</label>
+            <input type="text" class="form-control" id="edit-tab-form-inputImage" name="tabImage"  required="">
+        </div>
+    </fieldset>
+    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editTab" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Edit Group</span></button>
+    <div class="clearfix"></div>
+</form>
+';

+ 60 - 0
api/pages/settings-user-manage-groups.php

@@ -0,0 +1,60 @@
+<?php
+
+$pageSettingsUserManageGroups = '
+<script>
+    buildGroupManagement();
+</script>
+<div class="panel bg-theme-dark panel-info">
+    <div class="panel-heading">
+        <span lang="en">MANAGE GROUPS</span>
+        <button type="button" class="btn btn-info btn-circle pull-right popup-with-form" href="#new-group-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+    </div>
+    <div class="table-responsive">
+        <table class="table table-hover manage-u-table">
+            <thead>
+                <tr>
+                    <th width="70" class="text-center">#</th>
+                    <th lang="en">GROUP NAME</th>
+                    <th lang="en">USERS</th>
+                    <th lang="en">DEFAULT</th>
+                    <th lang="en">EDIT</th>
+                    <th lang="en">DELETE</th>
+                </tr>
+            </thead>
+            <tbody id="manageGroupTable"></tbody>
+        </table>
+    </div>
+</div>
+<form id="new-group-form" class="mfp-hide white-popup-block mfp-with-anim">
+    <input type="hidden" id="newGroupID" name="groupID" value="0">
+    <input type="hidden" name="groupDefault" value="0" required="">
+    <h1 lang="en">Add New Group</h1>
+    <fieldset style="border:0;">
+        <div class="form-group">
+            <label class="control-label" for="new-group-form-inputName" lang="en">Group Name</label>
+            <input type="text" class="form-control" id="new-group-form-inputName" name="groupName" required="" autofocus> </div>
+        <div class="form-group">
+            <label class="control-label" for="new-group-form-inputImage" lang="en">Group Image</label>
+            <input type="text" class="form-control" id="new-group-form-inputImage" name="groupImage" required=""> </div>
+    </fieldset>
+    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewGroup" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Group</span></button>
+    <div class="clearfix"></div>
+</form>
+<form id="edit-group-form" class="mfp-hide white-popup-block mfp-with-anim">
+    <input type="hidden" name="id" value="x">
+    <input type="hidden" name="oldGroupName" value="x">
+    <h1 lang="en">Edit Group</h1>
+    <fieldset style="border:0;">
+        <div class="form-group">
+            <label class="control-label" for="edit-group-form-inputEditGroupName" lang="en">Group Name</label>
+            <input type="text" class="form-control" id="edit-group-form-inputEditGroupName" name="groupName" required="" autofocus>
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="edit-group-form-inputEditGroupImage" lang="en">Group Image</label>
+            <input type="text" class="form-control" id="edit-group-form-inputEditGroupImage" name="groupImage"  required="">
+        </div>
+    </fieldset>
+    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editGroup" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Edit Group</span></button>
+    <div class="clearfix"></div>
+</form>
+';

+ 48 - 0
api/pages/settings-user-manage-users.php

@@ -0,0 +1,48 @@
+<?php
+
+$pageSettingsUserManageUsers = '
+<script>
+    buildUserManagement();
+</script>
+<div class="panel bg-theme-dark panel-info">
+    <div class="panel-heading">
+        <span lang="en">MANAGE USERS</span>
+        <button type="button" class="btn btn-info btn-circle pull-right popup-with-form" href="#new-user-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+    </div>
+    <div class="table-responsive">
+        <table class="table table-hover manage-u-table">
+            <thead>
+                <tr>
+                    <th width="70" class="text-center">#</th>
+                    <th lang="en">NAME & EMAIL</th>
+                    <th lang="en">ADDED</th>
+                    <th lang="en">GROUP</th>
+                    <th lang="en">EDIT</th>
+                    <th lang="en">EMAIL</th>
+                    <th lang="en">DELETE</th>
+                </tr>
+            </thead>
+            <tbody id="manageUserTable"></tbody>
+        </table>
+    </div>
+</div>
+<form id="new-user-form" class="mfp-hide white-popup-block mfp-with-anim">
+    <h1 lang="en">Add New User</h1>
+    <fieldset style="border:0;">
+        <div class="form-group">
+            <label class="control-label" for="new-user-form-inputUsername" lang="en">Username</label>
+            <input type="text" class="form-control" id="new-user-form-inputUsername" name="username" required="" autofocus>
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="new-user-form-inputEmail" lang="en">Email</label>
+            <input type="email" class="form-control" id="new-user-form-inputEmail" name="email"  required="">
+        </div>
+        <div class="form-group">
+            <label class="control-label" for="new-user-form-inputPassword" lang="en">Password</label>
+            <input type="password" class="form-control" id="new-user-form-inputPassword" name="password"  required="">
+        </div>
+    </fieldset>
+    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewUser" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add User</span></button>
+    <div class="clearfix"></div>
+</form>
+';

+ 193 - 0
api/pages/settings.php

@@ -0,0 +1,193 @@
+<?php
+if(file_exists('config'.DIRECTORY_SEPARATOR.'config.php')){
+$pageSettings = '
+<script>
+
+    (function() {
+        updateCheck();
+        [].slice.call(document.querySelectorAll(\'.sttabs\')).forEach(function(el) {
+            new CBPFWTabs(el);
+        });
+    })();
+</script>
+<div class="container-fluid">
+    <div class="row bg-title">
+        <div class="col-lg-3 col-md-4 col-sm-4 col-xs-12">
+            <h4 class="page-title" lang="en">Organizr Settings</h4>
+        </div>
+        <div class="col-lg-9 col-sm-8 col-md-8 col-xs-12">
+            <ol id="settingsBreadcrumb" class="breadcrumb">
+                <li lang="en">Settings</li>
+                <li lang="en">Tab Editor</li>
+            </ol>
+        </div>
+        <!-- /.col-lg-12 -->
+    </div>
+    <!--.row-->
+    <div class="row">
+
+
+
+    <!-- Tabstyle start -->
+    <section class="">
+        <div class="sttabs tabs-style-flip">
+            <nav>
+                <ul>
+                    <li onclick="changeSettingsMenu(\'Settings::Tab Editor\')"><a href="#settings-main-tab-editor" class="sticon ti-layout-tab-v"><span lang="en">Tab Editor</span></a></li>
+                    <li onclick="changeSettingsMenu(\'Settings::Customize\')"><a href="#settings-main-customize" class="sticon ti-paint-bucket"><span lang="en">Customize</span></a></li>
+                    <li onclick="changeSettingsMenu(\'Settings::User Management\')"><a href="#settings-main-user-management" class="sticon ti-user"><span lang="en">User Management</span></a></li>
+					<li onclick="changeSettingsMenu(\'Settings::Plugins\')"><a href="#settings-main-plugins" class="sticon ti-plug"><span lang="en">Plugins</span></a></li>
+                    <li onclick="changeSettingsMenu(\'Settings::System Settings\')"><a href="#settings-main-system-settings" class="sticon ti-settings"><span lang="en">System Settings</span></a></li>
+                </ul>
+            </nav>
+            <div class="content-wrap">
+                <section id="settings-main-tab-editor">
+                    <ul class="nav customtab2 nav-tabs" role="tablist">
+                        <li onclick="changeSettingsMenu(\'Settings::Tab Editor::Tabs\');loadTabEditor();" role="presentation" class=""><a href="#settings-tab-editor-tabs" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-layout-tab-v"></i></span><span class="hidden-xs" lang="en"> Tabs</span></a>
+                        </li>
+                        <li onclick="changeSettingsMenu(\'Settings::Tab Editor::Categories\');loadCategoryEditor();" role="presentation" class=""><a href="#settings-tab-editor-categories" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-layout-list-thumb"></i></span><span class="hidden-xs" lang="en"> Categories</span></a>
+                        </li>
+                    </ul>
+                    <!-- Tab panes -->
+                    <div class="tab-content">
+                        <div role="tabpanel" class="tab-pane fade" id="settings-tab-editor-tabs">
+                            <h2 lang="en">Loading...</h2>
+                            <div class="clearfix"></div>
+                        </div>
+                        <div role="tabpanel" class="tab-pane fade" id="settings-tab-editor-categories">
+                            <h2 lang="en">Loading...</h2>
+                        </div>
+                    </div>
+                </section>
+                <section id="settings-main-customize">
+                    <h2 lang="en">Customize Organizr</h2>
+                </section>
+                <section id="settings-main-user-management">
+                    <ul class="nav customtab2 nav-tabs" role="tablist">
+                        <li onclick="changeSettingsMenu(\'Settings::User Management::Manage Users\');loadUserManagement();" role="presentation" class=""><a href="#settings-user-manage-users" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-id-badge"></i></span><span class="hidden-xs" lang="en"> Manage Users</span></a>
+                        </li>
+                        <li onclick="changeSettingsMenu(\'Settings::User Management::Manage Groups\');loadGroupManagement();" role="presentation" class=""><a href="#settings-user-manage-groups" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-briefcase"></i></span><span class="hidden-xs" lang="en"> Manage Groups</span></a>
+                        </li>
+                    </ul>
+                    <!-- Tab panes -->
+                    <div class="tab-content">
+                        <div role="tabpanel" class="tab-pane fade" id="settings-user-manage-users">
+                            <h2 lang="en">Loading...</h2>
+                            <div class="clearfix"></div>
+                        </div>
+                        <div role="tabpanel" class="tab-pane fade" id="settings-user-manage-groups">
+                            <h2 lang="en">Loading...</h2>
+                            <div class="clearfix"></div>
+                        </div>
+                    </div>
+                </section>
+                <section id="settings-main-plugins">
+                    <h2 lang="en">Plugins</h2>
+                </section>
+                <section id="settings-main-system-settings">
+                    <ul class="nav customtab2 nav-tabs" role="tablist">
+                        <li onclick="changeSettingsMenu(\'Settings::System Settings::About\')" role="presentation" class="active"><a href="#settings-settings-about" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true"><span class="visible-xs"><i class="ti-info-alt"></i></span><span class="hidden-xs" lang="en"> About</span></a>
+                        </li>
+                        <li onclick="changeSettingsMenu(\'Settings::System Settings::Main\')" role="presentation" class=""><a href="#settings-settings-main" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-settings"></i></span><span class="hidden-xs" lang="en"> Main</span></a>
+                        </li>
+                        <li onclick="changeSettingsMenu(\'Settings::System Settings::Logs\');loadLogs();" role="presentation" class=""><a href="#settings-settings-logs" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-receipt"></i></span><span class="hidden-xs" lang="en"> Logs</span></a>
+                        </li>
+                        <li onclick="changeSettingsMenu(\'Settings::System Settings::Updates\')" role="presentation" class=""><a href="#settings-settings-updates" aria-controls="profile" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-package"></i></span> <span class="hidden-xs" lang="en">Updates</span></a>
+                        </li>
+                        <li onclick="changeSettingsMenu(\'Settings::System Settings::Donate\')" role="presentation" class=""><a href="#settings-settings-donate" aria-controls="profile" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-money"></i></span> <span class="hidden-xs" lang="en">Donate</span></a>
+                        </li>
+                    </ul>
+                    <!-- Tab panes -->
+                    <div class="tab-content">
+                        <div role="tabpanel" class="tab-pane fade" id="settings-settings-main">
+                            <h2 lang="en">Main Settings</h2>
+                            <div class="clearfix"></div>
+                        </div>
+                        <div role="tabpanel" class="tab-pane fade" id="settings-settings-logs">
+                            <h2 lang="en">Loading...</h2>
+                            <div class="clearfix"></div>
+                        </div>
+                        <div role="tabpanel" class="tab-pane fade active in" id="settings-settings-about">
+							<div class="row">
+								<div class="col-lg-6 col-sm-12 col-md-6">
+									<div class="panel bg-theme-dark">
+										<div class="p-30">
+											<div class="row">
+												<div class="col-xs-12"><img src="plugins/images/organizr/logo-wide.png" alt="organizr" class="img-responsive"></div>
+											</div>
+										</div>
+										<hr class="m-t-10">
+										<div class="p-20 text-center">
+											<p lang="en">Below you will find all the links for everything that has to do with Organizr</p>
+										</div>
+										<hr>
+										<ul class="dp-table profile-social-icons">
+											<li><a href="https://organizr.us" target="_blank"><i class="mdi mdi-web mdi-24px"></i></a></li>
+											<li><a href="https://reddit.com/r/organizr" target="_blank"><i class="mdi mdi-reddit mdi-24px"></i></a></li>
+											<li><a href="https://organizr.us/discord" target="_blank"><i class="mdi mdi-discord mdi-24px"></i></a></li>
+											<li><a href="https://github.com/causefx/organizr" target="_blank"><i class="mdi mdi-github-box mdi-24px"></i></a></li>
+										</ul>
+									</div>
+								</div>
+                                <div class="col-lg-6 col-sm-12 col-md-6">
+                                    <div class="white-box bg-theme-dark">
+                                        <h3 class="box-title" lang="en">Information</h3>
+                                        <ul class="feeds">
+                                            <li><div class="bg-info"><i class="mdi mdi-webpack mdi-24px text-white"></i></div><span class="text-muted" lang="en">Organizr Version</span> '.$GLOBALS['installedVersion'].'</li>
+                                            <li><div class="bg-info"><i class="mdi mdi-github-box mdi-24px text-white"></i></div><span class="text-muted" lang="en">Organizr Branch</span> '.$GLOBALS['branch'].'</li>
+                                            <li><div class="bg-info"><i class="mdi mdi-database mdi-24px text-white"></i></div><span class="text-muted" lang="en">Database Location</span> '.$GLOBALS['dbLocation'].$GLOBALS['dbName'].'</li>
+                                            <hr class="m-t-10">
+                                            <li><div class="bg-info"><i class="mdi mdi-language-php mdi-24px text-white"></i></div><span class="text-muted" lang="en">PHP Version</span> '.phpversion().'</li>
+                                            <li><div class="bg-info"><i class="mdi mdi-package-variant-closed mdi-24px text-white"></i></div><span class="text-muted" lang="en">Webserver Version</span> '.$_SERVER['SERVER_SOFTWARE'].'</li>
+                                            <hr class="m-t-10">
+                                            <li><div class="bg-info"><i class="mdi mdi-account-card-details mdi-24px text-white"></i></div><span class="text-muted" lang="en">License</span> '.ucwords($GLOBALS['license']).'</li>
+
+                                        </ul>
+                                    </div>
+                                </div>
+							</div>
+                            <div class="clearfix"></div>
+                        </div>
+                        <div role="tabpanel" class="tab-pane fade" id="settings-settings-donate">
+                            <div class="row">
+                                <div class="col-md-3 col-sm-6 col-xs-12">
+                                    <div class="white-box bg-theme-dark">
+                                        <h1 class="m-t-0"><i class="fa fa-cc-visa text-info"></i></h1>
+                                        <h2>**** **** **** 2150</h2> <span class="pull-right">Expiry date: 10/16</span> <span class="font-500">Johnathan Doe</span> </div>
+                                </div>
+                                <div class="col-md-3 col-sm-6 col-xs-12">
+                                    <div class="white-box">
+                                        <h1 class="m-t-0"><i class="fa fa-cc-mastercard text-danger"></i></h1>
+                                        <h2>**** **** **** 2150</h2> <span class="pull-right">Expiry date: 10/16</span> <span class="font-500">Johnathan Doe</span> </div>
+                                </div>
+                                <div class="col-md-3 col-sm-6 col-xs-12">
+                                    <div class="white-box">
+                                        <h1 class="m-t-0"><i class="fa fa-cc-discover text-success"></i></h1>
+                                        <h2>**** **** **** 2150</h2> <span class="pull-right">Expiry date: 10/16</span> <span class="font-500">Johnathan Doe</span> </div>
+                                </div>
+                                <div class="col-md-3 col-sm-6 col-xs-12">
+                                    <div class="white-box">
+                                        <h1 class="m-t-0"><i class="fa fa-cc-amex text-warning"></i></h1>
+                                        <h2>**** **** **** 2150</h2> <span class="pull-right">Expiry date: 10/16</span> <span class="font-500">Johnathan Doe</span> </div>
+                                </div>
+                            </div>
+                            <div class="clearfix"></div>
+                        </div>
+                        <div role="tabpanel" class="tab-pane fade" id="settings-settings-updates">
+                            <div id="githubVersions"></div>
+                            <div class="clearfix"></div>
+                        </div>
+                    </div>
+                </section>
+            </div>
+            <!-- /content -->
+        </div>
+        <!-- /tabs -->
+    </section>
+
+    </div>
+    <!--./row-->
+</div>
+<!-- /.container-fluid -->
+';
+}

+ 363 - 0
api/pages/wizard.php

@@ -0,0 +1,363 @@
+<?php
+
+$pageWizard = '
+<script>
+    (function() {
+        $(\'#adminValidator\').wizard({
+            onInit: function() {
+                $(\'#validation\').formValidation({
+                    framework: \'bootstrap\',
+                    fields: {
+                        username: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The username is required\'
+                                },
+                                stringLength: {
+                                    min: 3,
+                                    max: 30,
+                                    message: \'The username must be more than 2 and less than 30 characters long\'
+                                },
+                                regexp: {
+                                    regexp: /^[a-zA-Z0-9_\.]+$/,
+                                    message: \'The username can only consist of alphabetical, number, dot and underscore\'
+                                }
+                            }
+                        },
+                        license: {
+                            validators: {
+                                regexp: {
+                                    regexp: /^[a-zA-Z0-9_\.]+$/,
+                                    message: \'Please choose a license\'
+                                }
+                            }
+                        },
+                        email: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The email address is required\'
+                                },
+                                emailAddress: {
+                                    message: \'The input is not a valid email address\'
+                                }
+                            }
+                        },
+                        key: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The key hash is required\'
+                                },
+                                stringLength: {
+                                    min: 3,
+                                    max: 30,
+                                    message: \'The key hash must be more than 2 and less than 30 characters long\'
+                                }
+                            }
+                        },
+                        location: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The database location is required\'
+                                }
+                            }
+                        },
+                        dbName: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The Database Name is required\'
+                                },
+                                stringLength: {
+                                    min: 2,
+                                    max: 30,
+                                    message: \'The Database Name must be more than 1 and less than 30 characters long\'
+                                },
+                                regexp: {
+                                    regexp: /^[a-zA-Z0-9_\.]+$/,
+                                    message: \'The Database Name can only consist of alphabetical, number, dot and underscore\'
+                                }
+                            }
+                        },
+                        api: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The API Key is required\'
+                                },
+                                stringLength: {
+                                    min: 20,
+                                    max: 20,
+                                    message: \'The API Key must be 20 characters long\'
+                                }
+                            }
+                        },
+                        registrationPassword: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The registration password is required\'
+                                }
+                            }
+                        },
+                        password: {
+                            validators: {
+                                notEmpty: {
+                                    message: \'The password is required\'
+                                },
+                                different: {
+                                    field: \'username\',
+                                    message: \'The password cannot be the same as username\'
+                                }
+                            }
+                        }
+                    }
+                });
+            },
+            validator: function() {
+                var fv = $(\'#validation\').data(\'formValidation\');
+                var $this = $(this);
+                // Validate the container
+                fv.validateContainer($this);
+                var isValidStep = fv.isValidContainer($this);
+                if (isValidStep === false || isValidStep === null) {
+                    return false;
+                }
+                return true;
+            },
+            onFinish: function() {
+                //$(\'#validation\').submit();
+                var post = $( \'#validation\' ).serializeArray();
+                console.log( post );
+                organizrAPI(\'POST\',\'api/?v1/wizard_config\',post).success(function(data) {
+            		var html = JSON.parse(data);
+                    console.log(html.data);
+                    if(html.data == true){
+                        location.reload();
+                    }else{
+                        console.error(\'Organizr Function: Signup Error Occured\');
+                    }
+            	}).fail(function(xhr) {
+            		console.error("Organizr Function: Connection Failed");
+            	});
+            }
+        });
+        generateAPI();
+        $( ".wizardInput" ).focusout(function() {
+            var value = $(this).val();
+            var name = $(this).attr(\'name\');
+            if (typeof value !== \'undefined\' && typeof name !== \'undefined\') {
+                $(\'#verify-\'+name).text(value);
+            }
+        });
+    })();
+</script>
+<div class="container-fluid">
+    <div class="row bg-title">
+        <div class="col-lg-3 col-md-4 col-sm-4 col-xs-12">
+            <h4 class="page-title">Organizr Setup Wizard</h4>
+        </div>
+        <!-- /.col-lg-12 -->
+    </div>
+    <!--.row-->
+    <div class="row">
+        <div class="col-sm-12">
+            <div class="white-box">
+                <h3 class="box-title m-b-0" lang="en">Admin Creation</h3>
+                <div class="wizard" id="adminValidator">
+                    <ul class="wizard-steps" role="tablist">
+                        <li class="active" role="tab">
+                            <h4><span><i class="ti-direction"></i></span><item lang="en">Install Type</item></h4>
+                        </li>
+                        <li role="tab">
+                            <h4><span><i class="ti-user"></i></span><item lang="en">Admin Info</item></h4>
+                        </li>
+                        <li role="tab">
+                            <h4><span><i class="ti-key"></i></span><item lang="en">Security</item></h4>
+                        </li>
+                        <li role="tab">
+                            <h4><span><i class="ti-server"></i></span><item lang="en">Database</item></h4>
+                        </li>
+                        <li role="tab">
+                            <h4><span><i class="ti-check"></i></span><item lang="en">Verify</item></h4>
+                        </li>
+                    </ul>
+                    <form class="form-horizontal" id="validation" name="validation" onsubmit="return:false;">
+                        <div class="wizard-content">
+                            <div class="wizard-pane active" role="tabpanel">
+                                <div class="form-group">
+                                    <label for="license" lang="en">Install Type</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-direction"></i></div>
+                                        <select name="license" class="form-control wizardInput" id="form-license">
+                                            <option lang="en">Choose License</option>
+                                            <option lang="en" value="personal">Personal</option>
+                                            <option lang="en" value="business">Business</option>
+                                        </select>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="wizard-pane" role="tabpanel">
+                                <div class="form-group">
+                                    <label for="username" lang="en">Username</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-user"></i></div>
+                                        <input type="text" class="form-control wizardInput" name="username" id="form-username">
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label for="email lang="en"">Email</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-email"></i></div>
+                                        <input type="text" class="form-control wizardInput" name="email" id="form-email">
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label for="passwrod" lang="en">Password</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-lock"></i></div>
+                                        <input type="password" class="form-control wizardInput" name="password" id="form-password">
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="wizard-pane" role="tabpanel">
+                                <div class="panel panel-info">
+                                    <div class="panel-heading">
+                                        <i class="ti-alert fa-fw"></i> <span lang="en">Notice</span>
+                                        <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a> <a href="#" data-perform="panel-dismiss"><i class="ti-close"></i></a> </div>
+                                    </div>
+                                    <div class="panel-wrapper collapse in" aria-expanded="true">
+                                        <div class="panel-body">
+                                            <p lang="en">The Hash Key will be used to decrypt all passwords etc... on the server.</p>
+                                            <p lang="en">The API Key will be used for all calls to organizr for the UI. [Auto-Generated]</p>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label for="key" lang="en">Hash Key</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-key"></i></div>
+                                        <input type="password" class="form-control wizardInput" name="hashKey" id="form-hashKey">
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label for="key" lang="en">Registration Password</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-key"></i></div>
+                                        <input type="password" class="form-control wizardInput" name="registrationPassword" id="form-registrationPassword">
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label for="key" lang="en">API Key</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-key"></i></div>
+                                        <input type="password" class="form-control wizardInput disabled" name="api" id="form-api">
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="wizard-pane" role="tabpanel">
+                                <div class="panel panel-danger">
+                                    <div class="panel-heading">
+                                        <i class="ti-alert fa-fw"></i> <span lang="en">Attenetion</span>
+                                        <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a> <a href="#" data-perform="panel-dismiss"><i class="ti-close"></i></a> </div>
+                                    </div>
+                                    <div class="panel-wrapper collapse in" aria-expanded="true">
+                                        <div class="panel-body">
+                                            <p lang="en">The Database will contain sensitive information.  Please place in directory outside of root Web Directory.</p>
+                                            <p lang="en">Parent Directory: <code>'.dirname(__DIR__, 3).'</code>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label for="dbName" lang="en">Database Name</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-server"></i></div>
+                                        <input type="text" class="form-control wizardInput" name="dbName" id="form-dbName" placeholder="orgDBname">
+                                    </div>
+                                </div>
+                                <div class="form-group">
+                                    <label for="location" lang="en">Database Location</label>
+                                    <div class="input-group">
+                                        <div class="input-group-addon"><i class="ti-server"></i></div>
+                                        <input type="text" class="form-control wizardInput" name="location" id="form-location" placeholder="'.dirname(__DIR__, 3).'">
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="wizard-pane" role="tabpanel">
+                                <div class="row">
+                                    <div class="col-md-6">
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3 lang="en"">License:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static" id="verify-license"></p>
+                                            </div>
+                                        </div>
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3 lang="en"">Username:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static" id="verify-username"></p>
+                                            </div>
+                                        </div>
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3 lang="en"">Email:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static" id="verify-email"></p>
+                                            </div>
+                                        </div>
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3" lang="en">Password:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static">
+                                                    <a class="mytooltip" href="javascript:void(0)"> <span lang="en">Hover to show </span><span class="tooltip-content5"><span class="tooltip-text3"><span class="tooltip-inner2" id="verify-password"></span></span></span></a>
+                                                </p>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="col-md-6">
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3" lang="en">Hash Key:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static">
+                                                    <a class="mytooltip" href="javascript:void(0)"> <span lang="en">Hover to show </span><span class="tooltip-content5"><span class="tooltip-text3"><span class="tooltip-inner2" id="verify-hashKey">pass</span></span></span></a>
+                                                </p>
+                                            </div>
+                                        </div>
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3" lang="en">Registration Password:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static">
+                                                    <a class="mytooltip" href="javascript:void(0)"> <span lang="en">Hover to show </span><span class="tooltip-content5"><span class="tooltip-text3"><span class="tooltip-inner2" id="verify-registrationPassword">pass</span></span></span></a>
+                                                </p>
+                                            </div>
+                                        </div>
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3" lang="en">API Key:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static">
+                                                    <a class="mytooltip" href="javascript:void(0)"> <span lang="en">Hover to show </span><span class="tooltip-content5"><span class="tooltip-text3"><span class="tooltip-inner2" id="verify-api">pass</span></span></span></a>
+                                                </p>
+                                            </div>
+                                        </div>
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3" lang="en">Database Location:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static" id="verify-location">  </p>
+                                            </div>
+                                        </div>
+                                        <div class="form-group">
+                                            <label class="control-label col-md-3" lang="en">Database Name:</label>
+                                            <div class="col-md-9">
+                                                <p class="form-control-static" id="verify-dbName">  </p>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                <!--/row-->
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+
+    </div>
+    <!--./row-->
+</div>
+<!-- /.container-fluid -->
+';

+ 7 - 0
api/vendor/autoload.php

@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInitcbdc783d76f8e7563dcce7d8af053ecb::getLoader();

+ 445 - 0
api/vendor/composer/ClassLoader.php

@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    http://www.php-fig.org/psr/psr-0/
+ * @see    http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    // PSR-4
+    private $prefixLengthsPsr4 = array();
+    private $prefixDirsPsr4 = array();
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    private $prefixesPsr0 = array();
+    private $fallbackDirsPsr0 = array();
+
+    private $useIncludePath = false;
+    private $classMap = array();
+    private $classMapAuthoritative = false;
+    private $missingClasses = array();
+    private $apcuPrefix;
+
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
+        }
+
+        return array();
+    }
+
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-4 base directories
+     * @param bool         $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return bool|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            includeFile($file);
+
+            return true;
+        }
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath.'\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        $length = $this->prefixLengthsPsr4[$first][$search];
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
+}

+ 21 - 0
api/vendor/composer/LICENSE

@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+

+ 64 - 0
api/vendor/composer/autoload_classmap.php

@@ -0,0 +1,64 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Dibi\\Bridges\\Nette\\DibiExtension22' => $vendorDir . '/dibi/dibi/src/Dibi/Bridges/Nette/DibiExtension22.php',
+    'Dibi\\Bridges\\Tracy\\Panel' => $vendorDir . '/dibi/dibi/src/Dibi/Bridges/Tracy/Panel.php',
+    'Dibi\\Connection' => $vendorDir . '/dibi/dibi/src/Dibi/Connection.php',
+    'Dibi\\ConstraintViolationException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\DataSource' => $vendorDir . '/dibi/dibi/src/Dibi/DataSource.php',
+    'Dibi\\DateTime' => $vendorDir . '/dibi/dibi/src/Dibi/DateTime.php',
+    'Dibi\\Driver' => $vendorDir . '/dibi/dibi/src/Dibi/interfaces.php',
+    'Dibi\\DriverException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\Drivers\\FirebirdDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/FirebirdDriver.php',
+    'Dibi\\Drivers\\MsSqlDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/MsSqlDriver.php',
+    'Dibi\\Drivers\\MsSqlReflector' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/MsSqlReflector.php',
+    'Dibi\\Drivers\\MySqlDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/MySqlDriver.php',
+    'Dibi\\Drivers\\MySqlReflector' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/MySqlReflector.php',
+    'Dibi\\Drivers\\MySqliDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/MySqliDriver.php',
+    'Dibi\\Drivers\\OdbcDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/OdbcDriver.php',
+    'Dibi\\Drivers\\OracleDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/OracleDriver.php',
+    'Dibi\\Drivers\\PdoDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/PdoDriver.php',
+    'Dibi\\Drivers\\PostgreDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/PostgreDriver.php',
+    'Dibi\\Drivers\\Sqlite3Driver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/Sqlite3Driver.php',
+    'Dibi\\Drivers\\SqliteReflector' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/SqliteReflector.php',
+    'Dibi\\Drivers\\SqlsrvDriver' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/SqlsrvDriver.php',
+    'Dibi\\Drivers\\SqlsrvReflector' => $vendorDir . '/dibi/dibi/src/Dibi/Drivers/SqlsrvReflector.php',
+    'Dibi\\Event' => $vendorDir . '/dibi/dibi/src/Dibi/Event.php',
+    'Dibi\\Exception' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\Expression' => $vendorDir . '/dibi/dibi/src/Dibi/Expression.php',
+    'Dibi\\Fluent' => $vendorDir . '/dibi/dibi/src/Dibi/Fluent.php',
+    'Dibi\\ForeignKeyConstraintViolationException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\HashMap' => $vendorDir . '/dibi/dibi/src/Dibi/HashMap.php',
+    'Dibi\\HashMapBase' => $vendorDir . '/dibi/dibi/src/Dibi/HashMap.php',
+    'Dibi\\Helpers' => $vendorDir . '/dibi/dibi/src/Dibi/Helpers.php',
+    'Dibi\\IDataSource' => $vendorDir . '/dibi/dibi/src/Dibi/interfaces.php',
+    'Dibi\\Literal' => $vendorDir . '/dibi/dibi/src/Dibi/Literal.php',
+    'Dibi\\Loggers\\FileLogger' => $vendorDir . '/dibi/dibi/src/Dibi/Loggers/FileLogger.php',
+    'Dibi\\Loggers\\FirePhpLogger' => $vendorDir . '/dibi/dibi/src/Dibi/Loggers/FirePhpLogger.php',
+    'Dibi\\NotImplementedException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\NotNullConstraintViolationException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\NotSupportedException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\PcreException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\ProcedureException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'Dibi\\Reflection\\Column' => $vendorDir . '/dibi/dibi/src/Dibi/Reflection/Column.php',
+    'Dibi\\Reflection\\Database' => $vendorDir . '/dibi/dibi/src/Dibi/Reflection/Database.php',
+    'Dibi\\Reflection\\ForeignKey' => $vendorDir . '/dibi/dibi/src/Dibi/Reflection/ForeignKey.php',
+    'Dibi\\Reflection\\Index' => $vendorDir . '/dibi/dibi/src/Dibi/Reflection/Index.php',
+    'Dibi\\Reflection\\Result' => $vendorDir . '/dibi/dibi/src/Dibi/Reflection/Result.php',
+    'Dibi\\Reflection\\Table' => $vendorDir . '/dibi/dibi/src/Dibi/Reflection/Table.php',
+    'Dibi\\Reflector' => $vendorDir . '/dibi/dibi/src/Dibi/interfaces.php',
+    'Dibi\\Result' => $vendorDir . '/dibi/dibi/src/Dibi/Result.php',
+    'Dibi\\ResultDriver' => $vendorDir . '/dibi/dibi/src/Dibi/interfaces.php',
+    'Dibi\\ResultIterator' => $vendorDir . '/dibi/dibi/src/Dibi/ResultIterator.php',
+    'Dibi\\Row' => $vendorDir . '/dibi/dibi/src/Dibi/Row.php',
+    'Dibi\\Strict' => $vendorDir . '/dibi/dibi/src/Dibi/Strict.php',
+    'Dibi\\Translator' => $vendorDir . '/dibi/dibi/src/Dibi/Translator.php',
+    'Dibi\\Type' => $vendorDir . '/dibi/dibi/src/Dibi/Type.php',
+    'Dibi\\UniqueConstraintViolationException' => $vendorDir . '/dibi/dibi/src/Dibi/exceptions.php',
+    'dibi' => $vendorDir . '/dibi/dibi/src/Dibi/dibi.php',
+);

+ 10 - 0
api/vendor/composer/autoload_files.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    '0097ca414fcb37c7130ac24b05f485f8' => $vendorDir . '/dibi/dibi/src/loader.php',
+);

+ 9 - 0
api/vendor/composer/autoload_namespaces.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 10 - 0
api/vendor/composer/autoload_psr4.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Lcobucci\\JWT\\' => array($vendorDir . '/lcobucci/jwt/src'),
+);

+ 70 - 0
api/vendor/composer/autoload_real.php

@@ -0,0 +1,70 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInitcbdc783d76f8e7563dcce7d8af053ecb
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array('ComposerAutoloaderInitcbdc783d76f8e7563dcce7d8af053ecb', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        spl_autoload_unregister(array('ComposerAutoloaderInitcbdc783d76f8e7563dcce7d8af053ecb', 'loadClassLoader'));
+
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require_once __DIR__ . '/autoload_static.php';
+
+            call_user_func(\Composer\Autoload\ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
+
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
+
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
+            }
+        }
+
+        $loader->register(true);
+
+        if ($useStaticLoader) {
+            $includeFiles = Composer\Autoload\ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb::$files;
+        } else {
+            $includeFiles = require __DIR__ . '/autoload_files.php';
+        }
+        foreach ($includeFiles as $fileIdentifier => $file) {
+            composerRequirecbdc783d76f8e7563dcce7d8af053ecb($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}
+
+function composerRequirecbdc783d76f8e7563dcce7d8af053ecb($fileIdentifier, $file)
+{
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+        require $file;
+
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+    }
+}

+ 94 - 0
api/vendor/composer/autoload_static.php

@@ -0,0 +1,94 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb
+{
+    public static $files = array (
+        '0097ca414fcb37c7130ac24b05f485f8' => __DIR__ . '/..' . '/dibi/dibi/src/loader.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        'L' => 
+        array (
+            'Lcobucci\\JWT\\' => 13,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Lcobucci\\JWT\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/lcobucci/jwt/src',
+        ),
+    );
+
+    public static $classMap = array (
+        'Dibi\\Bridges\\Nette\\DibiExtension22' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Bridges/Nette/DibiExtension22.php',
+        'Dibi\\Bridges\\Tracy\\Panel' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Bridges/Tracy/Panel.php',
+        'Dibi\\Connection' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Connection.php',
+        'Dibi\\ConstraintViolationException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\DataSource' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/DataSource.php',
+        'Dibi\\DateTime' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/DateTime.php',
+        'Dibi\\Driver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/interfaces.php',
+        'Dibi\\DriverException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\Drivers\\FirebirdDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/FirebirdDriver.php',
+        'Dibi\\Drivers\\MsSqlDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/MsSqlDriver.php',
+        'Dibi\\Drivers\\MsSqlReflector' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/MsSqlReflector.php',
+        'Dibi\\Drivers\\MySqlDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/MySqlDriver.php',
+        'Dibi\\Drivers\\MySqlReflector' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/MySqlReflector.php',
+        'Dibi\\Drivers\\MySqliDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/MySqliDriver.php',
+        'Dibi\\Drivers\\OdbcDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/OdbcDriver.php',
+        'Dibi\\Drivers\\OracleDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/OracleDriver.php',
+        'Dibi\\Drivers\\PdoDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/PdoDriver.php',
+        'Dibi\\Drivers\\PostgreDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/PostgreDriver.php',
+        'Dibi\\Drivers\\Sqlite3Driver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/Sqlite3Driver.php',
+        'Dibi\\Drivers\\SqliteReflector' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/SqliteReflector.php',
+        'Dibi\\Drivers\\SqlsrvDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/SqlsrvDriver.php',
+        'Dibi\\Drivers\\SqlsrvReflector' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Drivers/SqlsrvReflector.php',
+        'Dibi\\Event' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Event.php',
+        'Dibi\\Exception' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\Expression' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Expression.php',
+        'Dibi\\Fluent' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Fluent.php',
+        'Dibi\\ForeignKeyConstraintViolationException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\HashMap' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/HashMap.php',
+        'Dibi\\HashMapBase' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/HashMap.php',
+        'Dibi\\Helpers' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Helpers.php',
+        'Dibi\\IDataSource' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/interfaces.php',
+        'Dibi\\Literal' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Literal.php',
+        'Dibi\\Loggers\\FileLogger' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Loggers/FileLogger.php',
+        'Dibi\\Loggers\\FirePhpLogger' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Loggers/FirePhpLogger.php',
+        'Dibi\\NotImplementedException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\NotNullConstraintViolationException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\NotSupportedException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\PcreException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\ProcedureException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'Dibi\\Reflection\\Column' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Reflection/Column.php',
+        'Dibi\\Reflection\\Database' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Reflection/Database.php',
+        'Dibi\\Reflection\\ForeignKey' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Reflection/ForeignKey.php',
+        'Dibi\\Reflection\\Index' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Reflection/Index.php',
+        'Dibi\\Reflection\\Result' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Reflection/Result.php',
+        'Dibi\\Reflection\\Table' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Reflection/Table.php',
+        'Dibi\\Reflector' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/interfaces.php',
+        'Dibi\\Result' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Result.php',
+        'Dibi\\ResultDriver' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/interfaces.php',
+        'Dibi\\ResultIterator' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/ResultIterator.php',
+        'Dibi\\Row' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Row.php',
+        'Dibi\\Strict' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Strict.php',
+        'Dibi\\Translator' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Translator.php',
+        'Dibi\\Type' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/Type.php',
+        'Dibi\\UniqueConstraintViolationException' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/exceptions.php',
+        'dibi' => __DIR__ . '/..' . '/dibi/dibi/src/Dibi/dibi.php',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb::$classMap;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 131 - 0
api/vendor/composer/installed.json

@@ -0,0 +1,131 @@
+[
+    {
+        "name": "dibi/dibi",
+        "version": "v3.1.0",
+        "version_normalized": "3.1.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/dg/dibi.git",
+            "reference": "fa35c0cf7de1286ce1cf9fbdba058c1d54c849f8"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/dg/dibi/zipball/fa35c0cf7de1286ce1cf9fbdba058c1d54c849f8",
+            "reference": "fa35c0cf7de1286ce1cf9fbdba058c1d54c849f8",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.4.4"
+        },
+        "replace": {
+            "dg/dibi": "*"
+        },
+        "require-dev": {
+            "nette/tester": "~1.7",
+            "tracy/tracy": "~2.2"
+        },
+        "time": "2017-09-25T15:57:54+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "3.1-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "classmap": [
+                "src/"
+            ],
+            "files": [
+                "src/loader.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD-3-Clause",
+            "GPL-2.0",
+            "GPL-3.0"
+        ],
+        "authors": [
+            {
+                "name": "David Grudl",
+                "homepage": "https://davidgrudl.com"
+            }
+        ],
+        "description": "Dibi is Database Abstraction Library for PHP",
+        "homepage": "https://dibiphp.com",
+        "keywords": [
+            "access",
+            "database",
+            "dbal",
+            "mssql",
+            "mysql",
+            "odbc",
+            "oracle",
+            "pdo",
+            "postgresql",
+            "sqlite",
+            "sqlsrv"
+        ]
+    },
+    {
+        "name": "lcobucci/jwt",
+        "version": "3.2.2",
+        "version_normalized": "3.2.2.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/lcobucci/jwt.git",
+            "reference": "0b5930be73582369e10c4d4bb7a12bac927a203c"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0b5930be73582369e10c4d4bb7a12bac927a203c",
+            "reference": "0b5930be73582369e10c4d4bb7a12bac927a203c",
+            "shasum": ""
+        },
+        "require": {
+            "ext-openssl": "*",
+            "php": ">=5.5"
+        },
+        "require-dev": {
+            "mdanter/ecc": "~0.3.1",
+            "mikey179/vfsstream": "~1.5",
+            "phpmd/phpmd": "~2.2",
+            "phpunit/php-invoker": "~1.1",
+            "phpunit/phpunit": "~4.5",
+            "squizlabs/php_codesniffer": "~2.3"
+        },
+        "suggest": {
+            "mdanter/ecc": "Required to use Elliptic Curves based algorithms."
+        },
+        "time": "2017-09-01T08:23:26+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "3.1-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Lcobucci\\JWT\\": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD-3-Clause"
+        ],
+        "authors": [
+            {
+                "name": "Luís Otávio Cobucci Oblonczyk",
+                "email": "lcobucci@gmail.com",
+                "role": "Developer"
+            }
+        ],
+        "description": "A simple library to work with JSON Web Token and JSON Web Signature",
+        "keywords": [
+            "JWS",
+            "jwt"
+        ]
+    }
+]

+ 32 - 0
api/vendor/dibi/dibi/composer.json

@@ -0,0 +1,32 @@
+{
+	"name": "dibi/dibi",
+	"description": "Dibi is Database Abstraction Library for PHP",
+	"keywords": ["database", "dbal", "mysql", "postgresql", "sqlite", "mssql", "sqlsrv", "oracle", "access", "pdo", "odbc"],
+	"homepage": "https://dibiphp.com",
+	"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
+	"authors": [
+		{
+			"name": "David Grudl",
+			"homepage": "https://davidgrudl.com"
+		}
+	],
+	"require": {
+		"php": ">=5.4.4"
+	},
+	"require-dev": {
+		"tracy/tracy": "~2.2",
+		"nette/tester": "~1.7"
+	},
+	"replace": {
+		"dg/dibi": "*"
+	},
+	"autoload": {
+		"classmap": ["src/"],
+		"files": ["src/loader.php"]
+	},
+	"extra": {
+		"branch-alias": {
+			"dev-master": "3.1-dev"
+		}
+	}
+}

+ 31 - 0
api/vendor/dibi/dibi/contributing.md

@@ -0,0 +1,31 @@
+How to contribute & use the issue tracker
+=========================================
+
+Dibi welcomes your contributions. There are several ways to help out:
+
+* Create an issue on GitHub, if you have found a bug
+* Write test cases for open bug issues
+* Write fixes for open bug/feature issues, preferably with test cases included
+* Contribute to the documentation
+
+Issues
+------
+
+Please **do not use the issue tracker to ask questions**. We will be happy to help you
+on [Dibi forum](https://forum.dibiphp.com).
+
+A good bug report shouldn't leave others needing to chase you up for more
+information. Please try to be as detailed as possible in your report.
+
+**Feature requests** are welcome. But take a moment to find out whether your idea
+fits with the scope and aims of the project. It's up to *you* to make a strong
+case to convince the project's developers of the merits of this feature.
+
+Contributing
+------------
+
+The best way to propose a feature is to discuss your ideas on [Dibi forum](https://forum.dibiphp.com) before implementing them.
+
+Please do not fix whitespace, format code, or make a purely cosmetic patch.
+
+Thanks! :heart:

+ 161 - 0
api/vendor/dibi/dibi/examples/connecting-to-databases.php

@@ -0,0 +1,161 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Connecting to Databases | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+// connects to SQlite using dibi class
+echo '<p>Connecting to Sqlite: ';
+try {
+	dibi::connect([
+		'driver' => 'sqlite3',
+		'database' => 'data/sample.s3db',
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to SQlite using Dibi\Connection object
+echo '<p>Connecting to Sqlite: ';
+try {
+	$connection = new Dibi\Connection([
+		'driver' => 'sqlite3',
+		'database' => 'data/sample.s3db',
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to MySQL using DSN
+echo '<p>Connecting to MySQL: ';
+try {
+	dibi::connect('driver=mysql&host=localhost&username=root&password=xxx&database=test&charset=cp1250');
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to MySQLi using array
+echo '<p>Connecting to MySQLi: ';
+try {
+	dibi::connect([
+		'driver' => 'mysqli',
+		'host' => 'localhost',
+		'username' => 'root',
+		'password' => 'xxx',
+		'database' => 'dibi',
+		'options' => [
+			MYSQLI_OPT_CONNECT_TIMEOUT => 30,
+		],
+		'flags' => MYSQLI_CLIENT_COMPRESS,
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to ODBC
+echo '<p>Connecting to ODBC: ';
+try {
+	dibi::connect([
+		'driver' => 'odbc',
+		'username' => 'root',
+		'password' => '***',
+		'dsn' => 'Driver={Microsoft Access Driver (*.mdb)};Dbq=' . __DIR__ . '/data/sample.mdb',
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to PostgreSql
+echo '<p>Connecting to PostgreSql: ';
+try {
+	dibi::connect([
+		'driver' => 'postgre',
+		'string' => 'host=localhost port=5432 dbname=mary',
+		'persistent' => true,
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to PDO
+echo '<p>Connecting to Sqlite via PDO: ';
+try {
+	dibi::connect([
+		'driver' => 'pdo',
+		'dsn' => 'sqlite::memory:',
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to MS SQL
+echo '<p>Connecting to MS SQL: ';
+try {
+	dibi::connect([
+		'driver' => 'mssql',
+		'host' => 'localhost',
+		'username' => 'root',
+		'password' => 'xxx',
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to SQLSRV
+echo '<p>Connecting to Microsoft SQL Server: ';
+try {
+	dibi::connect([
+		'driver' => 'sqlsrv',
+		'host' => '(local)',
+		'username' => 'Administrator',
+		'password' => 'xxx',
+		'database' => 'main',
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";
+
+
+// connects to Oracle
+echo '<p>Connecting to Oracle: ';
+try {
+	dibi::connect([
+		'driver' => 'oracle',
+		'username' => 'root',
+		'password' => 'xxx',
+		'database' => 'db',
+	]);
+	echo 'OK';
+} catch (Dibi\Exception $e) {
+	echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+echo "</p>\n";

BIN
api/vendor/dibi/dibi/examples/data/arrow.png


BIN
api/vendor/dibi/dibi/examples/data/dibi-powered.gif


BIN
api/vendor/dibi/dibi/examples/data/sample.dump.sql.gz


BIN
api/vendor/dibi/dibi/examples/data/sample.mdb


BIN
api/vendor/dibi/dibi/examples/data/sample.s3db


+ 64 - 0
api/vendor/dibi/dibi/examples/data/style.css

@@ -0,0 +1,64 @@
+body {
+	font: 15px/1.5 Tahoma, Verdana, Myriad Web, Syntax, sans-serif;
+	color: #333;
+	background: #fff url('dibi-powered.gif') no-repeat 99% 1em;
+	margin: 1.6em;
+	padding: 0;
+}
+
+h1, h2 {
+	font-size: 210%;
+	font-weight: normal;
+	color: #036;
+}
+
+h2 {
+	font-size: 150%;
+}
+
+a {
+	color: #000080;
+}
+
+table.dump {
+	padding: 0;
+	margin: 0;
+	border-collapse:collapse;
+}
+
+table.dump td, table.dump th {
+	color: #505767;
+	background: #fff;
+	border: 1px solid #d1cdab;
+	padding: 6px 6px 6px 12px;
+	text-align: left;
+}
+
+table.dump th {
+	font-size: 80%;
+	color: #525b37;
+	background: #e3e9ba;
+}
+
+/* dump() */
+pre.tracy-dump, pre.dump {
+	color: #444; background: white;
+	border: 1px solid silver;
+	padding: 1em;
+	margin: 1em 0;
+}
+pre.tracy-dump .php-array, pre.tracy-dump .php-object {
+	color: #C22;
+}
+pre.tracy-dump .php-string {
+	color: #080;
+}
+pre.tracy-dump .php-int, pre.tracy-dump .php-float {
+	color: #37D;
+}
+pre.tracy-dump .php-null, pre.tracy-dump .php-bool {
+	color: black;
+}
+pre.tracy-dump .php-visibility {
+	font-size: 85%; color: #999;
+}

+ 49 - 0
api/vendor/dibi/dibi/examples/database-reflection.php

@@ -0,0 +1,49 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Database Reflection | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+// retrieve database reflection
+$database = dibi::getDatabaseInfo();
+
+echo "<h2>Database '{$database->getName()}'</h2>\n";
+echo "<ul>\n";
+foreach ($database->getTables() as $table) {
+	echo '<li>', ($table->isView() ? 'view' : 'table') . " {$table->getName()}</li>\n";
+}
+echo "</ul>\n";
+
+
+// table reflection
+$table = $database->getTable('products');
+
+echo "<h2>Table '{$table->getName()}'</h2>\n";
+
+echo "Columns\n";
+echo "<ul>\n";
+foreach ($table->getColumns() as $column) {
+	echo "<li>{$column->getName()} <i>{$column->getNativeType()}</i> <code>{$column->getDefault()}</code></li>\n";
+}
+echo "</ul>\n";
+
+
+echo 'Indexes';
+echo "<ul>\n";
+foreach ($table->getIndexes() as $index) {
+	echo "<li>{$index->getName()} " . ($index->isPrimary() ? 'primary ' : '') . ($index->isUnique() ? 'unique' : '') . ' (';
+	foreach ($index->getColumns() as $column) {
+		echo $column->getName(), ', ';
+	}
+	echo ")</li>\n";
+}
+echo "</ul>\n";

+ 32 - 0
api/vendor/dibi/dibi/examples/dumping-sql-and-result-set.php

@@ -0,0 +1,32 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Dumping SQL and Result Set | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+$res = dibi::query('
+	SELECT * FROM products
+	INNER JOIN orders USING (product_id)
+	INNER JOIN customers USING (customer_id)
+');
+
+
+echo '<h2>dibi::dump()</h2>';
+
+// dump last query (dibi::$sql)
+dibi::dump();
+
+
+// dump result table
+echo '<h2>Dibi\Result::dump()</h2>';
+
+$res->dump();

+ 94 - 0
api/vendor/dibi/dibi/examples/fetching-examples.php

@@ -0,0 +1,94 @@
+<?php
+
+if (@!include __DIR__ . '/../vendor/autoload.php') {
+	die('Install dependencies using `composer install --dev`');
+}
+
+Tracy\Debugger::enable();
+
+?>
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Fetching Examples | dibi</h1>
+
+<?php
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+/*
+TABLE products
+
+product_id | title
+-----------+----------
+	1      | Chair
+	2      | Table
+	3      | Computer
+
+*/
+
+
+// fetch a single row
+echo "<h2>fetch()</h2>\n";
+$row = dibi::fetch('SELECT title FROM products');
+Tracy\Dumper::dump($row); // Chair
+
+
+// fetch a single value
+echo "<h2>fetchSingle()</h2>\n";
+$value = dibi::fetchSingle('SELECT title FROM products');
+Tracy\Dumper::dump($value); // Chair
+
+
+// fetch complete result set
+echo "<h2>fetchAll()</h2>\n";
+$all = dibi::fetchAll('SELECT * FROM products');
+Tracy\Dumper::dump($all);
+
+
+// fetch complete result set like association array
+echo "<h2>fetchAssoc('title')</h2>\n";
+$res = dibi::query('SELECT * FROM products');
+$assoc = $res->fetchAssoc('title'); // key
+Tracy\Dumper::dump($assoc);
+
+
+// fetch complete result set like pairs key => value
+echo "<h2>fetchPairs('product_id', 'title')</h2>\n";
+$res = dibi::query('SELECT * FROM products');
+$pairs = $res->fetchPairs('product_id', 'title');
+Tracy\Dumper::dump($pairs);
+
+
+// fetch row by row
+echo "<h2>using foreach</h2>\n";
+$res = dibi::query('SELECT * FROM products');
+foreach ($res as $n => $row) {
+	Tracy\Dumper::dump($row);
+}
+
+
+// more complex association array
+$res = dibi::query('
+	SELECT *
+	FROM products
+	INNER JOIN orders USING (product_id)
+	INNER JOIN customers USING (customer_id)
+');
+
+echo "<h2>fetchAssoc('name|title')</h2>\n";
+$assoc = $res->fetchAssoc('name|title'); // key
+Tracy\Dumper::dump($assoc);
+
+echo "<h2>fetchAssoc('name[]title')</h2>\n";
+$res = dibi::query('SELECT * FROM products INNER JOIN orders USING (product_id) INNER JOIN customers USING (customer_id)');
+$assoc = $res->fetchAssoc('name[]title'); // key
+Tracy\Dumper::dump($assoc);
+
+echo "<h2>fetchAssoc('name->title')</h2>\n";
+$res = dibi::query('SELECT * FROM products INNER JOIN orders USING (product_id) INNER JOIN customers USING (customer_id)');
+$assoc = $res->fetchAssoc('name->title'); // key
+Tracy\Dumper::dump($assoc);

+ 18 - 0
api/vendor/dibi/dibi/examples/importing-dump-from-file.php

@@ -0,0 +1,18 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Importing SQL Dump from File | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+$count = dibi::loadFile('compress.zlib://data/sample.dump.sql.gz');
+
+echo 'Number of SQL commands:', $count;

+ 61 - 0
api/vendor/dibi/dibi/examples/query-language-and-conditions.php

@@ -0,0 +1,61 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Query Language & Conditions | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+// some variables
+$cond1 = true;
+$cond2 = false;
+$foo = -1;
+$bar = 2;
+
+// conditional variable
+$name = $cond1 ? 'K%' : null;
+
+// if & end
+dibi::test('
+	SELECT *
+	FROM customers
+	%if', isset($name), 'WHERE name LIKE ?', $name, '%end'
+);
+// -> SELECT * FROM customers WHERE name LIKE 'K%'
+
+
+// if & else & (optional) end
+dibi::test('
+	SELECT *
+	FROM people
+	WHERE id > 0
+		%if', ($foo > 0), 'AND foo=?', $foo, '
+		%else %if', ($bar > 0), 'AND bar=?', $bar, '
+');
+// -> SELECT * FROM people WHERE id > 0 AND bar=2
+
+
+// nested condition
+dibi::test('
+	SELECT *
+	FROM customers
+	WHERE
+		%if', isset($name), 'name LIKE ?', $name, '
+			%if', $cond2, 'AND admin=1 %end
+		%else 1 LIMIT 10 %end'
+);
+// -> SELECT * FROM customers WHERE LIMIT 10
+
+
+// IF()
+dibi::test('UPDATE products SET', [
+	'price' => ['IF(price_fixed, price, ?)', 123],
+]);
+// -> SELECT * FROM customers WHERE LIMIT 10

+ 87 - 0
api/vendor/dibi/dibi/examples/query-language-basic-examples.php

@@ -0,0 +1,87 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Query Language Basic Examples | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+date_default_timezone_set('Europe/Prague');
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+// SELECT
+$ipMask = '192.168.%';
+$timestamp = mktime(0, 0, 0, 10, 13, 1997);
+
+dibi::test('
+	SELECT COUNT(*) as [count]
+	FROM [comments]
+	WHERE [ip] LIKE ?', $ipMask, '
+	AND [date] > ', new Dibi\DateTime($timestamp)
+);
+// -> SELECT COUNT(*) as [count] FROM [comments] WHERE [ip] LIKE '192.168.%' AND [date] > 876693600
+
+
+// dibi detects INSERT or REPLACE command
+dibi::test('
+	REPLACE INTO products', [
+		'title' => 'Super product',
+		'price' => 318,
+		'active' => true,
+]);
+// -> REPLACE INTO products ([title], [price], [active]) VALUES ('Super product', 318, 1)
+
+
+// multiple INSERT command
+$array = [
+	'title' => 'Super Product',
+	'price' => 12,
+	'brand' => null,
+	'created' => new DateTime,
+];
+dibi::test('INSERT INTO products', $array, $array, $array);
+// -> INSERT INTO products ([title], [price], [brand], [created]) VALUES ('Super Product', ...) , (...) , (...)
+
+
+// dibi detects UPDATE command
+dibi::test('
+	UPDATE colors SET', [
+		'color' => 'blue',
+		'order' => 12,
+	], '
+	WHERE id=?', 123);
+// -> UPDATE colors SET [color]='blue', [order]=12 WHERE id=123
+
+
+// modifier applied to array
+$array = [1, 2, 3];
+dibi::test('
+	SELECT *
+	FROM people
+	WHERE id IN (?)', $array
+);
+// -> SELECT * FROM people WHERE id IN ( 1, 2, 3 )
+
+
+// modifier %by for ORDER BY
+$order = [
+	'field1' => 'asc',
+	'field2' => 'desc',
+];
+dibi::test('
+	SELECT *
+	FROM people
+	ORDER BY %by', $order, '
+');
+// -> SELECT * FROM people ORDER BY [field1] ASC, [field2] DESC
+
+
+// indentifiers and strings syntax mix
+dibi::test('UPDATE [table] SET `item` = "5 1/4"" diskette"');
+// -> UPDATE [table] SET [item] = '5 1/4" diskette'

+ 50 - 0
api/vendor/dibi/dibi/examples/result-set-data-types.php

@@ -0,0 +1,50 @@
+<?php
+
+use Dibi\Type;
+
+if (@!include __DIR__ . '/../vendor/autoload.php') {
+	die('Install dependencies using `composer install --dev`');
+}
+
+Tracy\Debugger::enable();
+
+date_default_timezone_set('Europe/Prague');
+
+?>
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Result Set Data Types | dibi</h1>
+
+<?php
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+// using manual hints
+$res = dibi::query('SELECT * FROM [customers]');
+
+$res->setType('customer_id', Type::INTEGER)
+	->setType('added', Type::DATETIME)
+	->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
+
+
+Tracy\Dumper::dump($res->fetch());
+// outputs:
+// Dibi\Row(3) {
+//    customer_id => 1
+//    name => "Dave Lister" (11)
+//    added => "2007-03-11 17:20:03" (19)
+
+
+// using auto-detection (works well with MySQL or other strictly typed databases)
+$res = dibi::query('SELECT * FROM [customers]');
+
+Tracy\Dumper::dump($res->fetch());
+// outputs:
+// Dibi\Row(3) {
+//    customer_id => 1
+//    name => "Dave Lister" (11)
+//    added => "2007-03-11 17:20:03" (19)

+ 33 - 0
api/vendor/dibi/dibi/examples/tracy-and-exceptions.php

@@ -0,0 +1,33 @@
+<?php
+
+if (@!include __DIR__ . '/../vendor/autoload.php') {
+	die('Install dependencies using `composer install --dev`');
+}
+
+
+// enable Tracy
+Tracy\Debugger::enable();
+
+
+$connection = dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+	'profiler' => [
+		'run' => true,
+	],
+]);
+
+
+// add panel to debug bar
+$panel = new Dibi\Bridges\Tracy\Panel;
+$panel->register($connection);
+
+
+// throws error because SQL is bad
+dibi::query('SELECT FROM customers WHERE customer_id < ?', 38);
+
+?><!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Tracy & SQL Exceptions | dibi</h1>
+
+<p>Dibi can display and log exceptions via <a href="https://tracy.nette.org">Tracy</a>.</p>

+ 40 - 0
api/vendor/dibi/dibi/examples/tracy.php

@@ -0,0 +1,40 @@
+<?php
+
+if (@!include __DIR__ . '/../vendor/autoload.php') {
+	die('Install dependencies using `composer install --dev`');
+}
+
+
+// enable Tracy
+Tracy\Debugger::enable();
+
+
+$connection = dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+	'profiler' => [
+		'run' => true,
+	],
+]);
+
+
+// add panel to debug bar
+$panel = new Dibi\Bridges\Tracy\Panel;
+$panel->register($connection);
+
+
+// query will be logged
+dibi::query('SELECT 123');
+
+// result set will be dumped
+Tracy\Debugger::barDump(dibi::fetchAll('SELECT * FROM customers WHERE customer_id < ?', 38), '[customers]');
+
+
+?>
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<style> html { background: url(data/arrow.png) no-repeat bottom right; height: 100%; } </style>
+
+<h1>Tracy | dibi</h1>
+
+<p>Dibi can log queries and dump variables to the <a href="https://tracy.nette.org">Tracy</a>.</p>

+ 29 - 0
api/vendor/dibi/dibi/examples/using-datetime.php

@@ -0,0 +1,29 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using DateTime | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+date_default_timezone_set('Europe/Prague');
+
+
+// CHANGE TO REAL PARAMETERS!
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+	'formatDate' => "'Y-m-d'",
+	'formatDateTime' => "'Y-m-d H-i-s'",
+]);
+
+
+// generate and dump SQL
+dibi::test('
+	INSERT INTO [mytable]', [
+		'id' => 123,
+		'date' => new DateTime('12.3.2007'),
+		'stamp' => new DateTime('23.1.2007 10:23'),
+	]
+);
+// -> INSERT INTO [mytable] ([id], [date], [stamp]) VALUES (123, '2007-03-12', '2007-01-23 10-23-00')

+ 33 - 0
api/vendor/dibi/dibi/examples/using-extension-methods.php

@@ -0,0 +1,33 @@
+<?php
+
+if (@!include __DIR__ . '/../vendor/autoload.php') {
+	die('Install dependencies using `composer install --dev`');
+}
+
+Tracy\Debugger::enable();
+
+?>
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using Extension Methods | dibi</h1>
+
+<?php
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+// using the "prototype" to add custom method to class Dibi\Result
+Dibi\Result::extensionMethod('fetchShuffle', function (Dibi\Result $obj) {
+	$all = $obj->fetchAll();
+	shuffle($all);
+	return $all;
+});
+
+
+// fetch complete result set shuffled
+$res = dibi::query('SELECT * FROM [customers]');
+$all = $res->fetchShuffle();
+Tracy\Dumper::dump($all);

+ 77 - 0
api/vendor/dibi/dibi/examples/using-fluent-syntax.php

@@ -0,0 +1,77 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using Fluent Syntax | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+date_default_timezone_set('Europe/Prague');
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+$id = 10;
+$record = [
+	'title' => 'Super product',
+	'price' => 318,
+	'active' => true,
+];
+
+// SELECT ...
+dibi::select('product_id')->as('id')
+	->select('title')
+	->from('products')
+	->innerJoin('orders')->using('(product_id)')
+	->innerJoin('customers USING (customer_id)')
+	->orderBy('title')
+	->test();
+// -> SELECT [product_id] AS [id] , [title] FROM [products] INNER JOIN [orders]
+//    USING (product_id) INNER JOIN customers USING (customer_id) ORDER BY [title]
+
+
+// SELECT ...
+echo dibi::select('title')->as('id')
+	->from('products')
+	->fetchSingle();
+// -> Chair (as result of query: SELECT [title] AS [id] FROM [products])
+
+
+// INSERT ...
+dibi::insert('products', $record)
+	->setFlag('IGNORE')
+	->test();
+// -> INSERT IGNORE INTO [products] ([title], [price], [active]) VALUES ('Super product', 318, 1)
+
+
+// UPDATE ...
+dibi::update('products', $record)
+	->where('product_id = ?', $id)
+	->test();
+// -> UPDATE [products] SET [title]='Super product', [price]=318, [active]=1 WHERE product_id = 10
+
+
+// DELETE ...
+dibi::delete('products')
+	->where('product_id = ?', $id)
+	->test();
+// -> DELETE FROM [products] WHERE product_id = 10
+
+
+// custom commands
+dibi::command()
+	->update('products')
+	->where('product_id = ?', $id)
+	->set($record)
+	->test();
+// -> UPDATE [products] SET [title]='Super product', [price]=318, [active]=1 WHERE product_id = 10
+
+
+dibi::command()
+	->truncate('products')
+	->test();
+// -> TRUNCATE [products]

+ 28 - 0
api/vendor/dibi/dibi/examples/using-limit-and-offset.php

@@ -0,0 +1,28 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using Limit & Offset | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+// no limit
+dibi::test('SELECT * FROM [products]');
+// -> SELECT * FROM [products]
+
+
+// with limit = 2
+dibi::test('SELECT * FROM [products] %lmt', 2);
+// -> SELECT * FROM [products] LIMIT 2
+
+
+// with limit = 2, offset = 1
+dibi::test('SELECT * FROM [products] %lmt %ofs', 2, 1);
+// -> SELECT * FROM [products] LIMIT 2 OFFSET 1

+ 37 - 0
api/vendor/dibi/dibi/examples/using-logger.php

@@ -0,0 +1,37 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using Logger | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+date_default_timezone_set('Europe/Prague');
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+	// enable query logging to this file
+	'profiler' => [
+		'run' => true,
+		'file' => 'data/log.sql',
+	],
+]);
+
+
+try {
+	$res = dibi::query('SELECT * FROM [customers] WHERE [customer_id] = ?', 1);
+
+	$res = dibi::query('SELECT * FROM [customers] WHERE [customer_id] < ?', 5);
+
+	$res = dibi::query('SELECT FROM [customers] WHERE [customer_id] < ?', 38);
+} catch (Dibi\Exception $e) {
+	echo '<p>', get_class($e), ': ', $e->getMessage(), '</p>';
+}
+
+
+// outputs a log file
+echo '<h2>File data/log.sql:</h2>';
+
+echo '<pre>', file_get_contents('data/log.sql'), '</pre>';

+ 43 - 0
api/vendor/dibi/dibi/examples/using-profiler.php

@@ -0,0 +1,43 @@
+<?php ob_start() // needed by FirePHP ?>
+
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using Profiler | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+	'profiler' => [
+		'run' => true,
+	],
+]);
+
+
+// execute some queries...
+for ($i = 0; $i < 20; $i++) {
+	$res = dibi::query('SELECT * FROM [customers] WHERE [customer_id] < ?', $i);
+}
+
+// display output
+?>
+<p>Last query: <strong><?php echo dibi::$sql; ?></strong></p>
+
+<p>Number of queries: <strong><?php echo dibi::$numOfQueries; ?></strong></p>
+
+<p>Elapsed time for last query: <strong><?php echo sprintf('%0.3f', dibi::$elapsedTime * 1000); ?> ms</strong></p>
+
+<p>Total elapsed time: <strong><?php echo sprintf('%0.3f', dibi::$totalTime * 1000); ?> ms</strong></p>
+
+<br>
+
+<p>Dibi can log to your Firebug Console. You first need to install the Firefox, Firebug and FirePHP extensions. You can install them from here:</p>
+
+<ul>
+	<li>Firebug: https://addons.mozilla.org/en-US/firefox/addon/1843
+	<li>FirePHP: http://www.firephp.org/
+</ul>

+ 54 - 0
api/vendor/dibi/dibi/examples/using-substitutions.php

@@ -0,0 +1,54 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using Substitutions | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+// create new substitution :blog:  ==>  wp_
+dibi::getSubstitutes()->blog = 'wp_';
+
+dibi::test('SELECT * FROM [:blog:items]');
+// -> SELECT * FROM [wp_items]
+
+
+// create new substitution :: (empty)  ==>  my_
+dibi::getSubstitutes()->{''} = 'my_';
+
+dibi::test("UPDATE ::table SET [text]='Hello World'");
+// -> UPDATE my_table SET [text]='Hello World'
+
+
+// create substitutions using fallback callback
+function substFallBack($expr)
+{
+	$const = 'SUBST_' . strtoupper($expr);
+	if (defined($const)) {
+		return constant($const);
+	} else {
+		throw new Exception("Undefined substitution :$expr:");
+	}
+}
+
+
+// define callback
+dibi::getSubstitutes()->setCallback('substFallBack');
+
+// define substitutes as constants
+define('SUBST_ACCOUNT', 'eshop_');
+define('SUBST_ACTIVE', 7);
+
+dibi::test("
+	UPDATE :account:user
+	SET name='John Doe', status=:active:
+	WHERE id=", 7
+);
+// -> UPDATE eshop_user SET name='John Doe', status=7 WHERE id= 7

+ 34 - 0
api/vendor/dibi/dibi/examples/using-transactions.php

@@ -0,0 +1,34 @@
+<!DOCTYPE html><link rel="stylesheet" href="data/style.css">
+
+<h1>Using Transactions | dibi</h1>
+
+<?php
+
+require __DIR__ . '/../src/loader.php';
+
+
+dibi::connect([
+	'driver' => 'sqlite3',
+	'database' => 'data/sample.s3db',
+]);
+
+
+echo "<h2>Before</h2>\n";
+dibi::query('SELECT * FROM [products]')->dump();
+// -> 3 rows
+
+
+dibi::begin();
+dibi::query('INSERT INTO [products]', [
+	'title' => 'Test product',
+]);
+
+echo "<h2>After INSERT</h2>\n";
+dibi::query('SELECT * FROM [products]')->dump();
+
+
+dibi::rollback(); // or dibi::commit();
+
+echo "<h2>After rollback</h2>\n";
+dibi::query('SELECT * FROM [products]')->dump();
+// -> 3 rows again

+ 55 - 0
api/vendor/dibi/dibi/license.md

@@ -0,0 +1,55 @@
+Licenses
+========
+
+Good news! You may use Dibi under the terms of either the New BSD License
+or the GNU General Public License (GPL) version 2 or 3.
+
+The BSD License is recommended for most projects. It is easy to understand and it
+places almost no restrictions on what you can do with the framework. If the GPL
+fits better to your project, you can use the framework under this license.
+
+You don't have to notify anyone which license you are using. You can freely
+use Dibi in commercial projects as long as the copyright header
+remains intact.
+
+
+New BSD License
+---------------
+
+Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+	* Redistributions of source code must retain the above copyright notice,
+	this list of conditions and the following disclaimer.
+
+	* Redistributions in binary form must reproduce the above copyright notice,
+	this list of conditions and the following disclaimer in the documentation
+	and/or other materials provided with the distribution.
+
+	* Neither the name of "Dibi" nor the names of its contributors
+	may be used to endorse or promote products derived from this software
+	without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as is" and
+any express or implied warranties, including, but not limited to, the implied
+warranties of merchantability and fitness for a particular purpose are
+disclaimed. In no event shall the copyright owner or contributors be liable for
+any direct, indirect, incidental, special, exemplary, or consequential damages
+(including, but not limited to, procurement of substitute goods or services;
+loss of use, data, or profits; or business interruption) however caused and on
+any theory of liability, whether in contract, strict liability, or tort
+(including negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+
+GNU General Public License
+--------------------------
+
+GPL licenses are very very long, so instead of including them here we offer
+you URLs with full text:
+
+- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
+- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)

+ 133 - 0
api/vendor/dibi/dibi/readme.md

@@ -0,0 +1,133 @@
+[Dibi](https://dibiphp.com) - smart database layer for PHP  [![Buy me a coffee](https://files.nette.org/images/coffee1s.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9XXL5ZJHAYQUN)
+=========================================================
+
+[![Downloads this Month](https://img.shields.io/packagist/dm/dibi/dibi.svg)](https://packagist.org/packages/dibi/dibi)
+[![Build Status](https://travis-ci.org/dg/dibi.svg?branch=master)](https://travis-ci.org/dg/dibi)
+[![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/dg/dibi?branch=master&svg=true)](https://ci.appveyor.com/project/dg/dibi/branch/master)
+[![Latest Stable Version](https://poser.pugx.org/dibi/dibi/v/stable)](https://github.com/dg/dibi/releases)
+[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/dg/dibi/blob/master/license.md)
+
+Database access functions in PHP are not standardised. This library
+hides the differences between them, and above all, it gives you a very handy interface.
+
+The best way to install Dibi is to use a [Composer](https://getcomposer.org/download):
+
+    php composer.phar require dibi/dibi
+
+Or you can download the latest package from https://dibiphp.com. In this
+package is also `Dibi.minified`, shrinked single-file version of whole Dibi,
+useful when you don't want to modify the library, but just use it.
+
+Dibi requires PHP 5.4.4 or later. It has been tested with PHP 7 too.
+
+
+Examples
+--------
+
+Refer to the `examples` directory for examples. Dibi documentation is
+available on the [homepage](https://dibiphp.com).
+
+Connect to database:
+
+```php
+// connect to database (static way)
+dibi::connect([
+    'driver'   => 'mysql',
+    'host'     => 'localhost',
+    'username' => 'root',
+    'password' => '***',
+]);
+
+// or object way; in all other examples use $connection-> instead of dibi::
+$connection = new DibiConnection($options);
+```
+
+SELECT, INSERT, UPDATE
+
+```php
+dibi::query('SELECT * FROM users WHERE id = ?', $id);
+
+$arr = [
+    'name' => 'John',
+    'is_admin'  => true,
+];
+dibi::query('INSERT INTO users', $arr);
+// INSERT INTO users (`name`, `is_admin`) VALUES ('John', 1)
+
+dibi::query('UPDATE users SET', $arr, 'WHERE `id`=?', $x);
+// UPDATE users SET `name`='John', `is_admin`=1 WHERE `id` = 123
+
+dibi::query('UPDATE users SET', [
+	'title' => array('SHA1(?)', 'tajneheslo'),
+]);
+// UPDATE users SET 'title' = SHA1('tajneheslo')
+```
+
+Getting results
+
+```php
+$result = dibi::query('SELECT * FROM users');
+
+$value = $result->fetchSingle(); // single value
+$all = $result->fetchAll(); // all rows
+$assoc = $result->fetchAssoc('id'); // all rows as associative array
+$pairs = $result->fetchPairs('customerID', 'name'); // all rows as key => value pairs
+
+// iterating
+foreach ($result as $n => $row) {
+    print_r($row);
+}
+```
+
+Modifiers for arrays:
+
+```php
+dibi::query('SELECT * FROM users WHERE %and', [
+	array('number > ?', 10),
+	array('number < ?', 100),
+]);
+// SELECT * FROM users WHERE (number > 10) AND (number < 100)
+```
+
+<table>
+<tr><td> %and </td><td>  </td><td> `[key]=val AND [key2]="val2" AND ...` </td></tr>
+<tr><td> %or </td><td>  </td><td> `[key]=val OR [key2]="val2" OR ...` </td></tr>
+<tr><td> %a </td><td> assoc </td><td> `[key]=val, [key2]="val2", ...` </td></tr>
+<tr><td> %l %in </td><td> list </td><td> `(val, "val2", ...)` </td></tr>
+<tr><td> %v </td><td> values </td><td> `([key], [key2], ...) VALUES (val, "val2", ...)` </td></tr>
+<tr><td> %m </td><td> multivalues </td><td> `([key], [key2], ...) VALUES (val, "val2", ...), (val, "val2", ...), ...` </td></tr>
+<tr><td> %by </td><td> ordering </td><td> `[key] ASC, [key2] DESC ...` </td></tr>
+<tr><td> %n </td><td> identifiers </td><td> `[key], [key2] AS alias, ...` </td></tr>
+<tr><td> other  </td><td> - </td><td> `val, val2, ...` </td></tr>
+</table>
+
+
+Modifiers for LIKE
+
+```php
+dibi::query("SELECT * FROM table WHERE name LIKE %like~", $query);
+```
+
+<table>
+<tr><td> %like~	</td><td> begins with </td></tr>
+<tr><td> %~like	</td><td> ends with </td></tr>
+<tr><td> %~like~ </td><td> contains </td></tr>
+</table>
+
+DateTime:
+
+```php
+dibi::query('UPDATE users SET', [
+    'time' => new DateTime,
+]);
+// UPDATE users SET ('2008-01-01 01:08:10')
+```
+
+Testing:
+
+```php
+echo dibi::$sql; // last SQL query
+echo dibi::$elapsedTime;
+echo dibi::$numOfQueries;
+echo dibi::$totalTime;
+```

+ 71 - 0
api/vendor/dibi/dibi/src/Dibi/Bridges/Nette/DibiExtension22.php

@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Bridges\Nette;
+
+use Dibi;
+use Nette;
+
+
+/**
+ * Dibi extension for Nette Framework 2.2. Creates 'connection' & 'panel' services.
+ */
+class DibiExtension22 extends Nette\DI\CompilerExtension
+{
+	/** @var bool */
+	private $debugMode;
+
+
+	public function __construct($debugMode = null)
+	{
+		$this->debugMode = $debugMode;
+	}
+
+
+	public function loadConfiguration()
+	{
+		$container = $this->getContainerBuilder();
+		$config = $this->getConfig();
+
+		if ($this->debugMode === null) {
+			$this->debugMode = $container->parameters['debugMode'];
+		}
+
+		$useProfiler = isset($config['profiler'])
+			? $config['profiler']
+			: class_exists('Tracy\Debugger') && $this->debugMode;
+
+		unset($config['profiler']);
+
+		if (isset($config['flags'])) {
+			$flags = 0;
+			foreach ((array) $config['flags'] as $flag) {
+				$flags |= constant($flag);
+			}
+			$config['flags'] = $flags;
+		}
+
+		$connection = $container->addDefinition($this->prefix('connection'))
+			->setClass('Dibi\Connection', [$config])
+			->setAutowired(isset($config['autowired']) ? $config['autowired'] : true);
+
+		if (class_exists('Tracy\Debugger')) {
+			$connection->addSetup(
+				[new Nette\DI\Statement('Tracy\Debugger::getBlueScreen'), 'addPanel'],
+				[['Dibi\Bridges\Tracy\Panel', 'renderException']]
+			);
+		}
+		if ($useProfiler) {
+			$panel = $container->addDefinition($this->prefix('panel'))
+				->setClass('Dibi\Bridges\Tracy\Panel', [
+					isset($config['explain']) ? $config['explain'] : true,
+					isset($config['filter']) && $config['filter'] === false ? Dibi\Event::ALL : Dibi\Event::QUERY,
+				]);
+			$connection->addSetup([$panel, 'register'], [$connection]);
+		}
+	}
+}

+ 12 - 0
api/vendor/dibi/dibi/src/Dibi/Bridges/Nette/config.sample.neon

@@ -0,0 +1,12 @@
+# This will create service named 'dibi.connection'.
+# Requires Nette Framework 2.2 or later
+
+extensions:
+	dibi: Dibi\Bridges\Nette\DibiExtension22
+
+dibi:
+	host: localhost
+	username: root
+	password: ***
+	database: foo
+	lazy: true

+ 657 - 0
api/vendor/dibi/dibi/src/Dibi/Connection.php

@@ -0,0 +1,657 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+use Traversable;
+
+
+/**
+ * dibi connection.
+ *
+ * @property-read int $affectedRows
+ * @property-read int $insertId
+ */
+class Connection
+{
+	use Strict;
+
+	/** @var array of function (Event $event); Occurs after query is executed */
+	public $onEvent;
+
+	/** @var array  Current connection configuration */
+	private $config;
+
+	/** @var Driver */
+	private $driver;
+
+	/** @var Translator */
+	private $translator;
+
+	/** @var bool  Is connected? */
+	private $connected = false;
+
+	/** @var HashMap Substitutes for identifiers */
+	private $substitutes;
+
+
+	/**
+	 * Connection options: (see driver-specific options too)
+	 *   - lazy (bool) => if true, connection will be established only when required
+	 *   - result (array) => result set options
+	 *       - formatDateTime => date-time format (if empty, DateTime objects will be returned)
+	 *   - profiler (array or bool)
+	 *       - run (bool) => enable profiler?
+	 *       - file => file to log
+	 *   - substitutes (array) => map of driver specific substitutes (under development)
+	 * @param  mixed   connection parameters
+	 * @param  string  connection name
+	 * @throws Exception
+	 */
+	public function __construct($config, $name = null)
+	{
+		if (is_string($config)) {
+			parse_str($config, $config);
+
+		} elseif ($config instanceof Traversable) {
+			$tmp = [];
+			foreach ($config as $key => $val) {
+				$tmp[$key] = $val instanceof Traversable ? iterator_to_array($val) : $val;
+			}
+			$config = $tmp;
+
+		} elseif (!is_array($config)) {
+			throw new \InvalidArgumentException('Configuration must be array, string or object.');
+		}
+
+		Helpers::alias($config, 'username', 'user');
+		Helpers::alias($config, 'password', 'pass');
+		Helpers::alias($config, 'host', 'hostname');
+		Helpers::alias($config, 'result|formatDate', 'resultDate');
+		Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
+
+		if (!isset($config['driver'])) {
+			$config['driver'] = \dibi::$defaultDriver;
+		}
+
+		if ($config['driver'] instanceof Driver) {
+			$this->driver = $config['driver'];
+			$config['driver'] = get_class($this->driver);
+		} elseif (is_subclass_of($config['driver'], 'Dibi\Driver')) {
+			$this->driver = new $config['driver'];
+		} else {
+			$class = preg_replace(['#\W#', '#sql#'], ['_', 'Sql'], ucfirst(strtolower($config['driver'])));
+			$class = "Dibi\\Drivers\\{$class}Driver";
+			if (!class_exists($class)) {
+				throw new Exception("Unable to create instance of dibi driver '$class'.");
+			}
+			$this->driver = new $class;
+		}
+
+		$config['name'] = $name;
+		$this->config = $config;
+
+		// profiler
+		$profilerCfg = &$config['profiler'];
+		if (is_scalar($profilerCfg)) {
+			$profilerCfg = ['run' => (bool) $profilerCfg];
+		}
+		if (!empty($profilerCfg['run'])) {
+			$filter = isset($profilerCfg['filter']) ? $profilerCfg['filter'] : Event::QUERY;
+
+			if (isset($profilerCfg['file'])) {
+				$this->onEvent[] = [new Loggers\FileLogger($profilerCfg['file'], $filter), 'logEvent'];
+			}
+
+			if (Loggers\FirePhpLogger::isAvailable()) {
+				$this->onEvent[] = [new Loggers\FirePhpLogger($filter), 'logEvent'];
+			}
+		}
+
+		$this->substitutes = new HashMap(function ($expr) { return ":$expr:"; });
+		if (!empty($config['substitutes'])) {
+			foreach ($config['substitutes'] as $key => $value) {
+				$this->substitutes->$key = $value;
+			}
+		}
+
+		if (empty($config['lazy'])) {
+			$this->connect();
+		}
+	}
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		// disconnects and rolls back transaction - do not rely on auto-disconnect and rollback!
+		$this->connected && $this->driver->getResource() && $this->disconnect();
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 */
+	final public function connect()
+	{
+		$event = $this->onEvent ? new Event($this, Event::CONNECT) : null;
+		try {
+			$this->driver->connect($this->config);
+			$this->connected = true;
+			$event && $this->onEvent($event->done());
+
+		} catch (Exception $e) {
+			$event && $this->onEvent($event->done($e));
+			throw $e;
+		}
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	final public function disconnect()
+	{
+		$this->driver->disconnect();
+		$this->connected = false;
+	}
+
+
+	/**
+	 * Returns true when connection was established.
+	 * @return bool
+	 */
+	final public function isConnected()
+	{
+		return $this->connected;
+	}
+
+
+	/**
+	 * Returns configuration variable. If no $key is passed, returns the entire array.
+	 * @see self::__construct
+	 * @param  string
+	 * @param  mixed  default value to use if key not found
+	 * @return mixed
+	 */
+	final public function getConfig($key = null, $default = null)
+	{
+		if ($key === null) {
+			return $this->config;
+
+		} elseif (isset($this->config[$key])) {
+			return $this->config[$key];
+
+		} else {
+			return $default;
+		}
+	}
+
+
+	/** @deprecated */
+	public static function alias(&$config, $key, $alias)
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use Helpers::alias().', E_USER_DEPRECATED);
+		Helpers::alias($config, $key, $alias);
+	}
+
+
+	/**
+	 * Returns the driver and connects to a database in lazy mode.
+	 * @return Driver
+	 */
+	final public function getDriver()
+	{
+		$this->connected || $this->connect();
+		return $this->driver;
+	}
+
+
+	/**
+	 * Generates (translates) and executes SQL query.
+	 * @param  array|mixed      one or more arguments
+	 * @return Result|int   result set or number of affected rows
+	 * @throws Exception
+	 */
+	final public function query($args)
+	{
+		$args = func_get_args();
+		return $this->nativeQuery($this->translateArgs($args));
+	}
+
+
+	/**
+	 * Generates SQL query.
+	 * @param  array|mixed      one or more arguments
+	 * @return string
+	 * @throws Exception
+	 */
+	final public function translate($args)
+	{
+		$args = func_get_args();
+		return $this->translateArgs($args);
+	}
+
+
+	/**
+	 * Generates and prints SQL query.
+	 * @param  array|mixed  one or more arguments
+	 * @return bool
+	 */
+	final public function test($args)
+	{
+		$args = func_get_args();
+		try {
+			Helpers::dump($this->translateArgs($args));
+			return true;
+
+		} catch (Exception $e) {
+			if ($e->getSql()) {
+				Helpers::dump($e->getSql());
+			} else {
+				echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
+			}
+			return false;
+		}
+	}
+
+
+	/**
+	 * Generates (translates) and returns SQL query as DataSource.
+	 * @param  array|mixed      one or more arguments
+	 * @return DataSource
+	 * @throws Exception
+	 */
+	final public function dataSource($args)
+	{
+		$args = func_get_args();
+		return new DataSource($this->translateArgs($args), $this);
+	}
+
+
+	/**
+	 * Generates SQL query.
+	 * @param  array
+	 * @return string
+	 */
+	protected function translateArgs($args)
+	{
+		$this->connected || $this->connect();
+		if (!$this->translator) {
+			$this->translator = new Translator($this);
+		}
+		$translator = clone $this->translator;
+		return $translator->translate($args);
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string           SQL statement.
+	 * @return Result|int   result set or number of affected rows
+	 * @throws Exception
+	 */
+	final public function nativeQuery($sql)
+	{
+		$this->connected || $this->connect();
+
+		\dibi::$sql = $sql;
+		$event = $this->onEvent ? new Event($this, Event::QUERY, $sql) : null;
+		try {
+			$res = $this->driver->query($sql);
+
+		} catch (Exception $e) {
+			$event && $this->onEvent($event->done($e));
+			throw $e;
+		}
+
+		if ($res) {
+			$res = $this->createResultSet($res);
+		} else {
+			$res = $this->driver->getAffectedRows();
+		}
+
+		$event && $this->onEvent($event->done($res));
+		return $res;
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int  number of rows
+	 * @throws Exception
+	 */
+	public function getAffectedRows()
+	{
+		$this->connected || $this->connect();
+		$rows = $this->driver->getAffectedRows();
+		if (!is_int($rows) || $rows < 0) {
+			throw new Exception('Cannot retrieve number of affected rows.');
+		}
+		return $rows;
+	}
+
+
+	/**
+	 * @deprecated
+	 */
+	public function affectedRows()
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
+		return $this->getAffectedRows();
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @param  string     optional sequence name
+	 * @return int
+	 * @throws Exception
+	 */
+	public function getInsertId($sequence = null)
+	{
+		$this->connected || $this->connect();
+		$id = $this->driver->getInsertId($sequence);
+		if ($id < 1) {
+			throw new Exception('Cannot retrieve last generated ID.');
+		}
+		return Helpers::intVal($id);
+	}
+
+
+	/**
+	 * @deprecated
+	 */
+	public function insertId($sequence = null)
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
+		return $this->getInsertId($sequence);
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 */
+	public function begin($savepoint = null)
+	{
+		$this->connected || $this->connect();
+		$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
+		try {
+			$this->driver->begin($savepoint);
+			$event && $this->onEvent($event->done());
+
+		} catch (Exception $e) {
+			$event && $this->onEvent($event->done($e));
+			throw $e;
+		}
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 */
+	public function commit($savepoint = null)
+	{
+		$this->connected || $this->connect();
+		$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
+		try {
+			$this->driver->commit($savepoint);
+			$event && $this->onEvent($event->done());
+
+		} catch (Exception $e) {
+			$event && $this->onEvent($event->done($e));
+			throw $e;
+		}
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 */
+	public function rollback($savepoint = null)
+	{
+		$this->connected || $this->connect();
+		$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
+		try {
+			$this->driver->rollback($savepoint);
+			$event && $this->onEvent($event->done());
+
+		} catch (Exception $e) {
+			$event && $this->onEvent($event->done($e));
+			throw $e;
+		}
+	}
+
+
+	/**
+	 * Result set factory.
+	 * @param  ResultDriver
+	 * @return Result
+	 */
+	public function createResultSet(ResultDriver $resultDriver)
+	{
+		$res = new Result($resultDriver);
+		return $res->setFormat(Type::DATE, $this->config['result']['formatDate'])
+			->setFormat(Type::DATETIME, $this->config['result']['formatDateTime']);
+	}
+
+
+	/********************* fluent SQL builders ****************d*g**/
+
+
+	/**
+	 * @return Fluent
+	 */
+	public function command()
+	{
+		return new Fluent($this);
+	}
+
+
+	/**
+	 * @param  mixed    column name
+	 * @return Fluent
+	 */
+	public function select($args)
+	{
+		$args = func_get_args();
+		return $this->command()->__call('select', $args);
+	}
+
+
+	/**
+	 * @param  string   table
+	 * @param  array
+	 * @return Fluent
+	 */
+	public function update($table, $args)
+	{
+		if (!(is_array($args) || $args instanceof Traversable)) {
+			throw new \InvalidArgumentException('Arguments must be array or Traversable.');
+		}
+		return $this->command()->update('%n', $table)->set($args);
+	}
+
+
+	/**
+	 * @param  string   table
+	 * @param  array
+	 * @return Fluent
+	 */
+	public function insert($table, $args)
+	{
+		if ($args instanceof Traversable) {
+			$args = iterator_to_array($args);
+		} elseif (!is_array($args)) {
+			throw new \InvalidArgumentException('Arguments must be array or Traversable.');
+		}
+		return $this->command()->insert()
+			->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
+	}
+
+
+	/**
+	 * @param  string   table
+	 * @return Fluent
+	 */
+	public function delete($table)
+	{
+		return $this->command()->delete()->from('%n', $table);
+	}
+
+
+	/********************* substitutions ****************d*g**/
+
+
+	/**
+	 * Returns substitution hashmap.
+	 * @return HashMap
+	 */
+	public function getSubstitutes()
+	{
+		return $this->substitutes;
+	}
+
+
+	/**
+	 * Provides substitution.
+	 * @return string
+	 */
+	public function substitute($value)
+	{
+		return strpos($value, ':') === false
+			? $value
+			: preg_replace_callback('#:([^:\s]*):#', function ($m) { return $this->substitutes->{$m[1]}; }, $value);
+	}
+
+
+	/********************* shortcuts ****************d*g**/
+
+
+	/**
+	 * Executes SQL query and fetch result - shortcut for query() & fetch().
+	 * @param  array|mixed    one or more arguments
+	 * @return Row|false
+	 * @throws Exception
+	 */
+	public function fetch($args)
+	{
+		$args = func_get_args();
+		return $this->query($args)->fetch();
+	}
+
+
+	/**
+	 * Executes SQL query and fetch results - shortcut for query() & fetchAll().
+	 * @param  array|mixed    one or more arguments
+	 * @return Row[]
+	 * @throws Exception
+	 */
+	public function fetchAll($args)
+	{
+		$args = func_get_args();
+		return $this->query($args)->fetchAll();
+	}
+
+
+	/**
+	 * Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
+	 * @param  array|mixed    one or more arguments
+	 * @return mixed
+	 * @throws Exception
+	 */
+	public function fetchSingle($args)
+	{
+		$args = func_get_args();
+		return $this->query($args)->fetchSingle();
+	}
+
+
+	/**
+	 * Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
+	 * @param  array|mixed    one or more arguments
+	 * @return array
+	 * @throws Exception
+	 */
+	public function fetchPairs($args)
+	{
+		$args = func_get_args();
+		return $this->query($args)->fetchPairs();
+	}
+
+
+	/**
+	 * @return Literal
+	 */
+	public static function literal($value)
+	{
+		return new Literal($value);
+	}
+
+
+	/********************* misc ****************d*g**/
+
+
+	/**
+	 * Import SQL dump from file.
+	 * @param  string  filename
+	 * @param  callable  function (int $count, ?float $percent): void
+	 * @return int  count of sql commands
+	 */
+	public function loadFile($file, callable $onProgress = null)
+	{
+		return Helpers::loadFromFile($this, $file, $onProgress);
+	}
+
+
+	/**
+	 * Gets a information about the current database.
+	 * @return Reflection\Database
+	 */
+	public function getDatabaseInfo()
+	{
+		$this->connected || $this->connect();
+		return new Reflection\Database($this->driver->getReflector(), isset($this->config['database']) ? $this->config['database'] : null);
+	}
+
+
+	/**
+	 * Prevents unserialization.
+	 */
+	public function __wakeup()
+	{
+		throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
+	}
+
+
+	/**
+	 * Prevents serialization.
+	 */
+	public function __sleep()
+	{
+		throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.');
+	}
+
+
+	protected function onEvent($arg)
+	{
+		foreach ($this->onEvent ?: [] as $handler) {
+			call_user_func($handler, $arg);
+		}
+	}
+}

+ 308 - 0
api/vendor/dibi/dibi/src/Dibi/DataSource.php

@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * Default implementation of IDataSource for dibi.
+ */
+class DataSource implements IDataSource
+{
+	use Strict;
+
+	/** @var Connection */
+	private $connection;
+
+	/** @var string */
+	private $sql;
+
+	/** @var Result */
+	private $result;
+
+	/** @var int */
+	private $count;
+
+	/** @var int */
+	private $totalCount;
+
+	/** @var array */
+	private $cols = [];
+
+	/** @var array */
+	private $sorting = [];
+
+	/** @var array */
+	private $conds = [];
+
+	/** @var int|null */
+	private $offset;
+
+	/** @var int|null */
+	private $limit;
+
+
+	/**
+	 * @param  string  SQL command or table or view name, as data source
+	 * @param  Connection  connection
+	 */
+	public function __construct($sql, Connection $connection)
+	{
+		if (strpbrk($sql, " \t\r\n") === false) {
+			$this->sql = $connection->getDriver()->escapeIdentifier($sql); // table name
+		} else {
+			$this->sql = '(' . $sql . ') t'; // SQL command
+		}
+		$this->connection = $connection;
+	}
+
+
+	/**
+	 * Selects columns to query.
+	 * @param  string|array  column name or array of column names
+	 * @param  string        column alias
+	 * @return self
+	 */
+	public function select($col, $as = null)
+	{
+		if (is_array($col)) {
+			$this->cols = $col;
+		} else {
+			$this->cols[$col] = $as;
+		}
+		$this->result = null;
+		return $this;
+	}
+
+
+	/**
+	 * Adds conditions to query.
+	 * @param  mixed  conditions
+	 * @return self
+	 */
+	public function where($cond)
+	{
+		if (is_array($cond)) {
+			// TODO: not consistent with select and orderBy
+			$this->conds[] = $cond;
+		} else {
+			$this->conds[] = func_get_args();
+		}
+		$this->result = $this->count = null;
+		return $this;
+	}
+
+
+	/**
+	 * Selects columns to order by.
+	 * @param  string|array  column name or array of column names
+	 * @param  string        sorting direction
+	 * @return self
+	 */
+	public function orderBy($row, $sorting = 'ASC')
+	{
+		if (is_array($row)) {
+			$this->sorting = $row;
+		} else {
+			$this->sorting[$row] = $sorting;
+		}
+		$this->result = null;
+		return $this;
+	}
+
+
+	/**
+	 * Limits number of rows.
+	 * @param  int|null limit
+	 * @param  int offset
+	 * @return self
+	 */
+	public function applyLimit($limit, $offset = null)
+	{
+		$this->limit = $limit;
+		$this->offset = $offset;
+		$this->result = $this->count = null;
+		return $this;
+	}
+
+
+	/**
+	 * Returns the dibi connection.
+	 * @return Connection
+	 */
+	final public function getConnection()
+	{
+		return $this->connection;
+	}
+
+
+	/********************* executing ****************d*g**/
+
+
+	/**
+	 * Returns (and queries) Result.
+	 * @return Result
+	 */
+	public function getResult()
+	{
+		if ($this->result === null) {
+			$this->result = $this->connection->nativeQuery($this->__toString());
+		}
+		return $this->result;
+	}
+
+
+	/**
+	 * @return ResultIterator
+	 */
+	public function getIterator()
+	{
+		return $this->getResult()->getIterator();
+	}
+
+
+	/**
+	 * Generates, executes SQL query and fetches the single row.
+	 * @return Row|false
+	 */
+	public function fetch()
+	{
+		return $this->getResult()->fetch();
+	}
+
+
+	/**
+	 * Like fetch(), but returns only first field.
+	 * @return mixed  value on success, false if no next record
+	 */
+	public function fetchSingle()
+	{
+		return $this->getResult()->fetchSingle();
+	}
+
+
+	/**
+	 * Fetches all records from table.
+	 * @return array
+	 */
+	public function fetchAll()
+	{
+		return $this->getResult()->fetchAll();
+	}
+
+
+	/**
+	 * Fetches all records from table and returns associative tree.
+	 * @param  string  associative descriptor
+	 * @return array
+	 */
+	public function fetchAssoc($assoc)
+	{
+		return $this->getResult()->fetchAssoc($assoc);
+	}
+
+
+	/**
+	 * Fetches all records from table like $key => $value pairs.
+	 * @param  string  associative key
+	 * @param  string  value
+	 * @return array
+	 */
+	public function fetchPairs($key = null, $value = null)
+	{
+		return $this->getResult()->fetchPairs($key, $value);
+	}
+
+
+	/**
+	 * Discards the internal cache.
+	 * @return void
+	 */
+	public function release()
+	{
+		$this->result = $this->count = $this->totalCount = null;
+	}
+
+
+	/********************* exporting ****************d*g**/
+
+
+	/**
+	 * Returns this data source wrapped in Fluent object.
+	 * @return Fluent
+	 */
+	public function toFluent()
+	{
+		return $this->connection->select('*')->from('(%SQL) t', $this->__toString());
+	}
+
+
+	/**
+	 * Returns this data source wrapped in DataSource object.
+	 * @return DataSource
+	 */
+	public function toDataSource()
+	{
+		return new self($this->__toString(), $this->connection);
+	}
+
+
+	/**
+	 * Returns SQL query.
+	 * @return string
+	 */
+	public function __toString()
+	{
+		try {
+			return $this->connection->translate('
+SELECT %n', (empty($this->cols) ? '*' : $this->cols), '
+FROM %SQL', $this->sql, '
+%ex', $this->conds ? ['WHERE %and', $this->conds] : null, '
+%ex', $this->sorting ? ['ORDER BY %by', $this->sorting] : null, '
+%ofs %lmt', $this->offset, $this->limit
+			);
+		} catch (\Exception $e) {
+			trigger_error($e->getMessage(), E_USER_ERROR);
+			return '';
+		}
+	}
+
+
+	/********************* counting ****************d*g**/
+
+
+	/**
+	 * Returns the number of rows in a given data source.
+	 * @return int
+	 */
+	public function count()
+	{
+		if ($this->count === null) {
+			$this->count = $this->conds || $this->offset || $this->limit
+				? Helpers::intVal($this->connection->nativeQuery(
+					'SELECT COUNT(*) FROM (' . $this->__toString() . ') t'
+				)->fetchSingle())
+				: $this->getTotalCount();
+		}
+		return $this->count;
+	}
+
+
+	/**
+	 * Returns the number of rows in a given data source.
+	 * @return int
+	 */
+	public function getTotalCount()
+	{
+		if ($this->totalCount === null) {
+			$this->totalCount = Helpers::intVal($this->connection->nativeQuery(
+				'SELECT COUNT(*) FROM ' . $this->sql
+			)->fetchSingle());
+		}
+		return $this->totalCount;
+	}
+}

+ 74 - 0
api/vendor/dibi/dibi/src/Dibi/DateTime.php

@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * DateTime.
+ */
+class DateTime extends \DateTime
+{
+	use Strict;
+
+	/**
+	 * @param  string|int
+	 */
+	public function __construct($time = 'now', \DateTimeZone $timezone = null)
+	{
+		if (is_numeric($time)) {
+			parent::__construct('@' . $time);
+			$this->setTimeZone($timezone ? $timezone : new \DateTimeZone(date_default_timezone_get()));
+		} elseif ($timezone === null) {
+			parent::__construct($time);
+		} else {
+			parent::__construct($time, $timezone);
+		}
+	}
+
+
+	public function modifyClone($modify = '')
+	{
+		$dolly = clone $this;
+		return $modify ? $dolly->modify($modify) : $dolly;
+	}
+
+
+	public function setTimestamp($timestamp)
+	{
+		$zone = $this->getTimezone();
+		$this->__construct('@' . $timestamp);
+		return $this->setTimeZone($zone);
+	}
+
+
+	public function getTimestamp()
+	{
+		$ts = $this->format('U');
+		return is_float($tmp = $ts * 1) ? $ts : $tmp;
+	}
+
+
+	public function __toString()
+	{
+		return $this->format('Y-m-d H:i:s.u');
+	}
+
+
+	public function __wakeup()
+	{
+		if (isset($this->fix, $this->fix[1])) {
+			$this->__construct($this->fix[0], new \DateTimeZone($this->fix[1]));
+			unset($this->fix);
+		} elseif (isset($this->fix)) {
+			$this->__construct($this->fix[0]);
+			unset($this->fix);
+		} else {
+			parent::__wakeup();
+		}
+	}
+}

+ 839 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/FirebirdDriver.php

@@ -0,0 +1,839 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi driver for Firebird/InterBase database.
+ *
+ * Driver options:
+ *   - database => the path to database file (server:/path/database.fdb)
+ *   - username (or user)
+ *   - password (or pass)
+ *   - charset => character encoding to set
+ *   - buffers (int) => buffers is the number of database buffers to allocate for the server-side cache. If 0 or omitted, server chooses its own default.
+ *   - resource (resource) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class FirebirdDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	const ERROR_EXCEPTION_THROWN = -836;
+
+	/** @var resource|null */
+	private $connection;
+
+	/** @var resource|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var resource|null */
+	private $transaction;
+
+	/** @var bool */
+	private $inTransaction = false;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('interbase')) {
+			throw new Dibi\NotSupportedException("PHP extension 'interbase' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		Dibi\Helpers::alias($config, 'database', 'db');
+
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+
+		} else {
+			// default values
+			$config += [
+				'username' => ini_get('ibase.default_password'),
+				'password' => ini_get('ibase.default_user'),
+				'database' => ini_get('ibase.default_db'),
+				'charset' => ini_get('ibase.default_charset'),
+				'buffers' => 0,
+			];
+
+			if (empty($config['persistent'])) {
+				$this->connection = @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
+			} else {
+				$this->connection = @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
+			}
+
+			if (!is_resource($this->connection)) {
+				throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode());
+			}
+		}
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@ibase_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException|Dibi\Exception
+	 */
+	public function query($sql)
+	{
+		$resource = $this->inTransaction ? $this->transaction : $this->connection;
+		$res = ibase_query($resource, $sql);
+
+		if ($res === false) {
+			if (ibase_errcode() == self::ERROR_EXCEPTION_THROWN) {
+				preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match);
+				throw new Dibi\ProcedureException($match[3], $match[1], $match[2], $sql);
+
+			} else {
+				throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql);
+			}
+
+		} elseif (is_resource($res)) {
+			return $this->createResultDriver($res);
+		}
+		return null;
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return ibase_affected_rows($this->connection);
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @param  string     generator name
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		return ibase_gen_id($sequence, 0, $this->connection);
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		if ($savepoint !== null) {
+			throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
+		}
+		$this->transaction = ibase_trans($this->getResource());
+		$this->inTransaction = true;
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		if ($savepoint !== null) {
+			throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
+		}
+
+		if (!ibase_commit($this->transaction)) {
+			throw new Dibi\DriverException('Unable to handle operation - failure when commiting transaction.');
+		}
+
+		$this->inTransaction = false;
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		if ($savepoint !== null) {
+			throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
+		}
+
+		if (!ibase_rollback($this->transaction)) {
+			throw new Dibi\DriverException('Unable to handle operation - failure when rolbacking transaction.');
+		}
+
+		$this->inTransaction = false;
+	}
+
+
+	/**
+	 * Is in transaction?
+	 * @return bool
+	 */
+	public function inTransaction()
+	{
+		return $this->inTransaction;
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return resource|null
+	 */
+	public function getResource()
+	{
+		return is_resource($this->connection) ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return $this;
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  resource
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver($resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ********************/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeText($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		return '"' . str_replace('"', '""', $value) . '"';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? '1' : '0';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d'");
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d H:i:s.u'");
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		throw new Dibi\NotImplementedException;
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit > 0 || $offset > 0) {
+			// http://www.firebirdsql.org/refdocs/langrefupd20-select.html
+			$sql = 'SELECT ' . ($limit > 0 ? 'FIRST ' . Dibi\Helpers::intVal($limit) : '')
+				. ($offset > 0 ? ' SKIP ' . Dibi\Helpers::intVal($offset) : '')
+				. ' * FROM (' . $sql . ')';
+		}
+	}
+
+
+	/********************* result set ********************/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && $this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		throw new Dibi\NotSupportedException('Firebird/Interbase do not support returning number of rows in result set.');
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		$result = $assoc ? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
+
+		if (ibase_errcode()) {
+			if (ibase_errcode() == self::ERROR_EXCEPTION_THROWN) {
+				preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
+				throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);
+
+			} else {
+				throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode());
+			}
+		}
+
+		return $result;
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 * @throws Dibi\Exception
+	 */
+	public function seek($row)
+	{
+		throw new Dibi\NotSupportedException('Firebird/Interbase do not support seek in result set.');
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		ibase_free_result($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return resource|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return is_resource($this->resultSet) ? $this->resultSet : null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$count = ibase_num_fields($this->resultSet);
+		$columns = [];
+		for ($i = 0; $i < $count; $i++) {
+			$row = (array) ibase_field_info($this->resultSet, $i);
+			$columns[] = [
+				'name' => $row['name'],
+				'fullname' => $row['name'],
+				'table' => $row['relation'],
+				'nativetype' => $row['type'],
+			];
+		}
+		return $columns;
+	}
+
+
+	/********************* Dibi\Reflector ********************/
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$res = $this->query("
+			SELECT TRIM(RDB\$RELATION_NAME),
+				CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END
+			FROM RDB\$RELATIONS
+			WHERE RDB\$SYSTEM_FLAG = 0;"
+		);
+		$tables = [];
+		while ($row = $res->fetch(false)) {
+			$tables[] = [
+				'name' => $row[0],
+				'view' => $row[1] === 'TRUE',
+			];
+		}
+		return $tables;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$table = strtoupper($table);
+		$res = $this->query("
+			SELECT TRIM(r.RDB\$FIELD_NAME) AS FIELD_NAME,
+				CASE f.RDB\$FIELD_TYPE
+					WHEN 261 THEN 'BLOB'
+					WHEN 14 THEN 'CHAR'
+					WHEN 40 THEN 'CSTRING'
+					WHEN 11 THEN 'D_FLOAT'
+					WHEN 27 THEN 'DOUBLE'
+					WHEN 10 THEN 'FLOAT'
+					WHEN 16 THEN 'INT64'
+					WHEN 8 THEN 'INTEGER'
+					WHEN 9 THEN 'QUAD'
+					WHEN 7 THEN 'SMALLINT'
+					WHEN 12 THEN 'DATE'
+					WHEN 13 THEN 'TIME'
+					WHEN 35 THEN 'TIMESTAMP'
+					WHEN 37 THEN 'VARCHAR'
+					ELSE 'UNKNOWN'
+				END AS FIELD_TYPE,
+				f.RDB\$FIELD_LENGTH AS FIELD_LENGTH,
+				r.RDB\$DEFAULT_VALUE AS DEFAULT_VALUE,
+				CASE r.RDB\$NULL_FLAG
+					WHEN 1 THEN 'FALSE' ELSE 'TRUE'
+				END AS NULLABLE
+			FROM RDB\$RELATION_FIELDS r
+				LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
+			WHERE r.RDB\$RELATION_NAME = '$table'
+			ORDER BY r.RDB\$FIELD_POSITION;"
+
+		);
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$key = $row['FIELD_NAME'];
+			$columns[$key] = [
+				'name' => $key,
+				'table' => $table,
+				'nativetype' => trim($row['FIELD_TYPE']),
+				'size' => $row['FIELD_LENGTH'],
+				'nullable' => $row['NULLABLE'] === 'TRUE',
+				'default' => $row['DEFAULT_VALUE'],
+				'autoincrement' => false,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table (the constraints are included).
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		$table = strtoupper($table);
+		$res = $this->query("
+			SELECT TRIM(s.RDB\$INDEX_NAME) AS INDEX_NAME,
+				TRIM(s.RDB\$FIELD_NAME) AS FIELD_NAME,
+				i.RDB\$UNIQUE_FLAG AS UNIQUE_FLAG,
+				i.RDB\$FOREIGN_KEY AS FOREIGN_KEY,
+				TRIM(r.RDB\$CONSTRAINT_TYPE) AS CONSTRAINT_TYPE,
+				s.RDB\$FIELD_POSITION AS FIELD_POSITION
+			FROM RDB\$INDEX_SEGMENTS s
+				LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
+				LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
+			WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
+			ORDER BY s.RDB\$FIELD_POSITION"
+		);
+		$indexes = [];
+		while ($row = $res->fetch(true)) {
+			$key = $row['INDEX_NAME'];
+			$indexes[$key]['name'] = $key;
+			$indexes[$key]['unique'] = $row['UNIQUE_FLAG'] === 1;
+			$indexes[$key]['primary'] = $row['CONSTRAINT_TYPE'] === 'PRIMARY KEY';
+			$indexes[$key]['table'] = $table;
+			$indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME'];
+		}
+		return $indexes;
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getForeignKeys($table)
+	{
+		$table = strtoupper($table);
+		$res = $this->query("
+			SELECT TRIM(s.RDB\$INDEX_NAME) AS INDEX_NAME,
+				TRIM(s.RDB\$FIELD_NAME) AS FIELD_NAME,
+			FROM RDB\$INDEX_SEGMENTS s
+				LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
+			WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
+				AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
+			ORDER BY s.RDB\$FIELD_POSITION"
+		);
+		$keys = [];
+		while ($row = $res->fetch(true)) {
+			$key = $row['INDEX_NAME'];
+			$keys[$key] = [
+				'name' => $key,
+				'column' => $row['FIELD_NAME'],
+				'table' => $table,
+			];
+		}
+		return $keys;
+	}
+
+
+	/**
+	 * Returns list of indices in given table (the constraints are not listed).
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndices($table)
+	{
+		$res = $this->query("
+			SELECT TRIM(RDB\$INDEX_NAME)
+			FROM RDB\$INDICES
+			WHERE RDB\$RELATION_NAME = UPPER('$table')
+				AND RDB\$UNIQUE_FLAG IS NULL
+				AND RDB\$FOREIGN_KEY IS NULL;"
+		);
+		$indices = [];
+		while ($row = $res->fetch(false)) {
+			$indices[] = $row[0];
+		}
+		return $indices;
+	}
+
+
+	/**
+	 * Returns list of constraints in given table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getConstraints($table)
+	{
+		$res = $this->query("
+			SELECT TRIM(RDB\$INDEX_NAME)
+			FROM RDB\$INDICES
+			WHERE RDB\$RELATION_NAME = UPPER('$table')
+				AND (
+					RDB\$UNIQUE_FLAG IS NOT NULL
+					OR RDB\$FOREIGN_KEY IS NOT NULL
+			);"
+		);
+		$constraints = [];
+		while ($row = $res->fetch(false)) {
+			$constraints[] = $row[0];
+		}
+		return $constraints;
+	}
+
+
+	/**
+	 * Returns metadata for all triggers in a table or database.
+	 * (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
+	 * @param  string
+	 * @param  string
+	 * @return array
+	 */
+	public function getTriggersMeta($table = null)
+	{
+		$res = $this->query("
+			SELECT TRIM(RDB\$TRIGGER_NAME) AS TRIGGER_NAME,
+				TRIM(RDB\$RELATION_NAME) AS TABLE_NAME,
+				CASE RDB\$TRIGGER_TYPE
+					WHEN 1 THEN 'BEFORE'
+					WHEN 2 THEN 'AFTER'
+					WHEN 3 THEN 'BEFORE'
+					WHEN 4 THEN 'AFTER'
+					WHEN 5 THEN 'BEFORE'
+					WHEN 6 THEN 'AFTER'
+				END AS TRIGGER_TYPE,
+				CASE RDB\$TRIGGER_TYPE
+					WHEN 1 THEN 'INSERT'
+					WHEN 2 THEN 'INSERT'
+					WHEN 3 THEN 'UPDATE'
+					WHEN 4 THEN 'UPDATE'
+					WHEN 5 THEN 'DELETE'
+					WHEN 6 THEN 'DELETE'
+				END AS TRIGGER_EVENT,
+				CASE RDB\$TRIGGER_INACTIVE
+					WHEN 1 THEN 'FALSE' ELSE 'TRUE'
+				END AS TRIGGER_ENABLED
+			FROM RDB\$TRIGGERS
+			WHERE RDB\$SYSTEM_FLAG = 0"
+			. ($table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table');")
+		);
+		$triggers = [];
+		while ($row = $res->fetch(true)) {
+			$triggers[$row['TRIGGER_NAME']] = [
+				'name' => $row['TRIGGER_NAME'],
+				'table' => $row['TABLE_NAME'],
+				'type' => trim($row['TRIGGER_TYPE']),
+				'event' => trim($row['TRIGGER_EVENT']),
+				'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE',
+			];
+		}
+		return $triggers;
+	}
+
+
+	/**
+	 * Returns list of triggers for given table.
+	 * (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
+	 * @param  string
+	 * @return array
+	 */
+	public function getTriggers($table = null)
+	{
+		$q = 'SELECT TRIM(RDB$TRIGGER_NAME)
+			FROM RDB$TRIGGERS
+			WHERE RDB$SYSTEM_FLAG = 0';
+		$q .= $table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table')";
+
+		$res = $this->query($q);
+		$triggers = [];
+		while ($row = $res->fetch(false)) {
+			$triggers[] = $row[0];
+		}
+		return $triggers;
+	}
+
+
+	/**
+	 * Returns metadata from stored procedures and their input and output parameters.
+	 * @param  string
+	 * @return array
+	 */
+	public function getProceduresMeta()
+	{
+		$res = $this->query("
+			SELECT
+				TRIM(p.RDB\$PARAMETER_NAME) AS PARAMETER_NAME,
+				TRIM(p.RDB\$PROCEDURE_NAME) AS PROCEDURE_NAME,
+				CASE p.RDB\$PARAMETER_TYPE
+					WHEN 0 THEN 'INPUT'
+					WHEN 1 THEN 'OUTPUT'
+					ELSE 'UNKNOWN'
+				END AS PARAMETER_TYPE,
+				CASE f.RDB\$FIELD_TYPE
+					WHEN 261 THEN 'BLOB'
+					WHEN 14 THEN 'CHAR'
+					WHEN 40 THEN 'CSTRING'
+					WHEN 11 THEN 'D_FLOAT'
+					WHEN 27 THEN 'DOUBLE'
+					WHEN 10 THEN 'FLOAT'
+					WHEN 16 THEN 'INT64'
+					WHEN 8 THEN 'INTEGER'
+					WHEN 9 THEN 'QUAD'
+					WHEN 7 THEN 'SMALLINT'
+					WHEN 12 THEN 'DATE'
+					WHEN 13 THEN 'TIME'
+					WHEN 35 THEN 'TIMESTAMP'
+					WHEN 37 THEN 'VARCHAR'
+					ELSE 'UNKNOWN'
+				END AS FIELD_TYPE,
+				f.RDB\$FIELD_LENGTH AS FIELD_LENGTH,
+				p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
+			FROM RDB\$PROCEDURE_PARAMETERS p
+				LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE
+			ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;"
+		);
+		$procedures = [];
+		while ($row = $res->fetch(true)) {
+			$key = $row['PROCEDURE_NAME'];
+			$io = trim($row['PARAMETER_TYPE']);
+			$num = $row['PARAMETER_NUMBER'];
+			$procedures[$key]['name'] = $row['PROCEDURE_NAME'];
+			$procedures[$key]['params'][$io][$num]['name'] = $row['PARAMETER_NAME'];
+			$procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']);
+			$procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH'];
+		}
+		return $procedures;
+	}
+
+
+	/**
+	 * Returns list of stored procedures.
+	 * @return array
+	 */
+	public function getProcedures()
+	{
+		$res = $this->query('
+			SELECT TRIM(RDB$PROCEDURE_NAME)
+			FROM RDB$PROCEDURES;'
+		);
+		$procedures = [];
+		while ($row = $res->fetch(false)) {
+			$procedures[] = $row[0];
+		}
+		return $procedures;
+	}
+
+
+	/**
+	 * Returns list of generators.
+	 * @return array
+	 */
+	public function getGenerators()
+	{
+		$res = $this->query('
+			SELECT TRIM(RDB$GENERATOR_NAME)
+			FROM RDB$GENERATORS
+			WHERE RDB$SYSTEM_FLAG = 0;'
+		);
+		$generators = [];
+		while ($row = $res->fetch(false)) {
+			$generators[] = $row[0];
+		}
+		return $generators;
+	}
+
+
+	/**
+	 * Returns list of user defined functions (UDF).
+	 * @return array
+	 */
+	public function getFunctions()
+	{
+		$res = $this->query('
+			SELECT TRIM(RDB$FUNCTION_NAME)
+			FROM RDB$FUNCTIONS
+			WHERE RDB$SYSTEM_FLAG = 0;'
+		);
+		$functions = [];
+		while ($row = $res->fetch(false)) {
+			$functions[] = $row[0];
+		}
+		return $functions;
+	}
+}

+ 409 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/MsSqlDriver.php

@@ -0,0 +1,409 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi driver for MS SQL database.
+ *
+ * Driver options:
+ *   - host => the MS SQL server host name. It can also include a port number (hostname:port)
+ *   - username (or user)
+ *   - password (or pass)
+ *   - database => the database name to select
+ *   - persistent (bool) => try to find a persistent link?
+ *   - resource (resource) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class MsSqlDriver implements Dibi\Driver, Dibi\ResultDriver
+{
+	use Dibi\Strict;
+
+	/** @var resource|null */
+	private $connection;
+
+	/** @var resource|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('mssql')) {
+			throw new Dibi\NotSupportedException("PHP extension 'mssql' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+		} elseif (empty($config['persistent'])) {
+			$this->connection = @mssql_connect($config['host'], $config['username'], $config['password'], true); // intentionally @
+		} else {
+			$this->connection = @mssql_pconnect($config['host'], $config['username'], $config['password']); // intentionally @
+		}
+
+		if (!is_resource($this->connection)) {
+			throw new Dibi\DriverException("Can't connect to DB.");
+		}
+
+		if (isset($config['database']) && !@mssql_select_db($this->escapeIdentifier($config['database']), $this->connection)) { // intentionally @
+			throw new Dibi\DriverException("Can't select DB '$config[database]'.");
+		}
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@mssql_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		$res = @mssql_query($sql, $this->connection); // intentionally @
+
+		if ($res === false) {
+			throw new Dibi\DriverException(mssql_get_last_message(), 0, $sql);
+
+		} elseif (is_resource($res)) {
+			return $this->createResultDriver($res);
+		}
+		return null;
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return mssql_rows_affected($this->connection);
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		$res = mssql_query('SELECT @@IDENTITY', $this->connection);
+		if (is_resource($res)) {
+			$row = mssql_fetch_row($res);
+			return $row[0];
+		}
+		return false;
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		$this->query('BEGIN TRANSACTION');
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		$this->query('COMMIT');
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		$this->query('ROLLBACK');
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return resource|null
+	 */
+	public function getResource()
+	{
+		return is_resource($this->connection) ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return new MsSqlReflector($this);
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  resource
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver($resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
+		return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? '1' : '0';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d'");
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d H:i:s.u'");
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($offset) {
+			throw new Dibi\NotSupportedException('Offset is not supported by this database.');
+
+		} elseif ($limit < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+
+		} elseif ($limit !== null) {
+			$sql = 'SELECT TOP ' . Dibi\Helpers::intVal($limit) . ' * FROM (' . $sql . ') t';
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && $this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		return mssql_num_rows($this->resultSet);
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		return mssql_fetch_array($this->resultSet, $assoc ? MSSQL_ASSOC : MSSQL_NUM);
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int      the 0-based cursor pos to seek to
+	 * @return bool     true on success, false if unable to seek to specified record
+	 */
+	public function seek($row)
+	{
+		return mssql_data_seek($this->resultSet, $row);
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		mssql_free_result($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$count = mssql_num_fields($this->resultSet);
+		$columns = [];
+		for ($i = 0; $i < $count; $i++) {
+			$row = (array) mssql_fetch_field($this->resultSet, $i);
+			$columns[] = [
+				'name' => $row['name'],
+				'fullname' => $row['column_source'] ? $row['column_source'] . '.' . $row['name'] : $row['name'],
+				'table' => $row['column_source'],
+				'nativetype' => $row['type'],
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return resource|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return is_resource($this->resultSet) ? $this->resultSet : null;
+	}
+}

+ 215 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/MsSqlReflector.php

@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi reflector for MS SQL databases.
+ * @internal
+ */
+class MsSqlReflector implements Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\Driver */
+	private $driver;
+
+
+	public function __construct(Dibi\Driver $driver)
+	{
+		$this->driver = $driver;
+	}
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$res = $this->driver->query('
+			SELECT TABLE_NAME, TABLE_TYPE
+			FROM INFORMATION_SCHEMA.TABLES
+		');
+		$tables = [];
+		while ($row = $res->fetch(false)) {
+			$tables[] = [
+				'name' => $row[0],
+				'view' => isset($row[1]) && $row[1] === 'VIEW',
+			];
+		}
+		return $tables;
+	}
+
+
+	/**
+	 * Returns count of rows in a table
+	 * @param  string
+	 * @return int
+	 */
+	public function getTableCount($table, $fallback = true)
+	{
+		if (empty($table)) {
+			return false;
+		}
+		$result = $this->driver->query("
+			SELECT MAX(rowcnt)
+			FROM sys.sysindexes
+			WHERE id=OBJECT_ID({$this->driver->escapeIdentifier($table)})
+		");
+		$row = $result->fetch(false);
+
+		if (!is_array($row) || count($row) < 1) {
+			if ($fallback) {
+				$row = $this->driver->query("SELECT COUNT(*) FROM {$this->driver->escapeIdentifier($table)}")->fetch(false);
+				$count = Dibi\Helpers::intVal($row[0]);
+			} else {
+				$count = false;
+			}
+		} else {
+			$count = Dibi\Helpers::intVal($row[0]);
+		}
+
+		return $count;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$res = $this->driver->query("
+			SELECT * FROM
+			INFORMATION_SCHEMA.COLUMNS
+			WHERE TABLE_NAME = {$this->driver->escapeText($table)}
+			ORDER BY TABLE_NAME, ORDINAL_POSITION
+		");
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$size = false;
+			$type = strtoupper($row['DATA_TYPE']);
+
+			$size_cols = [
+				'DATETIME' => 'DATETIME_PRECISION',
+				'DECIMAL' => 'NUMERIC_PRECISION',
+				'CHAR' => 'CHARACTER_MAXIMUM_LENGTH',
+				'NCHAR' => 'CHARACTER_OCTET_LENGTH',
+				'NVARCHAR' => 'CHARACTER_OCTET_LENGTH',
+				'VARCHAR' => 'CHARACTER_OCTET_LENGTH',
+			];
+
+			if (isset($size_cols[$type])) {
+				if ($size_cols[$type]) {
+					$size = $row[$size_cols[$type]];
+				}
+			}
+
+			$columns[] = [
+				'name' => $row['COLUMN_NAME'],
+				'table' => $table,
+				'nativetype' => $type,
+				'size' => $size,
+				'unsigned' => null,
+				'nullable' => $row['IS_NULLABLE'] === 'YES',
+				'default' => $row['COLUMN_DEFAULT'],
+				'autoincrement' => false,
+				'vendor' => $row,
+			];
+		}
+
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		$res = $this->driver->query(
+			"SELECT ind.name index_name, ind.index_id, ic.index_column_id,
+					col.name column_name, ind.is_unique, ind.is_primary_key
+			FROM sys.indexes ind
+			INNER JOIN sys.index_columns ic ON
+				(ind.object_id = ic.object_id AND ind.index_id = ic.index_id)
+			INNER JOIN sys.columns col ON
+				(ic.object_id = col.object_id and ic.column_id = col.column_id)
+			INNER JOIN sys.tables t ON
+				(ind.object_id = t.object_id)
+			WHERE t.name = {$this->driver->escapeText($table)}
+				AND t.is_ms_shipped = 0
+			ORDER BY
+				t.name, ind.name, ind.index_id, ic.index_column_id
+		");
+
+		$indexes = [];
+		while ($row = $res->fetch(true)) {
+			$index_name = $row['index_name'];
+
+			if (!isset($indexes[$index_name])) {
+				$indexes[$index_name] = [];
+				$indexes[$index_name]['name'] = $index_name;
+				$indexes[$index_name]['unique'] = (bool) $row['is_unique'];
+				$indexes[$index_name]['primary'] = (bool) $row['is_primary_key'];
+				$indexes[$index_name]['columns'] = [];
+			}
+			$indexes[$index_name]['columns'][] = $row['column_name'];
+		}
+
+		return array_values($indexes);
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getForeignKeys($table)
+	{
+		$res = $this->driver->query("
+			SELECT f.name AS foreign_key,
+			OBJECT_NAME(f.parent_object_id) AS table_name,
+			COL_NAME(fc.parent_object_id,
+			fc.parent_column_id) AS column_name,
+			OBJECT_NAME (f.referenced_object_id) AS reference_table_name,
+			COL_NAME(fc.referenced_object_id,
+			fc.referenced_column_id) AS reference_column_name,
+			fc.*
+			FROM sys.foreign_keys AS f
+			INNER JOIN sys.foreign_key_columns AS fc
+			ON f.OBJECT_ID = fc.constraint_object_id
+			WHERE OBJECT_NAME(f.parent_object_id) = {$this->driver->escapeText($table)}
+		");
+
+		$keys = [];
+		while ($row = $res->fetch(true)) {
+			$key_name = $row['foreign_key'];
+
+			if (!isset($keys[$key_name])) {
+				$keys[$key_name]['name'] = $row['foreign_key']; // foreign key name
+				$keys[$key_name]['local'] = [$row['column_name']]; // local columns
+				$keys[$key_name]['table'] = $row['reference_table_name']; // referenced table
+				$keys[$key_name]['foreign'] = [$row['reference_column_name']]; // referenced columns
+				$keys[$key_name]['onDelete'] = false;
+				$keys[$key_name]['onUpdate'] = false;
+			} else {
+				$keys[$key_name]['local'][] = $row['column_name']; // local columns
+				$keys[$key_name]['foreign'][] = $row['reference_column_name']; // referenced columns
+			}
+		}
+		return array_values($keys);
+	}
+}

+ 502 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/MySqlDriver.php

@@ -0,0 +1,502 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi driver for MySQL database.
+ *
+ * Driver options:
+ *   - host => the MySQL server host name
+ *   - port (int) => the port number to attempt to connect to the MySQL server
+ *   - socket => the socket or named pipe
+ *   - username (or user)
+ *   - password (or pass)
+ *   - database => the database name to select
+ *   - flags (int) => driver specific constants (MYSQL_CLIENT_*)
+ *   - charset => character encoding to set (default is utf8)
+ *   - persistent (bool) => try to find a persistent link?
+ *   - unbuffered (bool) => sends query without fetching and buffering the result rows automatically?
+ *   - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
+ *   - resource (resource) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class MySqlDriver implements Dibi\Driver, Dibi\ResultDriver
+{
+	use Dibi\Strict;
+
+	const ERROR_ACCESS_DENIED = 1045;
+	const ERROR_DUPLICATE_ENTRY = 1062;
+	const ERROR_DATA_TRUNCATED = 1265;
+
+	/** @var resource|null */
+	private $connection;
+
+	/** @var resource|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var bool  Is buffered (seekable and countable)? */
+	private $buffered;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('mysql')) {
+			throw new Dibi\NotSupportedException("PHP extension 'mysql' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+
+		} else {
+			// default values
+			Dibi\Helpers::alias($config, 'flags', 'options');
+			$config += [
+				'charset' => 'utf8',
+				'timezone' => date('P'),
+				'username' => ini_get('mysql.default_user'),
+				'password' => ini_get('mysql.default_password'),
+			];
+			if (!isset($config['host'])) {
+				$host = ini_get('mysql.default_host');
+				if ($host) {
+					$config['host'] = $host;
+					$config['port'] = ini_get('mysql.default_port');
+				} else {
+					if (!isset($config['socket'])) {
+						$config['socket'] = ini_get('mysql.default_socket');
+					}
+					$config['host'] = null;
+				}
+			}
+
+			if (empty($config['socket'])) {
+				$host = $config['host'] . (empty($config['port']) ? '' : ':' . $config['port']);
+			} else {
+				$host = ':' . $config['socket'];
+			}
+
+			if (empty($config['persistent'])) {
+				$this->connection = @mysql_connect($host, $config['username'], $config['password'], true, $config['flags']); // intentionally @
+			} else {
+				$this->connection = @mysql_pconnect($host, $config['username'], $config['password'], $config['flags']); // intentionally @
+			}
+		}
+
+		if (!is_resource($this->connection)) {
+			throw new Dibi\DriverException(mysql_error(), mysql_errno());
+		}
+
+		if (isset($config['charset'])) {
+			if (!@mysql_set_charset($config['charset'], $this->connection)) { // intentionally @
+				$this->query("SET NAMES '$config[charset]'");
+			}
+		}
+
+		if (isset($config['database'])) {
+			if (!@mysql_select_db($config['database'], $this->connection)) { // intentionally @
+				throw new Dibi\DriverException(mysql_error($this->connection), mysql_errno($this->connection));
+			}
+		}
+
+		if (isset($config['sqlmode'])) {
+			$this->query("SET sql_mode='$config[sqlmode]'");
+		}
+
+		if (isset($config['timezone'])) {
+			$this->query("SET time_zone='$config[timezone]'");
+		}
+
+		$this->buffered = empty($config['unbuffered']);
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@mysql_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		if ($this->buffered) {
+			$res = @mysql_query($sql, $this->connection); // intentionally @
+		} else {
+			$res = @mysql_unbuffered_query($sql, $this->connection); // intentionally @
+		}
+
+		if ($code = mysql_errno($this->connection)) {
+			throw MySqliDriver::createException(mysql_error($this->connection), $code, $sql);
+
+		} elseif (is_resource($res)) {
+			return $this->createResultDriver($res);
+		}
+	}
+
+
+	/**
+	 * Retrieves information about the most recently executed query.
+	 * @return array
+	 */
+	public function getInfo()
+	{
+		$res = [];
+		preg_match_all('#(.+?): +(\d+) *#', mysql_info($this->connection), $matches, PREG_SET_ORDER);
+		if (preg_last_error()) {
+			throw new Dibi\PcreException;
+		}
+
+		foreach ($matches as $m) {
+			$res[$m[1]] = (int) $m[2];
+		}
+		return $res;
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return mysql_affected_rows($this->connection);
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		return mysql_insert_id($this->connection);
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return resource|null
+	 */
+	public function getResource()
+	{
+		return is_resource($this->connection) ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return new MySqlReflector($this);
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  resource
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver($resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		if (!is_resource($this->connection)) {
+			throw new Dibi\Exception('Lost connection to server.');
+		}
+		return "'" . mysql_real_escape_string($value, $this->connection) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		if (!is_resource($this->connection)) {
+			throw new Dibi\Exception('Lost connection to server.');
+		}
+		return "_binary'" . mysql_real_escape_string($value, $this->connection) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		// @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
+		return '`' . str_replace('`', '``', $value) . '`';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? 1 : 0;
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d'");
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d H:i:s.u'");
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit < 0 || $offset < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+
+		} elseif ($limit !== null || $offset) {
+			// see http://dev.mysql.com/doc/refman/5.0/en/select.html
+			$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : Dibi\Helpers::intVal($limit))
+				. ($offset ? ' OFFSET ' . Dibi\Helpers::intVal($offset) : '');
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && $this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		if (!$this->buffered) {
+			throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
+		}
+		return mysql_num_rows($this->resultSet);
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		return mysql_fetch_array($this->resultSet, $assoc ? MYSQL_ASSOC : MYSQL_NUM);
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 * @throws Dibi\Exception
+	 */
+	public function seek($row)
+	{
+		if (!$this->buffered) {
+			throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
+		}
+
+		return mysql_data_seek($this->resultSet, $row);
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		mysql_free_result($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$count = mysql_num_fields($this->resultSet);
+		$columns = [];
+		for ($i = 0; $i < $count; $i++) {
+			$row = (array) mysql_fetch_field($this->resultSet, $i);
+			$columns[] = [
+				'name' => $row['name'],
+				'table' => $row['table'],
+				'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
+				'nativetype' => strtoupper($row['type']),
+				'type' => $row['type'] === 'time' ? Dibi\Type::TIME_INTERVAL : null,
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return resource|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return is_resource($this->resultSet) ? $this->resultSet : null;
+	}
+}

+ 134 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/MySqlReflector.php

@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi reflector for MySQL databases.
+ * @internal
+ */
+class MySqlReflector implements Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\Driver */
+	private $driver;
+
+
+	public function __construct(Dibi\Driver $driver)
+	{
+		$this->driver = $driver;
+	}
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$res = $this->driver->query('SHOW FULL TABLES');
+		$tables = [];
+		while ($row = $res->fetch(false)) {
+			$tables[] = [
+				'name' => $row[0],
+				'view' => isset($row[1]) && $row[1] === 'VIEW',
+			];
+		}
+		return $tables;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$res = $this->driver->query("SHOW FULL COLUMNS FROM {$this->driver->escapeIdentifier($table)}");
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$type = explode('(', $row['Type']);
+			$columns[] = [
+				'name' => $row['Field'],
+				'table' => $table,
+				'nativetype' => strtoupper($type[0]),
+				'size' => isset($type[1]) ? (int) $type[1] : null,
+				'unsigned' => (bool) strstr($row['Type'], 'unsigned'),
+				'nullable' => $row['Null'] === 'YES',
+				'default' => $row['Default'],
+				'autoincrement' => $row['Extra'] === 'auto_increment',
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		$res = $this->driver->query("SHOW INDEX FROM {$this->driver->escapeIdentifier($table)}");
+		$indexes = [];
+		while ($row = $res->fetch(true)) {
+			$indexes[$row['Key_name']]['name'] = $row['Key_name'];
+			$indexes[$row['Key_name']]['unique'] = !$row['Non_unique'];
+			$indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
+			$indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
+		}
+		return array_values($indexes);
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function getForeignKeys($table)
+	{
+		$data = $this->driver->query("SELECT `ENGINE` FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = {$this->driver->escapeText($table)}")->fetch(true);
+		if ($data['ENGINE'] !== 'InnoDB') {
+			throw new Dibi\NotSupportedException("Foreign keys are not supported in {$data['ENGINE']} tables.");
+		}
+
+		$res = $this->driver->query("
+			SELECT rc.CONSTRAINT_NAME, rc.UPDATE_RULE, rc.DELETE_RULE, kcu.REFERENCED_TABLE_NAME,
+				GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) AS REFERENCED_COLUMNS,
+				GROUP_CONCAT(kcu.COLUMN_NAME ORDER BY kcu.ORDINAL_POSITION) AS COLUMNS
+			FROM information_schema.REFERENTIAL_CONSTRAINTS rc
+			INNER JOIN information_schema.KEY_COLUMN_USAGE kcu ON
+				kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
+				AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
+			WHERE rc.CONSTRAINT_SCHEMA = DATABASE()
+				AND rc.TABLE_NAME = {$this->driver->escapeText($table)}
+			GROUP BY rc.CONSTRAINT_NAME
+		");
+
+		$foreignKeys = [];
+		while ($row = $res->fetch(true)) {
+			$keyName = $row['CONSTRAINT_NAME'];
+
+			$foreignKeys[$keyName]['name'] = $keyName;
+			$foreignKeys[$keyName]['local'] = explode(',', $row['COLUMNS']);
+			$foreignKeys[$keyName]['table'] = $row['REFERENCED_TABLE_NAME'];
+			$foreignKeys[$keyName]['foreign'] = explode(',', $row['REFERENCED_COLUMNS']);
+			$foreignKeys[$keyName]['onDelete'] = $row['DELETE_RULE'];
+			$foreignKeys[$keyName]['onUpdate'] = $row['UPDATE_RULE'];
+		}
+		return array_values($foreignKeys);
+	}
+}

+ 516 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/MySqliDriver.php

@@ -0,0 +1,516 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi driver for MySQL database via improved extension.
+ *
+ * Driver options:
+ *   - host => the MySQL server host name
+ *   - port (int) => the port number to attempt to connect to the MySQL server
+ *   - socket => the socket or named pipe
+ *   - username (or user)
+ *   - password (or pass)
+ *   - database => the database name to select
+ *   - options (array) => array of driver specific constants (MYSQLI_*) and values {@see mysqli_options}
+ *   - flags (int) => driver specific constants (MYSQLI_CLIENT_*) {@see mysqli_real_connect}
+ *   - charset => character encoding to set (default is utf8)
+ *   - persistent (bool) => try to find a persistent link?
+ *   - unbuffered (bool) => sends query without fetching and buffering the result rows automatically?
+ *   - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
+ *   - resource (mysqli) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class MySqliDriver implements Dibi\Driver, Dibi\ResultDriver
+{
+	use Dibi\Strict;
+
+	const ERROR_ACCESS_DENIED = 1045;
+	const ERROR_DUPLICATE_ENTRY = 1062;
+	const ERROR_DATA_TRUNCATED = 1265;
+
+	/** @var \mysqli|null */
+	private $connection;
+
+	/** @var \mysqli_result|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var bool  Is buffered (seekable and countable)? */
+	private $buffered;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('mysqli')) {
+			throw new Dibi\NotSupportedException("PHP extension 'mysqli' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		mysqli_report(MYSQLI_REPORT_OFF);
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+
+		} else {
+			// default values
+			$config += [
+				'charset' => 'utf8',
+				'timezone' => date('P'),
+				'username' => ini_get('mysqli.default_user'),
+				'password' => ini_get('mysqli.default_pw'),
+				'socket' => (string) ini_get('mysqli.default_socket'),
+				'port' => null,
+			];
+			if (!isset($config['host'])) {
+				$host = ini_get('mysqli.default_host');
+				if ($host) {
+					$config['host'] = $host;
+					$config['port'] = ini_get('mysqli.default_port');
+				} else {
+					$config['host'] = null;
+					$config['port'] = null;
+				}
+			}
+
+			$foo = &$config['flags'];
+			$foo = &$config['database'];
+
+			$this->connection = mysqli_init();
+			if (isset($config['options'])) {
+				foreach ($config['options'] as $key => $value) {
+					mysqli_options($this->connection, $key, $value);
+				}
+			}
+			@mysqli_real_connect($this->connection, (empty($config['persistent']) ? '' : 'p:') . $config['host'], $config['username'], $config['password'], $config['database'], $config['port'], $config['socket'], $config['flags']); // intentionally @
+
+			if ($errno = mysqli_connect_errno()) {
+				throw new Dibi\DriverException(mysqli_connect_error(), $errno);
+			}
+		}
+
+		if (isset($config['charset'])) {
+			if (!@mysqli_set_charset($this->connection, $config['charset'])) {
+				$this->query("SET NAMES '$config[charset]'");
+			}
+		}
+
+		if (isset($config['sqlmode'])) {
+			$this->query("SET sql_mode='$config[sqlmode]'");
+		}
+
+		if (isset($config['timezone'])) {
+			$this->query("SET time_zone='$config[timezone]'");
+		}
+
+		$this->buffered = empty($config['unbuffered']);
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@mysqli_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		$res = @mysqli_query($this->connection, $sql, $this->buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); // intentionally @
+
+		if ($code = mysqli_errno($this->connection)) {
+			throw self::createException(mysqli_error($this->connection), $code, $sql);
+
+		} elseif (is_object($res)) {
+			return $this->createResultDriver($res);
+		}
+		return null;
+	}
+
+
+	/**
+	 * @return Dibi\DriverException
+	 */
+	public static function createException($message, $code, $sql)
+	{
+		if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
+			return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
+
+		} elseif (in_array($code, [1062, 1557, 1569, 1586], true)) {
+			return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
+
+		} elseif (in_array($code, [1048, 1121, 1138, 1171, 1252, 1263, 1566], true)) {
+			return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
+
+		} else {
+			return new Dibi\DriverException($message, $code, $sql);
+		}
+	}
+
+
+	/**
+	 * Retrieves information about the most recently executed query.
+	 * @return array
+	 */
+	public function getInfo()
+	{
+		$res = [];
+		preg_match_all('#(.+?): +(\d+) *#', mysqli_info($this->connection), $matches, PREG_SET_ORDER);
+		if (preg_last_error()) {
+			throw new Dibi\PcreException;
+		}
+
+		foreach ($matches as $m) {
+			$res[$m[1]] = (int) $m[2];
+		}
+		return $res;
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return mysqli_affected_rows($this->connection) === -1 ? false : mysqli_affected_rows($this->connection);
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		return mysqli_insert_id($this->connection);
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return \mysqli
+	 */
+	public function getResource()
+	{
+		return @$this->connection->thread_id ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return new MySqlReflector($this);
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver(\mysqli_result $resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		return "'" . mysqli_real_escape_string($this->connection, $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		return "_binary'" . mysqli_real_escape_string($this->connection, $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		return '`' . str_replace('`', '``', $value) . '`';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? '1' : '0';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d'");
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d H:i:s.u'");
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit < 0 || $offset < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+
+		} elseif ($limit !== null || $offset) {
+			// see http://dev.mysql.com/doc/refman/5.0/en/select.html
+			$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : Dibi\Helpers::intVal($limit))
+				. ($offset ? ' OFFSET ' . Dibi\Helpers::intVal($offset) : '');
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && @$this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		if (!$this->buffered) {
+			throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
+		}
+		return mysqli_num_rows($this->resultSet);
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		return mysqli_fetch_array($this->resultSet, $assoc ? MYSQLI_ASSOC : MYSQLI_NUM);
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 * @throws Dibi\Exception
+	 */
+	public function seek($row)
+	{
+		if (!$this->buffered) {
+			throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
+		}
+		return mysqli_data_seek($this->resultSet, $row);
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		mysqli_free_result($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		static $types;
+		if ($types === null) {
+			$consts = get_defined_constants(true);
+			$types = [];
+			foreach (isset($consts['mysqli']) ? $consts['mysqli'] : [] as $key => $value) {
+				if (strncmp($key, 'MYSQLI_TYPE_', 12) === 0) {
+					$types[$value] = substr($key, 12);
+				}
+			}
+			$types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
+		}
+
+		$count = mysqli_num_fields($this->resultSet);
+		$columns = [];
+		for ($i = 0; $i < $count; $i++) {
+			$row = (array) mysqli_fetch_field_direct($this->resultSet, $i);
+			$columns[] = [
+				'name' => $row['name'],
+				'table' => $row['orgtable'],
+				'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
+				'nativetype' => isset($types[$row['type']]) ? $types[$row['type']] : $row['type'],
+				'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TIME_INTERVAL : null,
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return \mysqli_result|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return $this->resultSet;
+	}
+}

+ 519 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/OdbcDriver.php

@@ -0,0 +1,519 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi driver interacting with databases via ODBC connections.
+ *
+ * Driver options:
+ *   - dsn => driver specific DSN
+ *   - username (or user)
+ *   - password (or pass)
+ *   - persistent (bool) => try to find a persistent link?
+ *   - resource (resource) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class OdbcDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	/** @var resource|null */
+	private $connection;
+
+	/** @var resource|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var int|false  Affected rows */
+	private $affectedRows = false;
+
+	/** @var int  Cursor */
+	private $row = 0;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('odbc')) {
+			throw new Dibi\NotSupportedException("PHP extension 'odbc' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+		} else {
+			// default values
+			$config += [
+				'username' => ini_get('odbc.default_user'),
+				'password' => ini_get('odbc.default_pw'),
+				'dsn' => ini_get('odbc.default_db'),
+			];
+
+			if (empty($config['persistent'])) {
+				$this->connection = @odbc_connect($config['dsn'], $config['username'], $config['password']); // intentionally @
+			} else {
+				$this->connection = @odbc_pconnect($config['dsn'], $config['username'], $config['password']); // intentionally @
+			}
+		}
+
+		if (!is_resource($this->connection)) {
+			throw new Dibi\DriverException(odbc_errormsg() . ' ' . odbc_error());
+		}
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@odbc_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		$this->affectedRows = false;
+		$res = @odbc_exec($this->connection, $sql); // intentionally @
+
+		if ($res === false) {
+			throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection), 0, $sql);
+
+		} elseif (is_resource($res)) {
+			$this->affectedRows = odbc_num_rows($res);
+			return odbc_num_fields($res) ? $this->createResultDriver($res) : null;
+		}
+		return null;
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return $this->affectedRows;
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		throw new Dibi\NotSupportedException('ODBC does not support autoincrementing.');
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		if (!odbc_autocommit($this->connection, false)) {
+			throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
+		}
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		if (!odbc_commit($this->connection)) {
+			throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
+		}
+		odbc_autocommit($this->connection, true);
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		if (!odbc_rollback($this->connection)) {
+			throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
+		}
+		odbc_autocommit($this->connection, true);
+	}
+
+
+	/**
+	 * Is in transaction?
+	 * @return bool
+	 */
+	public function inTransaction()
+	{
+		return !odbc_autocommit($this->connection);
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return resource|null
+	 */
+	public function getResource()
+	{
+		return is_resource($this->connection) ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return $this;
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  resource
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver($resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? '1' : '0';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format('#m/d/Y#');
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format('#m/d/Y H:i:s.u#');
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($offset) {
+			throw new Dibi\NotSupportedException('Offset is not supported by this database.');
+
+		} elseif ($limit < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+
+		} elseif ($limit !== null) {
+			$sql = 'SELECT TOP ' . Dibi\Helpers::intVal($limit) . ' * FROM (' . $sql . ') t';
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && $this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		// will return -1 with many drivers :-(
+		return odbc_num_rows($this->resultSet);
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		if ($assoc) {
+			return odbc_fetch_array($this->resultSet, ++$this->row);
+		} else {
+			$set = $this->resultSet;
+			if (!odbc_fetch_row($set, ++$this->row)) {
+				return false;
+			}
+			$count = odbc_num_fields($set);
+			$cols = [];
+			for ($i = 1; $i <= $count; $i++) {
+				$cols[] = odbc_result($set, $i);
+			}
+			return $cols;
+		}
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 */
+	public function seek($row)
+	{
+		$this->row = $row;
+		return true;
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		odbc_free_result($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$count = odbc_num_fields($this->resultSet);
+		$columns = [];
+		for ($i = 1; $i <= $count; $i++) {
+			$columns[] = [
+				'name' => odbc_field_name($this->resultSet, $i),
+				'table' => null,
+				'fullname' => odbc_field_name($this->resultSet, $i),
+				'nativetype' => odbc_field_type($this->resultSet, $i),
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return resource|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return is_resource($this->resultSet) ? $this->resultSet : null;
+	}
+
+
+	/********************* Dibi\Reflector ****************d*g**/
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$res = odbc_tables($this->connection);
+		$tables = [];
+		while ($row = odbc_fetch_array($res)) {
+			if ($row['TABLE_TYPE'] === 'TABLE' || $row['TABLE_TYPE'] === 'VIEW') {
+				$tables[] = [
+					'name' => $row['TABLE_NAME'],
+					'view' => $row['TABLE_TYPE'] === 'VIEW',
+				];
+			}
+		}
+		odbc_free_result($res);
+		return $tables;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$res = odbc_columns($this->connection);
+		$columns = [];
+		while ($row = odbc_fetch_array($res)) {
+			if ($row['TABLE_NAME'] === $table) {
+				$columns[] = [
+					'name' => $row['COLUMN_NAME'],
+					'table' => $table,
+					'nativetype' => $row['TYPE_NAME'],
+					'size' => $row['COLUMN_SIZE'],
+					'nullable' => (bool) $row['NULLABLE'],
+					'default' => $row['COLUMN_DEF'],
+				];
+			}
+		}
+		odbc_free_result($res);
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		throw new Dibi\NotImplementedException;
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getForeignKeys($table)
+	{
+		throw new Dibi\NotImplementedException;
+	}
+}

+ 540 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/OracleDriver.php

@@ -0,0 +1,540 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi driver for Oracle database.
+ *
+ * Driver options:
+ *   - database => the name of the local Oracle instance or the name of the entry in tnsnames.ora
+ *   - username (or user)
+ *   - password (or pass)
+ *   - charset => character encoding to set
+ *   - schema => alters session schema
+ *   - nativeDate => use native date format (defaults to false)
+ *   - resource (resource) => existing connection resource
+ *   - persistent => Creates persistent connections with oci_pconnect instead of oci_new_connect
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class OracleDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	/** @var resource|null */
+	private $connection;
+
+	/** @var resource|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var bool */
+	private $autocommit = true;
+
+	/** @var string  Date and datetime format */
+	private $fmtDate;
+	private $fmtDateTime;
+
+	/** @var int|false Number of affected rows */
+	private $affectedRows = false;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('oci8')) {
+			throw new Dibi\NotSupportedException("PHP extension 'oci8' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		$foo = &$config['charset'];
+
+		if (isset($config['formatDate']) || isset($config['formatDateTime'])) {
+			trigger_error('OracleDriver: options formatDate and formatDateTime are deprecated.', E_USER_DEPRECATED);
+		}
+		if (empty($config['nativeDate'])) {
+			$this->fmtDate = isset($config['formatDate']) ? $config['formatDate'] : 'U';
+			$this->fmtDateTime = isset($config['formatDateTime']) ? $config['formatDateTime'] : 'U';
+		}
+
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+		} elseif (empty($config['persistent'])) {
+			$this->connection = @oci_new_connect($config['username'], $config['password'], $config['database'], $config['charset']); // intentionally @
+		} else {
+			$this->connection = @oci_pconnect($config['username'], $config['password'], $config['database'], $config['charset']); // intentionally @
+		}
+
+		if (!$this->connection) {
+			$err = oci_error();
+			throw new Dibi\DriverException($err['message'], $err['code']);
+		}
+
+		if (isset($config['schema'])) {
+			$this->query('ALTER SESSION SET CURRENT_SCHEMA=' . $config['schema']);
+		}
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@oci_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		$this->affectedRows = false;
+		$res = oci_parse($this->connection, $sql);
+		if ($res) {
+			@oci_execute($res, $this->autocommit ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT);
+			$err = oci_error($res);
+			if ($err) {
+				throw self::createException($err['message'], $err['code'], $sql);
+
+			} elseif (is_resource($res)) {
+				$this->affectedRows = oci_num_rows($res);
+				return oci_num_fields($res) ? $this->createResultDriver($res) : null;
+			}
+		} else {
+			$err = oci_error($this->connection);
+			throw new Dibi\DriverException($err['message'], $err['code'], $sql);
+		}
+		return null;
+	}
+
+
+	/**
+	 * @return Dibi\DriverException
+	 */
+	public static function createException($message, $code, $sql)
+	{
+		if (in_array($code, [1, 2299, 38911], true)) {
+			return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
+
+		} elseif (in_array($code, [1400], true)) {
+			return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
+
+		} elseif (in_array($code, [2266, 2291, 2292], true)) {
+			return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
+
+		} else {
+			return new Dibi\DriverException($message, $code, $sql);
+		}
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return $this->affectedRows;
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		$row = $this->query("SELECT $sequence.CURRVAL AS ID FROM DUAL")->fetch(true);
+		return isset($row['ID']) ? Dibi\Helpers::intVal($row['ID']) : false;
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 */
+	public function begin($savepoint = null)
+	{
+		$this->autocommit = false;
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		if (!oci_commit($this->connection)) {
+			$err = oci_error($this->connection);
+			throw new Dibi\DriverException($err['message'], $err['code']);
+		}
+		$this->autocommit = true;
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		if (!oci_rollback($this->connection)) {
+			$err = oci_error($this->connection);
+			throw new Dibi\DriverException($err['message'], $err['code']);
+		}
+		$this->autocommit = true;
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return resource|null
+	 */
+	public function getResource()
+	{
+		return is_resource($this->connection) ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return $this;
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  resource
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver($resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'"; // TODO: not tested
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'"; // TODO: not tested
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		// @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
+		return '"' . str_replace('"', '""', $value) . '"';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? '1' : '0';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $this->fmtDate
+			? $value->format($this->fmtDate)
+			: "to_date('" . $value->format('Y-m-d') . "', 'YYYY-mm-dd')";
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $this->fmtDateTime
+			? $value->format($this->fmtDateTime)
+			: "to_date('" . $value->format('Y-m-d G:i:s') . "', 'YYYY-mm-dd hh24:mi:ss')";
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
+		$value = str_replace("'", "''", $value);
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit < 0 || $offset < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+
+		} elseif ($offset) {
+			// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
+			$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
+				. ($limit !== null ? 'WHERE ROWNUM <= ' . ((int) $offset + (int) $limit) : '')
+				. ') WHERE "__rnum" > ' . $offset;
+
+		} elseif ($limit !== null) {
+			$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . Dibi\Helpers::intVal($limit);
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && $this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		return oci_fetch_array($this->resultSet, ($assoc ? OCI_ASSOC : OCI_NUM) | OCI_RETURN_NULLS);
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 */
+	public function seek($row)
+	{
+		throw new Dibi\NotImplementedException;
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		oci_free_statement($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$count = oci_num_fields($this->resultSet);
+		$columns = [];
+		for ($i = 1; $i <= $count; $i++) {
+			$type = oci_field_type($this->resultSet, $i);
+			$columns[] = [
+				'name' => oci_field_name($this->resultSet, $i),
+				'table' => null,
+				'fullname' => oci_field_name($this->resultSet, $i),
+				'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return resource|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return is_resource($this->resultSet) ? $this->resultSet : null;
+	}
+
+
+	/********************* Dibi\Reflector ****************d*g**/
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$res = $this->query('SELECT * FROM cat');
+		$tables = [];
+		while ($row = $res->fetch(false)) {
+			if ($row[1] === 'TABLE' || $row[1] === 'VIEW') {
+				$tables[] = [
+					'name' => $row[0],
+					'view' => $row[1] === 'VIEW',
+				];
+			}
+		}
+		return $tables;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$res = $this->query('SELECT * FROM "ALL_TAB_COLUMNS" WHERE "TABLE_NAME" = ' . $this->escapeText($table));
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$columns[] = [
+				'table' => $row['TABLE_NAME'],
+				'name' => $row['COLUMN_NAME'],
+				'nativetype' => $row['DATA_TYPE'],
+				'size' => isset($row['DATA_LENGTH']) ? $row['DATA_LENGTH'] : null,
+				'nullable' => $row['NULLABLE'] === 'Y',
+				'default' => $row['DATA_DEFAULT'],
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		throw new Dibi\NotImplementedException;
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getForeignKeys($table)
+	{
+		throw new Dibi\NotImplementedException;
+	}
+}

+ 578 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/PdoDriver.php

@@ -0,0 +1,578 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+use PDO;
+
+
+/**
+ * The dibi driver for PDO.
+ *
+ * Driver options:
+ *   - dsn => driver specific DSN
+ *   - username (or user)
+ *   - password (or pass)
+ *   - options (array) => driver specific options {@see PDO::__construct}
+ *   - resource (PDO) => existing connection
+ *   - version
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class PdoDriver implements Dibi\Driver, Dibi\ResultDriver
+{
+	use Dibi\Strict;
+
+	/** @var PDO  Connection resource */
+	private $connection;
+
+	/** @var \PDOStatement|null  Resultset resource */
+	private $resultSet;
+
+	/** @var int|false  Affected rows */
+	private $affectedRows = false;
+
+	/** @var string */
+	private $driverName;
+
+	/** @var string */
+	private $serverVersion = '';
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('pdo')) {
+			throw new Dibi\NotSupportedException("PHP extension 'pdo' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		$foo = &$config['dsn'];
+		$foo = &$config['options'];
+		Dibi\Helpers::alias($config, 'resource', 'pdo');
+
+		if ($config['resource'] instanceof PDO) {
+			$this->connection = $config['resource'];
+			unset($config['resource'], $config['pdo']);
+		} else {
+			try {
+				$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
+			} catch (\PDOException $e) {
+				if ($e->getMessage() === 'could not find driver') {
+					throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
+				}
+				throw new Dibi\DriverException($e->getMessage(), $e->getCode());
+			}
+		}
+
+		$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
+		$this->serverVersion = isset($config['version'])
+			? $config['version']
+			: @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); // @ - may be not supported
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		$this->connection = null;
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		// must detect if SQL returns result set or num of affected rows
+		$cmd = strtoupper(substr(ltrim($sql), 0, 6));
+		static $list = ['UPDATE' => 1, 'DELETE' => 1, 'INSERT' => 1, 'REPLAC' => 1];
+		$this->affectedRows = false;
+
+		if (isset($list[$cmd])) {
+			$this->affectedRows = $this->connection->exec($sql);
+			if ($this->affectedRows !== false) {
+				return null;
+			}
+		} else {
+			$res = $this->connection->query($sql);
+			if ($res) {
+				return $this->createResultDriver($res);
+			}
+		}
+
+		list($sqlState, $code, $message) = $this->connection->errorInfo();
+		$message = "SQLSTATE[$sqlState]: $message";
+		switch ($this->driverName) {
+			case 'mysql':
+				throw MySqliDriver::createException($message, $code, $sql);
+
+			case 'oci':
+				throw OracleDriver::createException($message, $code, $sql);
+
+			case 'pgsql':
+				throw PostgreDriver::createException($message, $sqlState, $sql);
+
+			case 'sqlite':
+				throw Sqlite3Driver::createException($message, $code, $sql);
+
+			default:
+				throw new Dibi\DriverException($message, $code, $sql);
+		}
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return $this->affectedRows;
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		return $this->connection->lastInsertId();
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		if (!$this->connection->beginTransaction()) {
+			$err = $this->connection->errorInfo();
+			throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
+		}
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		if (!$this->connection->commit()) {
+			$err = $this->connection->errorInfo();
+			throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
+		}
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		if (!$this->connection->rollBack()) {
+			$err = $this->connection->errorInfo();
+			throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
+		}
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return PDO
+	 */
+	public function getResource()
+	{
+		return $this->connection;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		switch ($this->driverName) {
+			case 'mysql':
+				return new MySqlReflector($this);
+
+			case 'sqlite':
+				return new SqliteReflector($this);
+
+			default:
+				throw new Dibi\NotSupportedException;
+		}
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  \PDOStatement
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver(\PDOStatement $resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		if ($this->driverName === 'odbc') {
+			return "'" . str_replace("'", "''", $value) . "'";
+		} else {
+			return $this->connection->quote($value, PDO::PARAM_STR);
+		}
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		if ($this->driverName === 'odbc') {
+			return "'" . str_replace("'", "''", $value) . "'";
+		} else {
+			return $this->connection->quote($value, PDO::PARAM_LOB);
+		}
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		switch ($this->driverName) {
+			case 'mysql':
+				return '`' . str_replace('`', '``', $value) . '`';
+
+			case 'oci':
+			case 'pgsql':
+				return '"' . str_replace('"', '""', $value) . '"';
+
+			case 'sqlite':
+				return '[' . strtr($value, '[]', '  ') . ']';
+
+			case 'odbc':
+			case 'mssql':
+				return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
+
+			case 'dblib':
+			case 'sqlsrv':
+				return '[' . str_replace(']', ']]', $value) . ']';
+
+			default:
+				return $value;
+		}
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		if ($this->driverName === 'pgsql') {
+			return $value ? 'TRUE' : 'FALSE';
+		} else {
+			return $value ? '1' : '0';
+		}
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format($this->driverName === 'odbc' ? '#m/d/Y#' : "'Y-m-d'");
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format($this->driverName === 'odbc' ? '#m/d/Y H:i:s.u#' : "'Y-m-d H:i:s.u'");
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		switch ($this->driverName) {
+			case 'mysql':
+				$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
+				return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+
+			case 'oci':
+				$value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\\%_");
+				$value = str_replace("'", "''", $value);
+				return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+
+			case 'pgsql':
+				$bs = substr($this->connection->quote('\\', PDO::PARAM_STR), 1, -1); // standard_conforming_strings = on/off
+				$value = substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1);
+				$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
+				return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+
+			case 'sqlite':
+				$value = addcslashes(substr($this->connection->quote($value, PDO::PARAM_STR), 1, -1), '%_\\');
+				return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
+
+			case 'odbc':
+			case 'mssql':
+			case 'dblib':
+			case 'sqlsrv':
+				$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
+				return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+
+			default:
+				throw new Dibi\NotImplementedException;
+		}
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit < 0 || $offset < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+		}
+
+		switch ($this->driverName) {
+			case 'mysql':
+				if ($limit !== null || $offset) {
+					// see http://dev.mysql.com/doc/refman/5.0/en/select.html
+					$sql .= ' LIMIT ' . ($limit === null ? '18446744073709551615' : Dibi\Helpers::intVal($limit))
+						. ($offset ? ' OFFSET ' . Dibi\Helpers::intVal($offset) : '');
+				}
+				break;
+
+			case 'pgsql':
+				if ($limit !== null) {
+					$sql .= ' LIMIT ' . Dibi\Helpers::intVal($limit);
+				}
+				if ($offset) {
+					$sql .= ' OFFSET ' . Dibi\Helpers::intVal($offset);
+				}
+				break;
+
+			case 'sqlite':
+				if ($limit !== null || $offset) {
+					$sql .= ' LIMIT ' . ($limit === null ? '-1' : Dibi\Helpers::intVal($limit))
+						. ($offset ? ' OFFSET ' . Dibi\Helpers::intVal($offset) : '');
+				}
+				break;
+
+			case 'oci':
+				if ($offset) {
+					// see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
+					$sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
+						. ($limit !== null ? 'WHERE ROWNUM <= ' . ((int) $offset + (int) $limit) : '')
+						. ') WHERE "__rnum" > ' . $offset;
+
+				} elseif ($limit !== null) {
+					$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . Dibi\Helpers::intVal($limit);
+				}
+				break;
+
+			case 'mssql':
+			case 'sqlsrv':
+			case 'dblib':
+				if (version_compare($this->serverVersion, '11.0') >= 0) { // 11 == SQL Server 2012
+					// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
+					if ($limit !== null) {
+						$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
+					} elseif ($offset) {
+						$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
+					}
+					break;
+				}
+				// break omitted
+			case 'odbc':
+				if ($offset) {
+					throw new Dibi\NotSupportedException('Offset is not supported by this database.');
+
+				} elseif ($limit !== null) {
+					$sql = 'SELECT TOP ' . Dibi\Helpers::intVal($limit) . ' * FROM (' . $sql . ') t';
+					break;
+				}
+				// break omitted
+			default:
+				throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		return $this->resultSet->rowCount();
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		return $this->resultSet->fetch($assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM);
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 */
+	public function seek($row)
+	{
+		throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 * @throws Dibi\Exception
+	 */
+	public function getResultColumns()
+	{
+		$count = $this->resultSet->columnCount();
+		$columns = [];
+		for ($i = 0; $i < $count; $i++) {
+			$row = @$this->resultSet->getColumnMeta($i); // intentionally @
+			if ($row === false) {
+				throw new Dibi\NotSupportedException('Driver does not support meta data.');
+			}
+			$row = $row + [
+				'table' => null,
+				'native_type' => 'VAR_STRING',
+			];
+
+			$columns[] = [
+				'name' => $row['name'],
+				'table' => $row['table'],
+				'nativetype' => $row['native_type'],
+				'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TIME_INTERVAL : null,
+				'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return \PDOStatement|null
+	 */
+	public function getResultResource()
+	{
+		return $this->resultSet;
+	}
+}

+ 743 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/PostgreDriver.php

@@ -0,0 +1,743 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi driver for PostgreSQL database.
+ *
+ * Driver options:
+ *   - host, hostaddr, port, dbname, user, password, connect_timeout, options, sslmode, service => see PostgreSQL API
+ *   - string => or use connection string
+ *   - schema => the schema search path
+ *   - charset => character encoding to set (default is utf8)
+ *   - persistent (bool) => try to find a persistent link?
+ *   - resource (resource) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class PostgreDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	/** @var resource|null */
+	private $connection;
+
+	/** @var resource|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var int|false  Affected rows */
+	private $affectedRows = false;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('pgsql')) {
+			throw new Dibi\NotSupportedException("PHP extension 'pgsql' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		$error = null;
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+
+		} else {
+			$config += [
+				'charset' => 'utf8',
+			];
+			if (isset($config['string'])) {
+				$string = $config['string'];
+			} else {
+				$string = '';
+				Dibi\Helpers::alias($config, 'user', 'username');
+				Dibi\Helpers::alias($config, 'dbname', 'database');
+				foreach (['host', 'hostaddr', 'port', 'dbname', 'user', 'password', 'connect_timeout', 'options', 'sslmode', 'service'] as $key) {
+					if (isset($config[$key])) {
+						$string .= $key . '=' . $config[$key] . ' ';
+					}
+				}
+			}
+
+			set_error_handler(function ($severity, $message) use (&$error) {
+				$error = $message;
+			});
+			if (empty($config['persistent'])) {
+				$this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW);
+			} else {
+				$this->connection = pg_pconnect($string, PGSQL_CONNECT_FORCE_NEW);
+			}
+			restore_error_handler();
+		}
+
+		if (!is_resource($this->connection)) {
+			throw new Dibi\DriverException($error ?: 'Connecting error.');
+		}
+
+		pg_set_error_verbosity($this->connection, PGSQL_ERRORS_VERBOSE);
+
+		if (isset($config['charset']) && pg_set_client_encoding($this->connection, $config['charset'])) {
+			throw self::createException(pg_last_error($this->connection));
+		}
+
+		if (isset($config['schema'])) {
+			$this->query('SET search_path TO "' . implode('", "', (array) $config['schema']) . '"');
+		}
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@pg_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Pings database.
+	 * @return bool
+	 */
+	public function ping()
+	{
+		return pg_ping($this->connection);
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		$this->affectedRows = false;
+		$res = @pg_query($this->connection, $sql); // intentionally @
+
+		if ($res === false) {
+			throw self::createException(pg_last_error($this->connection), null, $sql);
+
+		} elseif (is_resource($res)) {
+			$this->affectedRows = pg_affected_rows($res);
+			if (pg_num_fields($res)) {
+				return $this->createResultDriver($res);
+			}
+		}
+		return null;
+	}
+
+
+	/**
+	 * @return Dibi\DriverException
+	 */
+	public static function createException($message, $code = null, $sql = null)
+	{
+		if ($code === null && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) {
+			$code = $m[1];
+			$message = substr($message, strlen($m[0]));
+		}
+
+		if ($code === '0A000' && strpos($message, 'truncate') !== false) {
+			return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
+
+		} elseif ($code === '23502') {
+			return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
+
+		} elseif ($code === '23503') {
+			return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
+
+		} elseif ($code === '23505') {
+			return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
+
+		} else {
+			return new Dibi\DriverException($message, $code, $sql);
+		}
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return $this->affectedRows;
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		if ($sequence === null) {
+			// PostgreSQL 8.1 is needed
+			$res = $this->query('SELECT LASTVAL()');
+		} else {
+			$res = $this->query("SELECT CURRVAL('$sequence')");
+		}
+
+		if (!$res) {
+			return false;
+		}
+
+		$row = $res->fetch(false);
+		return is_array($row) ? $row[0] : false;
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
+	}
+
+
+	/**
+	 * Is in transaction?
+	 * @return bool
+	 */
+	public function inTransaction()
+	{
+		return !in_array(pg_transaction_status($this->connection), [PGSQL_TRANSACTION_UNKNOWN, PGSQL_TRANSACTION_IDLE], true);
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return resource|null
+	 */
+	public function getResource()
+	{
+		return is_resource($this->connection) ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return $this;
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  resource
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver($resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		if (!is_resource($this->connection)) {
+			throw new Dibi\Exception('Lost connection to server.');
+		}
+		return "'" . pg_escape_string($this->connection, $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		if (!is_resource($this->connection)) {
+			throw new Dibi\Exception('Lost connection to server.');
+		}
+		return "'" . pg_escape_bytea($this->connection, $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		// @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
+		return '"' . str_replace('"', '""', $value) . '"';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? 'TRUE' : 'FALSE';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d'");
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d H:i:s.u'");
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$bs = pg_escape_string($this->connection, '\\'); // standard_conforming_strings = on/off
+		$value = pg_escape_string($this->connection, $value);
+		$value = strtr($value, ['%' => $bs . '%', '_' => $bs . '_', '\\' => '\\\\']);
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return pg_unescape_bytea($value);
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit < 0 || $offset < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+		}
+		if ($limit !== null) {
+			$sql .= ' LIMIT ' . Dibi\Helpers::intVal($limit);
+		}
+		if ($offset) {
+			$sql .= ' OFFSET ' . Dibi\Helpers::intVal($offset);
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && $this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		return pg_num_rows($this->resultSet);
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		return pg_fetch_array($this->resultSet, null, $assoc ? PGSQL_ASSOC : PGSQL_NUM);
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 */
+	public function seek($row)
+	{
+		return pg_result_seek($this->resultSet, $row);
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		pg_free_result($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$count = pg_num_fields($this->resultSet);
+		$columns = [];
+		for ($i = 0; $i < $count; $i++) {
+			$row = [
+				'name' => pg_field_name($this->resultSet, $i),
+				'table' => pg_field_table($this->resultSet, $i),
+				'nativetype' => pg_field_type($this->resultSet, $i),
+			];
+			$row['fullname'] = $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'];
+			$columns[] = $row;
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return resource|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return is_resource($this->resultSet) ? $this->resultSet : null;
+	}
+
+
+	/********************* Dibi\Reflector ****************d*g**/
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$version = pg_parameter_status($this->getResource(), 'server_version');
+		if ($version < 7.4) {
+			throw new Dibi\DriverException('Reflection requires PostgreSQL 7.4 and newer.');
+		}
+
+		$query = "
+			SELECT
+				table_name AS name,
+				CASE table_type
+					WHEN 'VIEW' THEN 1
+					ELSE 0
+				END AS view
+			FROM
+				information_schema.tables
+			WHERE
+				table_schema = ANY (current_schemas(false))";
+
+		if ($version >= 9.3) {
+			$query .= '
+				UNION ALL
+				SELECT
+					matviewname, 1
+				FROM
+					pg_matviews
+				WHERE
+					schemaname = ANY (current_schemas(false))';
+		}
+
+		$res = $this->query($query);
+		$tables = pg_fetch_all($res->resultSet);
+		return $tables ? $tables : [];
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$_table = $this->escapeText($this->escapeIdentifier($table));
+		$res = $this->query("
+			SELECT indkey
+			FROM pg_class
+			LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid AND pg_index.indisprimary
+			WHERE pg_class.oid = $_table::regclass
+		");
+		$primary = (int) pg_fetch_object($res->resultSet)->indkey;
+
+		$res = $this->query("
+			SELECT *
+			FROM information_schema.columns c
+			JOIN pg_class ON pg_class.relname = c.table_name
+			JOIN pg_namespace nsp ON nsp.oid = pg_class.relnamespace AND nsp.nspname = c.table_schema
+			WHERE pg_class.oid = $_table::regclass
+			ORDER BY c.ordinal_position
+		");
+
+		if (!$res->getRowCount()) {
+			$res = $this->query("
+				SELECT
+					a.attname AS column_name,
+					pg_type.typname AS udt_name,
+					a.attlen AS numeric_precision,
+					a.atttypmod-4 AS character_maximum_length,
+					NOT a.attnotnull AS is_nullable,
+					a.attnum AS ordinal_position,
+					adef.adsrc AS column_default
+				FROM
+					pg_attribute a
+					JOIN pg_type ON a.atttypid = pg_type.oid
+					JOIN pg_class cls ON a.attrelid = cls.oid
+					LEFT JOIN pg_attrdef adef ON adef.adnum = a.attnum AND adef.adrelid = a.attrelid
+				WHERE
+					cls.relkind IN ('r', 'v', 'mv')
+					AND a.attrelid = $_table::regclass
+					AND a.attnum > 0
+					AND NOT a.attisdropped
+				ORDER BY ordinal_position
+			");
+		}
+
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$size = (int) max($row['character_maximum_length'], $row['numeric_precision']);
+			$columns[] = [
+				'name' => $row['column_name'],
+				'table' => $table,
+				'nativetype' => strtoupper($row['udt_name']),
+				'size' => $size > 0 ? $size : null,
+				'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't',
+				'default' => $row['column_default'],
+				'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'], 0, 7) === 'nextval',
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		$_table = $this->escapeText($this->escapeIdentifier($table));
+		$res = $this->query("
+			SELECT
+				a.attnum AS ordinal_position,
+				a.attname AS column_name
+			FROM
+				pg_attribute a
+				JOIN pg_class cls ON a.attrelid = cls.oid
+			WHERE
+				a.attrelid = $_table::regclass
+				AND a.attnum > 0
+				AND NOT a.attisdropped
+			ORDER BY ordinal_position
+		");
+
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$columns[$row['ordinal_position']] = $row['column_name'];
+		}
+
+		$res = $this->query("
+			SELECT pg_class2.relname, indisunique, indisprimary, indkey
+			FROM pg_class
+			LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid
+			INNER JOIN pg_class as pg_class2 on pg_class2.oid = pg_index.indexrelid
+			WHERE pg_class.oid = $_table::regclass
+		");
+
+		$indexes = [];
+		while ($row = $res->fetch(true)) {
+			$indexes[$row['relname']]['name'] = $row['relname'];
+			$indexes[$row['relname']]['unique'] = $row['indisunique'] === 't';
+			$indexes[$row['relname']]['primary'] = $row['indisprimary'] === 't';
+			foreach (explode(' ', $row['indkey']) as $index) {
+				$indexes[$row['relname']]['columns'][] = $columns[$index];
+			}
+		}
+		return array_values($indexes);
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getForeignKeys($table)
+	{
+		$_table = $this->escapeText($this->escapeIdentifier($table));
+
+		$res = $this->query("
+			SELECT
+				c.conname AS name,
+				lt.attname AS local,
+				c.confrelid::regclass AS table,
+				ft.attname AS foreign,
+
+				CASE c.confupdtype
+					WHEN 'a' THEN 'NO ACTION'
+					WHEN 'r' THEN 'RESTRICT'
+					WHEN 'c' THEN 'CASCADE'
+					WHEN 'n' THEN 'SET NULL'
+					WHEN 'd' THEN 'SET DEFAULT'
+					ELSE 'UNKNOWN'
+				END AS \"onUpdate\",
+
+				CASE c.confdeltype
+					WHEN 'a' THEN 'NO ACTION'
+					WHEN 'r' THEN 'RESTRICT'
+					WHEN 'c' THEN 'CASCADE'
+					WHEN 'n' THEN 'SET NULL'
+					WHEN 'd' THEN 'SET DEFAULT'
+					ELSE 'UNKNOWN'
+				END AS \"onDelete\",
+
+				c.conkey,
+				lt.attnum AS lnum,
+				c.confkey,
+				ft.attnum AS fnum
+			FROM
+				pg_constraint c
+				JOIN pg_attribute lt ON c.conrelid = lt.attrelid AND lt.attnum = ANY (c.conkey)
+				JOIN pg_attribute ft ON c.confrelid = ft.attrelid AND ft.attnum = ANY (c.confkey)
+			WHERE
+				c.contype = 'f'
+				AND
+				c.conrelid = $_table::regclass
+		");
+
+		$fKeys = $references = [];
+		while ($row = $res->fetch(true)) {
+			if (!isset($fKeys[$row['name']])) {
+				$fKeys[$row['name']] = [
+					'name' => $row['name'],
+					'table' => $row['table'],
+					'local' => [],
+					'foreign' => [],
+					'onUpdate' => $row['onUpdate'],
+					'onDelete' => $row['onDelete'],
+				];
+
+				$l = explode(',', trim($row['conkey'], '{}'));
+				$f = explode(',', trim($row['confkey'], '{}'));
+
+				$references[$row['name']] = array_combine($l, $f);
+			}
+
+			if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) {
+				$fKeys[$row['name']]['local'][] = $row['local'];
+				$fKeys[$row['name']]['foreign'][] = $row['foreign'];
+			}
+		}
+
+		return $fKeys;
+	}
+}

+ 497 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/Sqlite3Driver.php

@@ -0,0 +1,497 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+use SQLite3;
+
+
+/**
+ * The dibi driver for SQLite3 database.
+ *
+ * Driver options:
+ *   - database (or file) => the filename of the SQLite3 database
+ *   - formatDate => how to format date in SQL (@see date)
+ *   - formatDateTime => how to format datetime in SQL (@see date)
+ *   - dbcharset => database character encoding (will be converted to 'charset')
+ *   - charset => character encoding to set (default is UTF-8)
+ *   - resource (SQLite3) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class Sqlite3Driver implements Dibi\Driver, Dibi\ResultDriver
+{
+	use Dibi\Strict;
+
+	/** @var SQLite3|null */
+	private $connection;
+
+	/** @var \SQLite3Result|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var string  Date and datetime format */
+	private $fmtDate;
+	private $fmtDateTime;
+
+	/** @var string  character encoding */
+	private $dbcharset;
+	private $charset;
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('sqlite3')) {
+			throw new Dibi\NotSupportedException("PHP extension 'sqlite3' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		Dibi\Helpers::alias($config, 'database', 'file');
+		$this->fmtDate = isset($config['formatDate']) ? $config['formatDate'] : 'U';
+		$this->fmtDateTime = isset($config['formatDateTime']) ? $config['formatDateTime'] : 'U';
+
+		if (isset($config['resource']) && $config['resource'] instanceof SQLite3) {
+			$this->connection = $config['resource'];
+		} else {
+			try {
+				$this->connection = new SQLite3($config['database']);
+			} catch (\Exception $e) {
+				throw new Dibi\DriverException($e->getMessage(), $e->getCode());
+			}
+		}
+
+		$this->dbcharset = empty($config['dbcharset']) ? 'UTF-8' : $config['dbcharset'];
+		$this->charset = empty($config['charset']) ? 'UTF-8' : $config['charset'];
+		if (strcasecmp($this->dbcharset, $this->charset) === 0) {
+			$this->dbcharset = $this->charset = null;
+		}
+
+		// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
+		$version = SQLite3::version();
+		if ($version['versionNumber'] >= '3006019') {
+			$this->query('PRAGMA foreign_keys = ON');
+		}
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		$this->connection->close();
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		if ($this->dbcharset !== null) {
+			$sql = iconv($this->charset, $this->dbcharset . '//IGNORE', $sql);
+		}
+
+		$res = @$this->connection->query($sql); // intentionally @
+		if ($code = $this->connection->lastErrorCode()) {
+			throw self::createException($this->connection->lastErrorMsg(), $code, $sql);
+
+		} elseif ($res instanceof \SQLite3Result && $res->numColumns()) {
+			return $this->createResultDriver($res);
+		}
+		return null;
+	}
+
+
+	/**
+	 * @return Dibi\DriverException
+	 */
+	public static function createException($message, $code, $sql)
+	{
+		if ($code !== 19) {
+			return new Dibi\DriverException($message, $code, $sql);
+
+		} elseif (strpos($message, 'must be unique') !== false
+			|| strpos($message, 'is not unique') !== false
+			|| strpos($message, 'UNIQUE constraint failed') !== false
+		) {
+			return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
+
+		} elseif (strpos($message, 'may not be NULL') !== false
+			|| strpos($message, 'NOT NULL constraint failed') !== false
+		) {
+			return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
+
+		} elseif (strpos($message, 'foreign key constraint failed') !== false
+			|| strpos($message, 'FOREIGN KEY constraint failed') !== false
+		) {
+			return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
+
+		} else {
+			return new Dibi\ConstraintViolationException($message, $code, $sql);
+		}
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return $this->connection->changes();
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		return $this->connection->lastInsertRowID();
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		$this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return SQLite3
+	 */
+	public function getResource()
+	{
+		return $this->connection;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return new SqliteReflector($this);
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  \SQLite3Result
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver(\SQLite3Result $resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		return "'" . $this->connection->escapeString($value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		return "X'" . bin2hex((string) $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		return '[' . strtr($value, '[]', '  ') . ']';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? '1' : '0';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format($this->fmtDate);
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format($this->fmtDateTime);
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$value = addcslashes($this->connection->escapeString($value), '%_\\');
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Dibi\Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit < 0 || $offset < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+
+		} elseif ($limit !== null || $offset) {
+			$sql .= ' LIMIT ' . ($limit === null ? '-1' : Dibi\Helpers::intVal($limit))
+				. ($offset ? ' OFFSET ' . Dibi\Helpers::intVal($offset) : '');
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->resultSet && @$this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function getRowCount()
+	{
+		throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		$row = $this->resultSet->fetchArray($assoc ? SQLITE3_ASSOC : SQLITE3_NUM);
+		$charset = $this->charset === null ? null : $this->charset . '//TRANSLIT';
+		if ($row && ($assoc || $charset)) {
+			$tmp = [];
+			foreach ($row as $k => $v) {
+				if ($charset !== null && is_string($v)) {
+					$v = iconv($this->dbcharset, $charset, $v);
+				}
+				$tmp[str_replace(['[', ']'], '', $k)] = $v;
+			}
+			return $tmp;
+		}
+		return $row;
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function seek($row)
+	{
+		throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		$this->resultSet->finalize();
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$count = $this->resultSet->numColumns();
+		$columns = [];
+		static $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null'];
+		for ($i = 0; $i < $count; $i++) {
+			$columns[] = [
+				'name' => $this->resultSet->columnName($i),
+				'table' => null,
+				'fullname' => $this->resultSet->columnName($i),
+				'nativetype' => $types[$this->resultSet->columnType($i)],
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return \SQLite3Result|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return $this->resultSet;
+	}
+
+
+	/********************* user defined functions ****************d*g**/
+
+
+	/**
+	 * Registers an user defined function for use in SQL statements.
+	 * @param  string  function name
+	 * @param  mixed   callback
+	 * @param  int     num of arguments
+	 * @return void
+	 */
+	public function registerFunction($name, callable $callback, $numArgs = -1)
+	{
+		$this->connection->createFunction($name, $callback, $numArgs);
+	}
+
+
+	/**
+	 * Registers an aggregating user defined function for use in SQL statements.
+	 * @param  string  function name
+	 * @param  mixed   callback called for each row of the result set
+	 * @param  mixed   callback called to aggregate the "stepped" data from each row
+	 * @param  int     num of arguments
+	 * @return void
+	 */
+	public function registerAggregateFunction($name, callable $rowCallback, callable $agrCallback, $numArgs = -1)
+	{
+		$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
+	}
+}

+ 153 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/SqliteReflector.php

@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi reflector for SQLite database.
+ * @internal
+ */
+class SqliteReflector implements Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\Driver */
+	private $driver;
+
+
+	public function __construct(Dibi\Driver $driver)
+	{
+		$this->driver = $driver;
+	}
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$res = $this->driver->query("
+			SELECT name, type = 'view' as view FROM sqlite_master WHERE type IN ('table', 'view')
+			UNION ALL
+			SELECT name, type = 'view' as view FROM sqlite_temp_master WHERE type IN ('table', 'view')
+			ORDER BY name
+		");
+		$tables = [];
+		while ($row = $res->fetch(true)) {
+			$tables[] = $row;
+		}
+		return $tables;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$res = $this->driver->query("PRAGMA table_info({$this->driver->escapeIdentifier($table)})");
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$column = $row['name'];
+			$type = explode('(', $row['type']);
+			$columns[] = [
+				'name' => $column,
+				'table' => $table,
+				'fullname' => "$table.$column",
+				'nativetype' => strtoupper($type[0]),
+				'size' => isset($type[1]) ? (int) $type[1] : null,
+				'nullable' => $row['notnull'] == '0',
+				'default' => $row['dflt_value'],
+				'autoincrement' => $row['pk'] && $type[0] === 'INTEGER',
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		$res = $this->driver->query("PRAGMA index_list({$this->driver->escapeIdentifier($table)})");
+		$indexes = [];
+		while ($row = $res->fetch(true)) {
+			$indexes[$row['name']]['name'] = $row['name'];
+			$indexes[$row['name']]['unique'] = (bool) $row['unique'];
+		}
+
+		foreach ($indexes as $index => $values) {
+			$res = $this->driver->query("PRAGMA index_info({$this->driver->escapeIdentifier($index)})");
+			while ($row = $res->fetch(true)) {
+				$indexes[$index]['columns'][$row['seqno']] = $row['name'];
+			}
+		}
+
+		$columns = $this->getColumns($table);
+		foreach ($indexes as $index => $values) {
+			$column = $indexes[$index]['columns'][0];
+			$primary = false;
+			foreach ($columns as $info) {
+				if ($column == $info['name']) {
+					$primary = $info['vendor']['pk'];
+					break;
+				}
+			}
+			$indexes[$index]['primary'] = (bool) $primary;
+		}
+		if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
+			foreach ($columns as $column) {
+				if ($column['vendor']['pk']) {
+					$indexes[] = [
+						'name' => 'ROWID',
+						'unique' => true,
+						'primary' => true,
+						'columns' => [$column['name']],
+					];
+					break;
+				}
+			}
+		}
+
+		return array_values($indexes);
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getForeignKeys($table)
+	{
+		$res = $this->driver->query("PRAGMA foreign_key_list({$this->driver->escapeIdentifier($table)})");
+		$keys = [];
+		while ($row = $res->fetch(true)) {
+			$keys[$row['id']]['name'] = $row['id']; // foreign key name
+			$keys[$row['id']]['local'][$row['seq']] = $row['from']; // local columns
+			$keys[$row['id']]['table'] = $row['table']; // referenced table
+			$keys[$row['id']]['foreign'][$row['seq']] = $row['to']; // referenced columns
+			$keys[$row['id']]['onDelete'] = $row['on_delete'];
+			$keys[$row['id']]['onUpdate'] = $row['on_update'];
+
+			if ($keys[$row['id']]['foreign'][0] == null) {
+				$keys[$row['id']]['foreign'] = null;
+			}
+		}
+		return array_values($keys);
+	}
+}

+ 439 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/SqlsrvDriver.php

@@ -0,0 +1,439 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+use Dibi\Connection;
+use Dibi\Helpers;
+
+
+/**
+ * The dibi driver for Microsoft SQL Server and SQL Azure databases.
+ *
+ * Driver options:
+ *   - host => the MS SQL server host name. It can also include a port number (hostname:port)
+ *   - username (or user)
+ *   - password (or pass)
+ *   - database => the database name to select
+ *   - options (array) => connection options {@link https://msdn.microsoft.com/en-us/library/cc296161(SQL.90).aspx}
+ *   - charset => character encoding to set (default is UTF-8)
+ *   - resource (resource) => existing connection resource
+ *   - lazy, profiler, result, substitutes, ... => see Dibi\Connection options
+ */
+class SqlsrvDriver implements Dibi\Driver, Dibi\ResultDriver
+{
+	use Dibi\Strict;
+
+	/** @var resource|null */
+	private $connection;
+
+	/** @var resource|null */
+	private $resultSet;
+
+	/** @var bool */
+	private $autoFree = true;
+
+	/** @var int|false  Affected rows */
+	private $affectedRows = false;
+
+	/** @var string */
+	private $version = '';
+
+
+	/**
+	 * @throws Dibi\NotSupportedException
+	 */
+	public function __construct()
+	{
+		if (!extension_loaded('sqlsrv')) {
+			throw new Dibi\NotSupportedException("PHP extension 'sqlsrv' is not loaded.");
+		}
+	}
+
+
+	/**
+	 * Connects to a database.
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public function connect(array &$config)
+	{
+		Helpers::alias($config, 'options|UID', 'username');
+		Helpers::alias($config, 'options|PWD', 'password');
+		Helpers::alias($config, 'options|Database', 'database');
+		Helpers::alias($config, 'options|CharacterSet', 'charset');
+
+		if (isset($config['resource'])) {
+			$this->connection = $config['resource'];
+
+		} else {
+			$options = $config['options'];
+
+			// Default values
+			if (!isset($options['CharacterSet'])) {
+				$options['CharacterSet'] = 'UTF-8';
+			}
+			$options['PWD'] = (string) $options['PWD'];
+			$options['UID'] = (string) $options['UID'];
+			$options['Database'] = (string) $options['Database'];
+
+			$this->connection = sqlsrv_connect($config['host'], $options);
+		}
+
+		if (!is_resource($this->connection)) {
+			$info = sqlsrv_errors();
+			throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
+		}
+		$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
+	}
+
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 */
+	public function disconnect()
+	{
+		@sqlsrv_close($this->connection); // @ - connection can be already disconnected
+	}
+
+
+	/**
+	 * Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return Dibi\ResultDriver|null
+	 * @throws Dibi\DriverException
+	 */
+	public function query($sql)
+	{
+		$this->affectedRows = false;
+		$res = sqlsrv_query($this->connection, $sql);
+
+		if ($res === false) {
+			$info = sqlsrv_errors();
+			throw new Dibi\DriverException($info[0]['message'], $info[0]['code'], $sql);
+
+		} elseif (is_resource($res)) {
+			$this->affectedRows = sqlsrv_rows_affected($res);
+			return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null;
+		}
+		return null;
+	}
+
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	public function getAffectedRows()
+	{
+		return $this->affectedRows;
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	public function getInsertId($sequence)
+	{
+		$res = sqlsrv_query($this->connection, 'SELECT SCOPE_IDENTITY()');
+		if (is_resource($res)) {
+			$row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
+			return $row[0];
+		}
+		return false;
+	}
+
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function begin($savepoint = null)
+	{
+		sqlsrv_begin_transaction($this->connection);
+	}
+
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function commit($savepoint = null)
+	{
+		sqlsrv_commit($this->connection);
+	}
+
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\DriverException
+	 */
+	public function rollback($savepoint = null)
+	{
+		sqlsrv_rollback($this->connection);
+	}
+
+
+	/**
+	 * Returns the connection resource.
+	 * @return resource|null
+	 */
+	public function getResource()
+	{
+		return is_resource($this->connection) ? $this->connection : null;
+	}
+
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Dibi\Reflector
+	 */
+	public function getReflector()
+	{
+		return new SqlsrvReflector($this);
+	}
+
+
+	/**
+	 * Result set driver factory.
+	 * @param  resource
+	 * @return Dibi\ResultDriver
+	 */
+	public function createResultDriver($resource)
+	{
+		$res = clone $this;
+		$res->resultSet = $resource;
+		return $res;
+	}
+
+
+	/********************* SQL ****************d*g**/
+
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	public function escapeText($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeBinary($value)
+	{
+		return "'" . str_replace("'", "''", $value) . "'";
+	}
+
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	public function escapeIdentifier($value)
+	{
+		// @see https://msdn.microsoft.com/en-us/library/ms176027.aspx
+		return '[' . str_replace(']', ']]', $value) . ']';
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	public function escapeBool($value)
+	{
+		return $value ? '1' : '0';
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDate($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d'");
+	}
+
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	public function escapeDateTime($value)
+	{
+		if (!$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
+			$value = new Dibi\DateTime($value);
+		}
+		return $value->format("'Y-m-d H:i:s.u'");
+	}
+
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	public function escapeLike($value, $pos)
+	{
+		$value = strtr($value, ["'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]']);
+		return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+	}
+
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	public function unescapeBinary($value)
+	{
+		return $value;
+	}
+
+
+	/** @deprecated */
+	public function escape($value, $type)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		return Helpers::escape($this, $value, $type);
+	}
+
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	public function applyLimit(&$sql, $limit, $offset)
+	{
+		if ($limit < 0 || $offset < 0) {
+			throw new Dibi\NotSupportedException('Negative offset or limit.');
+
+		} elseif (version_compare($this->version, '11', '<')) { // 11 == SQL Server 2012
+			if ($offset) {
+				throw new Dibi\NotSupportedException('Offset is not supported by this database.');
+
+			} elseif ($limit !== null) {
+				$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
+			}
+
+		} elseif ($limit !== null) {
+			// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
+			$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
+		} elseif ($offset) {
+			// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
+			$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
+		}
+	}
+
+
+	/********************* result set ****************d*g**/
+
+
+	/**
+	 * Automatically frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function __destruct()
+	{
+		$this->autoFree && $this->getResultResource() && $this->free();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	public function getRowCount()
+	{
+		throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
+	}
+
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 */
+	public function fetch($assoc)
+	{
+		return sqlsrv_fetch_array($this->resultSet, $assoc ? SQLSRV_FETCH_ASSOC : SQLSRV_FETCH_NUMERIC);
+	}
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int   the 0-based cursor pos to seek to
+	 * @return bool  true on success, false if unable to seek to specified record
+	 */
+	public function seek($row)
+	{
+		throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	public function free()
+	{
+		sqlsrv_free_stmt($this->resultSet);
+		$this->resultSet = null;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array
+	 */
+	public function getResultColumns()
+	{
+		$columns = [];
+		foreach ((array) sqlsrv_field_metadata($this->resultSet) as $fieldMetadata) {
+			$columns[] = [
+				'name' => $fieldMetadata['Name'],
+				'fullname' => $fieldMetadata['Name'],
+				'nativetype' => $fieldMetadata['Type'],
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns the result set resource.
+	 * @return resource|null
+	 */
+	public function getResultResource()
+	{
+		$this->autoFree = false;
+		return is_resource($this->resultSet) ? $this->resultSet : null;
+	}
+}

+ 135 - 0
api/vendor/dibi/dibi/src/Dibi/Drivers/SqlsrvReflector.php

@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Drivers;
+
+use Dibi;
+
+
+/**
+ * The dibi reflector for Microsoft SQL Server and SQL Azure databases.
+ * @internal
+ */
+class SqlsrvReflector implements Dibi\Reflector
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\Driver */
+	private $driver;
+
+
+	public function __construct(Dibi\Driver $driver)
+	{
+		$this->driver = $driver;
+	}
+
+
+	/**
+	 * Returns list of tables.
+	 * @return array
+	 */
+	public function getTables()
+	{
+		$res = $this->driver->query("SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE [TABLE_SCHEMA] = 'dbo'");
+		$tables = [];
+		while ($row = $res->fetch(false)) {
+			$tables[] = [
+				'name' => $row[0],
+				'view' => isset($row[1]) && $row[1] === 'VIEW',
+			];
+		}
+		return $tables;
+	}
+
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getColumns($table)
+	{
+		$res = $this->driver->query("
+			SELECT c.name as COLUMN_NAME, c.is_identity AS AUTO_INCREMENT
+			FROM sys.columns c
+			INNER JOIN sys.tables t ON c.object_id = t.object_id
+			WHERE t.name = {$this->driver->escapeText($table)}
+		");
+
+		$autoIncrements = [];
+		while ($row = $res->fetch(true)) {
+			$autoIncrements[$row['COLUMN_NAME']] = (bool) $row['AUTO_INCREMENT'];
+		}
+
+		$res = $this->driver->query("
+			SELECT C.COLUMN_NAME, C.DATA_TYPE, C.CHARACTER_MAXIMUM_LENGTH , C.COLUMN_DEFAULT  , C.NUMERIC_PRECISION, C.NUMERIC_SCALE , C.IS_NULLABLE, Case When Z.CONSTRAINT_NAME Is Null Then 0 Else 1 End As IsPartOfPrimaryKey
+			FROM INFORMATION_SCHEMA.COLUMNS As C
+			Outer Apply (
+				SELECT CCU.CONSTRAINT_NAME
+				FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS As TC
+				Join INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE As CCU
+					On CCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
+				WHERE TC.TABLE_SCHEMA = C.TABLE_SCHEMA
+					And TC.TABLE_NAME = C.TABLE_NAME
+					And TC.CONSTRAINT_TYPE = 'PRIMARY KEY'
+					And CCU.COLUMN_NAME = C.COLUMN_NAME
+			) As Z
+			WHERE C.TABLE_NAME = {$this->driver->escapeText($table)}
+		");
+		$columns = [];
+		while ($row = $res->fetch(true)) {
+			$columns[] = [
+				'name' => $row['COLUMN_NAME'],
+				'table' => $table,
+				'nativetype' => strtoupper($row['DATA_TYPE']),
+				'size' => $row['CHARACTER_MAXIMUM_LENGTH'],
+				'unsigned' => true,
+				'nullable' => $row['IS_NULLABLE'] === 'YES',
+				'default' => $row['COLUMN_DEFAULT'],
+				'autoincrement' => $autoIncrements[$row['COLUMN_NAME']],
+				'vendor' => $row,
+			];
+		}
+		return $columns;
+	}
+
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getIndexes($table)
+	{
+		$keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = N%s', $this->driver->escapeText($table)));
+		$keyUsages = [];
+		while ($row = $keyUsagesRes->fetch(true)) {
+			$keyUsages[$row['index_name']] = explode(',', $row['index_keys']);
+		}
+
+		$res = $this->driver->query("SELECT [i].* FROM [sys].[indexes] [i] INNER JOIN [sys].[tables] [t] ON [i].[object_id] = [t].[object_id] WHERE [t].[name] = {$this->driver->escapeText($table)}");
+		$indexes = [];
+		while ($row = $res->fetch(true)) {
+			$indexes[$row['name']]['name'] = $row['name'];
+			$indexes[$row['name']]['unique'] = $row['is_unique'] === 1;
+			$indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1;
+			$indexes[$row['name']]['columns'] = isset($keyUsages[$row['name']]) ? $keyUsages[$row['name']] : [];
+		}
+		return array_values($indexes);
+	}
+
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	public function getForeignKeys($table)
+	{
+		throw new Dibi\NotImplementedException;
+	}
+}

+ 97 - 0
api/vendor/dibi/dibi/src/Dibi/Event.php

@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * Profiler & logger event.
+ */
+class Event
+{
+	use Strict;
+
+	/** event type */
+	const CONNECT = 1,
+		SELECT = 4,
+		INSERT = 8,
+		DELETE = 16,
+		UPDATE = 32,
+		QUERY = 60, // SELECT | INSERT | DELETE | UPDATE
+		BEGIN = 64,
+		COMMIT = 128,
+		ROLLBACK = 256,
+		TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK
+		ALL = 1023;
+
+	/** @var Connection */
+	public $connection;
+
+	/** @var int */
+	public $type;
+
+	/** @var string */
+	public $sql;
+
+	/** @var Result|DriverException|null */
+	public $result;
+
+	/** @var float */
+	public $time;
+
+	/** @var int */
+	public $count;
+
+	/** @var array */
+	public $source;
+
+
+	public function __construct(Connection $connection, $type, $sql = null)
+	{
+		$this->connection = $connection;
+		$this->type = $type;
+		$this->sql = trim($sql);
+		$this->time = -microtime(true);
+
+		if ($type === self::QUERY && preg_match('#\(?\s*(SELECT|UPDATE|INSERT|DELETE)#iA', $this->sql, $matches)) {
+			static $types = [
+				'SELECT' => self::SELECT, 'UPDATE' => self::UPDATE,
+				'INSERT' => self::INSERT, 'DELETE' => self::DELETE,
+			];
+			$this->type = $types[strtoupper($matches[1])];
+		}
+
+		$rc = new \ReflectionClass('dibi');
+		$dibiDir = dirname($rc->getFileName()) . DIRECTORY_SEPARATOR;
+		foreach (debug_backtrace(false) as $row) {
+			if (isset($row['file']) && is_file($row['file']) && strpos($row['file'], $dibiDir) !== 0) {
+				$this->source = [$row['file'], (int) $row['line']];
+				break;
+			}
+		}
+
+		\dibi::$elapsedTime = false;
+		\dibi::$numOfQueries++;
+		\dibi::$sql = $sql;
+	}
+
+
+	public function done($result = null)
+	{
+		$this->result = $result;
+		try {
+			$this->count = $result instanceof Result ? count($result) : null;
+		} catch (Exception $e) {
+			$this->count = null;
+		}
+
+		$this->time += microtime(true);
+		\dibi::$elapsedTime = $this->time;
+		\dibi::$totalTime += $this->time;
+		return $this;
+	}
+}

+ 32 - 0
api/vendor/dibi/dibi/src/Dibi/Expression.php

@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * SQL expression.
+ */
+class Expression
+{
+	use Strict;
+
+	/** @var array */
+	private $values;
+
+
+	public function __construct()
+	{
+		$this->values = func_get_args();
+	}
+
+
+	public function getValues()
+	{
+		return $this->values;
+	}
+}

+ 521 - 0
api/vendor/dibi/dibi/src/Dibi/Fluent.php

@@ -0,0 +1,521 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * dibi SQL builder via fluent interfaces. EXPERIMENTAL!
+ *
+ * @method Fluent select(...$field)
+ * @method Fluent distinct()
+ * @method Fluent from($table)
+ * @method Fluent where(...$cond)
+ * @method Fluent groupBy(...$field)
+ * @method Fluent having(...$cond)
+ * @method Fluent orderBy(...$field)
+ * @method Fluent limit(int $limit)
+ * @method Fluent offset(int $offset)
+ * @method Fluent join(...$table)
+ * @method Fluent leftJoin(...$table)
+ * @method Fluent innerJoin(...$table)
+ * @method Fluent rightJoin(...$table)
+ * @method Fluent outerJoin(...$table)
+ * @method Fluent as(...$field)
+ * @method Fluent on(...$cond)
+ * @method Fluent using(...$cond)
+ */
+class Fluent implements IDataSource
+{
+	use Strict;
+
+	const REMOVE = false;
+
+	/** @var array */
+	public static $masks = [
+		'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
+			'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ],
+		'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'],
+		'INSERT' => ['INSERT', 'INTO', 'VALUES', 'SELECT'],
+		'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'],
+	];
+
+	/** @var array  default modifiers for arrays */
+	public static $modifiers = [
+		'SELECT' => '%n',
+		'FROM' => '%n',
+		'IN' => '%in',
+		'VALUES' => '%l',
+		'SET' => '%a',
+		'WHERE' => '%and',
+		'HAVING' => '%and',
+		'ORDER BY' => '%by',
+		'GROUP BY' => '%by',
+	];
+
+	/** @var array  clauses separators */
+	public static $separators = [
+		'SELECT' => ',',
+		'FROM' => ',',
+		'WHERE' => 'AND',
+		'GROUP BY' => ',',
+		'HAVING' => 'AND',
+		'ORDER BY' => ',',
+		'LIMIT' => false,
+		'OFFSET' => false,
+		'SET' => ',',
+		'VALUES' => ',',
+		'INTO' => false,
+	];
+
+	/** @var array  clauses */
+	public static $clauseSwitches = [
+		'JOIN' => 'FROM',
+		'INNER JOIN' => 'FROM',
+		'LEFT JOIN' => 'FROM',
+		'RIGHT JOIN' => 'FROM',
+	];
+
+	/** @var Connection */
+	private $connection;
+
+	/** @var array */
+	private $setups = [];
+
+	/** @var string */
+	private $command;
+
+	/** @var array */
+	private $clauses = [];
+
+	/** @var array */
+	private $flags = [];
+
+	/** @var array */
+	private $cursor;
+
+	/** @var HashMap  normalized clauses */
+	private static $normalizer;
+
+
+	/**
+	 * @param  Connection
+	 */
+	public function __construct(Connection $connection)
+	{
+		$this->connection = $connection;
+
+		if (self::$normalizer === null) {
+			self::$normalizer = new HashMap([__CLASS__, '_formatClause']);
+		}
+	}
+
+
+	/**
+	 * Appends new argument to the clause.
+	 * @param  string clause name
+	 * @param  array  arguments
+	 * @return self
+	 */
+	public function __call($clause, $args)
+	{
+		$clause = self::$normalizer->$clause;
+
+		// lazy initialization
+		if ($this->command === null) {
+			if (isset(self::$masks[$clause])) {
+				$this->clauses = array_fill_keys(self::$masks[$clause], null);
+			}
+			$this->cursor = &$this->clauses[$clause];
+			$this->cursor = [];
+			$this->command = $clause;
+		}
+
+		// auto-switch to a clause
+		if (isset(self::$clauseSwitches[$clause])) {
+			$this->cursor = &$this->clauses[self::$clauseSwitches[$clause]];
+		}
+
+		if (array_key_exists($clause, $this->clauses)) {
+			// append to clause
+			$this->cursor = &$this->clauses[$clause];
+
+			// TODO: really delete?
+			if ($args === [self::REMOVE]) {
+				$this->cursor = null;
+				return $this;
+			}
+
+			if (isset(self::$separators[$clause])) {
+				$sep = self::$separators[$clause];
+				if ($sep === false) { // means: replace
+					$this->cursor = [];
+
+				} elseif (!empty($this->cursor)) {
+					$this->cursor[] = $sep;
+				}
+			}
+
+		} else {
+			// append to currect flow
+			if ($args === [self::REMOVE]) {
+				return $this;
+			}
+
+			$this->cursor[] = $clause;
+		}
+
+		if ($this->cursor === null) {
+			$this->cursor = [];
+		}
+
+		// special types or argument
+		if (count($args) === 1) {
+			$arg = $args[0];
+			// TODO: really ignore true?
+			if ($arg === true) { // flag
+				return $this;
+
+			} elseif (is_string($arg) && preg_match('#^[a-z:_][a-z0-9_.:]*\z#i', $arg)) { // identifier
+				$args = [$clause === 'AS' ? '%N' : '%n', $arg];
+
+			} elseif (is_array($arg) || ($arg instanceof \Traversable && !$arg instanceof self)) { // any array
+				if (isset(self::$modifiers[$clause])) {
+					$args = [self::$modifiers[$clause], $arg];
+
+				} elseif (is_string(key($arg))) { // associative array
+					$args = ['%a', $arg];
+				}
+			} // case $arg === false is handled above
+		}
+
+		foreach ($args as $arg) {
+			if ($arg instanceof self) {
+				$arg = new Literal("($arg)");
+			}
+			$this->cursor[] = $arg;
+		}
+
+		return $this;
+	}
+
+
+	/**
+	 * Switch to a clause.
+	 * @param  string clause name
+	 * @return self
+	 */
+	public function clause($clause)
+	{
+		$this->cursor = &$this->clauses[self::$normalizer->$clause];
+		if ($this->cursor === null) {
+			$this->cursor = [];
+		}
+
+		return $this;
+	}
+
+
+	/**
+	 * Removes a clause.
+	 * @param  string clause name
+	 * @return self
+	 */
+	public function removeClause($clause)
+	{
+		$this->clauses[self::$normalizer->$clause] = null;
+		return $this;
+	}
+
+
+	/**
+	 * Change a SQL flag.
+	 * @param  string  flag name
+	 * @param  bool  value
+	 * @return self
+	 */
+	public function setFlag($flag, $value = true)
+	{
+		$flag = strtoupper($flag);
+		if ($value) {
+			$this->flags[$flag] = true;
+		} else {
+			unset($this->flags[$flag]);
+		}
+		return $this;
+	}
+
+
+	/**
+	 * Is a flag set?
+	 * @param  string  flag name
+	 * @return bool
+	 */
+	final public function getFlag($flag)
+	{
+		return isset($this->flags[strtoupper($flag)]);
+	}
+
+
+	/**
+	 * Returns SQL command.
+	 * @return string
+	 */
+	final public function getCommand()
+	{
+		return $this->command;
+	}
+
+
+	/**
+	 * Returns the dibi connection.
+	 * @return Connection
+	 */
+	final public function getConnection()
+	{
+		return $this->connection;
+	}
+
+
+	/**
+	 * Adds Result setup.
+	 * @param  string  method
+	 * @param  mixed   args
+	 * @return self
+	 */
+	public function setupResult($method)
+	{
+		$this->setups[] = func_get_args();
+		return $this;
+	}
+
+
+	/********************* executing ****************d*g**/
+
+
+	/**
+	 * Generates and executes SQL query.
+	 * @param  mixed what to return?
+	 * @return Result|int  result set or number of affected rows
+	 * @throws Exception
+	 */
+	public function execute($return = null)
+	{
+		$res = $this->query($this->_export());
+		switch ($return) {
+			case \dibi::IDENTIFIER:
+				return $this->connection->getInsertId();
+			case \dibi::AFFECTED_ROWS:
+				return $this->connection->getAffectedRows();
+			default:
+				return $res;
+		}
+	}
+
+
+	/**
+	 * Generates, executes SQL query and fetches the single row.
+	 * @return Row|false
+	 */
+	public function fetch()
+	{
+		if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
+			return $this->query($this->_export(null, ['%lmt', 1]))->fetch();
+		} else {
+			return $this->query($this->_export())->fetch();
+		}
+	}
+
+
+	/**
+	 * Like fetch(), but returns only first field.
+	 * @return mixed  value on success, false if no next record
+	 */
+	public function fetchSingle()
+	{
+		if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) {
+			return $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle();
+		} else {
+			return $this->query($this->_export())->fetchSingle();
+		}
+	}
+
+
+	/**
+	 * Fetches all records from table.
+	 * @param  int  offset
+	 * @param  int  limit
+	 * @return array
+	 */
+	public function fetchAll($offset = null, $limit = null)
+	{
+		return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll();
+	}
+
+
+	/**
+	 * Fetches all records from table and returns associative tree.
+	 * @param  string  associative descriptor
+	 * @return array
+	 */
+	public function fetchAssoc($assoc)
+	{
+		return $this->query($this->_export())->fetchAssoc($assoc);
+	}
+
+
+	/**
+	 * Fetches all records from table like $key => $value pairs.
+	 * @param  string  associative key
+	 * @param  string  value
+	 * @return array
+	 */
+	public function fetchPairs($key = null, $value = null)
+	{
+		return $this->query($this->_export())->fetchPairs($key, $value);
+	}
+
+
+	/**
+	 * Required by the IteratorAggregate interface.
+	 * @param  int  offset
+	 * @param  int  limit
+	 * @return ResultIterator
+	 */
+	public function getIterator($offset = null, $limit = null)
+	{
+		return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator();
+	}
+
+
+	/**
+	 * Generates and prints SQL query or it's part.
+	 * @param  string clause name
+	 * @return bool
+	 */
+	public function test($clause = null)
+	{
+		return $this->connection->test($this->_export($clause));
+	}
+
+
+	/**
+	 * @return int
+	 */
+	public function count()
+	{
+		return Helpers::intVal($this->query([
+			'SELECT COUNT(*) FROM (%ex', $this->_export(), ') [data]',
+		])->fetchSingle());
+	}
+
+
+	/**
+	 * @return Result|int
+	 */
+	private function query($args)
+	{
+		$res = $this->connection->query($args);
+		foreach ($this->setups as $setup) {
+			call_user_func_array([$res, array_shift($setup)], $setup);
+		}
+		return $res;
+	}
+
+
+	/********************* exporting ****************d*g**/
+
+
+	/**
+	 * @return DataSource
+	 */
+	public function toDataSource()
+	{
+		return new DataSource($this->connection->translate($this->_export()), $this->connection);
+	}
+
+
+	/**
+	 * Returns SQL query.
+	 * @return string
+	 */
+	final public function __toString()
+	{
+		try {
+			return $this->connection->translate($this->_export());
+		} catch (\Exception $e) {
+			trigger_error($e->getMessage(), E_USER_ERROR);
+			return '';
+		}
+	}
+
+
+	/**
+	 * Generates parameters for Translator.
+	 * @param  string clause name
+	 * @return array
+	 */
+	protected function _export($clause = null, $args = [])
+	{
+		if ($clause === null) {
+			$data = $this->clauses;
+			if ($this->command === 'SELECT' && ($data['LIMIT'] || $data['OFFSET'])) {
+				$args = array_merge(['%lmt %ofs', $data['LIMIT'][0], $data['OFFSET'][0]], $args);
+				unset($data['LIMIT'], $data['OFFSET']);
+			}
+
+		} else {
+			$clause = self::$normalizer->$clause;
+			if (array_key_exists($clause, $this->clauses)) {
+				$data = [$clause => $this->clauses[$clause]];
+			} else {
+				return [];
+			}
+		}
+
+		foreach ($data as $clause => $statement) {
+			if ($statement !== null) {
+				$args[] = $clause;
+				if ($clause === $this->command && $this->flags) {
+					$args[] = implode(' ', array_keys($this->flags));
+				}
+				foreach ($statement as $arg) {
+					$args[] = $arg;
+				}
+			}
+		}
+
+		return $args;
+	}
+
+
+	/**
+	 * Format camelCase clause name to UPPER CASE.
+	 * @param  string
+	 * @return string
+	 * @internal
+	 */
+	public static function _formatClause($s)
+	{
+		if ($s === 'order' || $s === 'group') {
+			$s .= 'By';
+			trigger_error("Did you mean '$s'?", E_USER_NOTICE);
+		}
+		return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
+	}
+
+
+	public function __clone()
+	{
+		// remove references
+		foreach ($this->clauses as $clause => $val) {
+			$this->clauses[$clause] = &$val;
+			unset($val);
+		}
+		$this->cursor = &$foo;
+	}
+}

+ 64 - 0
api/vendor/dibi/dibi/src/Dibi/HashMap.php

@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * Lazy cached storage.
+ * @internal
+ */
+abstract class HashMapBase
+{
+	private $callback;
+
+
+	public function __construct(callable $callback)
+	{
+		$this->callback = $callback;
+	}
+
+
+	public function setCallback(callable $callback)
+	{
+		$this->callback = $callback;
+	}
+
+
+	public function getCallback()
+	{
+		return $this->callback;
+	}
+}
+
+
+/**
+ * Lazy cached storage.
+ *
+ * @internal
+ */
+final class HashMap extends HashMapBase
+{
+	public function __set($nm, $val)
+	{
+		if ($nm == '') {
+			$nm = "\xFF";
+		}
+		$this->$nm = $val;
+	}
+
+
+	public function __get($nm)
+	{
+		if ($nm == '') {
+			$nm = "\xFF";
+			return isset($this->$nm) ? $this->$nm : $this->$nm = call_user_func($this->getCallback(), '');
+		} else {
+			return $this->$nm = call_user_func($this->getCallback(), $nm);
+		}
+	}
+}

+ 302 - 0
api/vendor/dibi/dibi/src/Dibi/Helpers.php

@@ -0,0 +1,302 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+class Helpers
+{
+	use Strict;
+
+	/** @var array */
+	private static $types;
+
+
+	/**
+	 * Prints out a syntax highlighted version of the SQL command or Result.
+	 * @param  string|Result
+	 * @param  bool  return output instead of printing it?
+	 * @return string
+	 */
+	public static function dump($sql = null, $return = false)
+	{
+		ob_start();
+		if ($sql instanceof Result && PHP_SAPI === 'cli') {
+			$hasColors = (substr(getenv('TERM'), 0, 5) === 'xterm');
+			$maxLen = 0;
+			foreach ($sql as $i => $row) {
+				if ($i === 0) {
+					foreach ($row as $col => $foo) {
+						$len = mb_strlen($col);
+						$maxLen = max($len, $maxLen);
+					}
+				}
+
+				echo $hasColors ? "\033[1;37m#row: $i\033[0m\n" : "#row: $i\n";
+				foreach ($row as $col => $val) {
+					$spaces = $maxLen - mb_strlen($col) + 2;
+					echo "$col" . str_repeat(' ', $spaces) . "$val\n";
+				}
+				echo "\n";
+			}
+
+			echo empty($row) ? "empty result set\n\n" : "\n";
+
+		} elseif ($sql instanceof Result) {
+			foreach ($sql as $i => $row) {
+				if ($i === 0) {
+					echo "\n<table class=\"dump\">\n<thead>\n\t<tr>\n\t\t<th>#row</th>\n";
+					foreach ($row as $col => $foo) {
+						echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n";
+					}
+					echo "\t</tr>\n</thead>\n<tbody>\n";
+				}
+
+				echo "\t<tr>\n\t\t<th>", $i, "</th>\n";
+				foreach ($row as $col) {
+					echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n";
+				}
+				echo "\t</tr>\n";
+			}
+
+			echo empty($row)
+				? '<p><em>empty result set</em></p>'
+				: "</tbody>\n</table>\n";
+
+		} else {
+			if ($sql === null) {
+				$sql = \dibi::$sql;
+			}
+
+			static $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE|START\s+TRANSACTION|BEGIN|COMMIT|ROLLBACK(?:\s+TO\s+SAVEPOINT)?|(?:RELEASE\s+)?SAVEPOINT';
+			static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
+
+			// insert new lines
+			$sql = " $sql ";
+			$sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
+
+			// reduce spaces
+			$sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
+
+			$sql = wordwrap($sql, 100);
+			$sql = preg_replace("#([ \t]*\r?\n){2,}#", "\n", $sql);
+
+			// syntax highlight
+			$highlighter = "#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is";
+			if (PHP_SAPI === 'cli') {
+				if (substr(getenv('TERM'), 0, 5) === 'xterm') {
+					$sql = preg_replace_callback($highlighter, function ($m) {
+						if (!empty($m[1])) { // comment
+							return "\033[1;30m" . $m[1] . "\033[0m";
+
+						} elseif (!empty($m[2])) { // error
+							return "\033[1;31m" . $m[2] . "\033[0m";
+
+						} elseif (!empty($m[3])) { // most important keywords
+							return "\033[1;34m" . $m[3] . "\033[0m";
+
+						} elseif (!empty($m[4])) { // other keywords
+							return "\033[1;32m" . $m[4] . "\033[0m";
+						}
+					}, $sql);
+				}
+				echo trim($sql) . "\n\n";
+
+			} else {
+				$sql = htmlspecialchars($sql);
+				$sql = preg_replace_callback($highlighter, function ($m) {
+					if (!empty($m[1])) { // comment
+						return '<em style="color:gray">' . $m[1] . '</em>';
+
+					} elseif (!empty($m[2])) { // error
+						return '<strong style="color:red">' . $m[2] . '</strong>';
+
+					} elseif (!empty($m[3])) { // most important keywords
+						return '<strong style="color:blue">' . $m[3] . '</strong>';
+
+					} elseif (!empty($m[4])) { // other keywords
+						return '<strong style="color:green">' . $m[4] . '</strong>';
+					}
+				}, $sql);
+				echo '<pre class="dump">', trim($sql), "</pre>\n\n";
+			}
+		}
+
+		if ($return) {
+			return ob_get_clean();
+		} else {
+			ob_end_flush();
+		}
+	}
+
+
+	/**
+	 * Finds the best suggestion.
+	 * @return string|null
+	 * @internal
+	 */
+	public static function getSuggestion(array $items, $value)
+	{
+		$best = null;
+		$min = (strlen($value) / 4 + 1) * 10 + .1;
+		foreach (array_unique($items, SORT_REGULAR) as $item) {
+			$item = is_object($item) ? $item->getName() : $item;
+			if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
+				$min = $len;
+				$best = $item;
+			}
+		}
+		return $best;
+	}
+
+
+	/** @internal */
+	public static function escape(Driver $driver, $value, $type)
+	{
+		static $types = [
+			Type::TEXT => 'text',
+			Type::BINARY => 'binary',
+			Type::BOOL => 'bool',
+			Type::DATE => 'date',
+			Type::DATETIME => 'datetime',
+			\dibi::IDENTIFIER => 'identifier',
+		];
+		if (isset($types[$type])) {
+			return $driver->{'escape' . $types[$type]}($value);
+		} else {
+			throw new \InvalidArgumentException('Unsupported type.');
+		}
+	}
+
+
+	/**
+	 * Heuristic type detection.
+	 * @param  string
+	 * @return string|null
+	 * @internal
+	 */
+	public static function detectType($type)
+	{
+		static $patterns = [
+			'^_' => Type::TEXT, // PostgreSQL arrays
+			'BYTEA|BLOB|BIN' => Type::BINARY,
+			'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
+			'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
+			'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::FLOAT,
+			'^TIME$' => Type::TIME,
+			'TIME' => Type::DATETIME, // DATETIME, TIMESTAMP
+			'DATE' => Type::DATE,
+			'BOOL' => Type::BOOL,
+		];
+
+		foreach ($patterns as $s => $val) {
+			if (preg_match("#$s#i", $type)) {
+				return $val;
+			}
+		}
+		return null;
+	}
+
+
+	/**
+	 * @internal
+	 */
+	public static function getTypeCache()
+	{
+		if (self::$types === null) {
+			self::$types = new HashMap([__CLASS__, 'detectType']);
+		}
+		return self::$types;
+	}
+
+
+	/**
+	 * Apply configuration alias or default values.
+	 * @param  array  connect configuration
+	 * @param  string key
+	 * @param  string alias key
+	 * @return void
+	 */
+	public static function alias(&$config, $key, $alias)
+	{
+		$foo = &$config;
+		foreach (explode('|', $key) as $key) {
+			$foo = &$foo[$key];
+		}
+
+		if (!isset($foo) && isset($config[$alias])) {
+			$foo = $config[$alias];
+			unset($config[$alias]);
+		}
+	}
+
+
+	/**
+	 * Import SQL dump from file.
+	 * @return int  count of sql commands
+	 */
+	public static function loadFromFile(Connection $connection, $file, callable $onProgress = null)
+	{
+		@set_time_limit(0); // intentionally @
+
+		$handle = @fopen($file, 'r'); // intentionally @
+		if (!$handle) {
+			throw new \RuntimeException("Cannot open file '$file'.");
+		}
+
+		$stat = fstat($handle);
+		$count = $size = 0;
+		$delimiter = ';';
+		$sql = '';
+		$driver = $connection->getDriver();
+		while (($s = fgets($handle)) !== false) {
+			$size += strlen($s);
+			if (strtoupper(substr($s, 0, 10)) === 'DELIMITER ') {
+				$delimiter = trim(substr($s, 10));
+
+			} elseif (substr($ts = rtrim($s), -strlen($delimiter)) === $delimiter) {
+				$sql .= substr($ts, 0, -strlen($delimiter));
+				$driver->query($sql);
+				$sql = '';
+				$count++;
+				if ($onProgress) {
+					call_user_func($onProgress, $count, isset($stat['size']) ? $size * 100 / $stat['size'] : null);
+				}
+
+			} else {
+				$sql .= $s;
+			}
+		}
+
+		if (rtrim($sql) !== '') {
+			$driver->query($sql);
+			$count++;
+			if ($onProgress) {
+				call_user_func($onProgress, $count, isset($stat['size']) ? 100 : null);
+			}
+		}
+		fclose($handle);
+		return $count;
+	}
+
+
+	/**
+	 * @internal
+	 * @return string|int
+	 */
+	public static function intVal($value)
+	{
+		if (is_int($value)) {
+			return $value;
+		} elseif (is_string($value) && preg_match('#-?\d++\z#A', $value)) {
+			// support for long numbers - keep them unchanged
+			return is_float($number = $value * 1) ? $value : $number;
+		} else {
+			throw new Exception("Expected number, '$value' given.");
+		}
+	}
+}

+ 35 - 0
api/vendor/dibi/dibi/src/Dibi/Literal.php

@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * SQL literal value.
+ */
+class Literal
+{
+	use Strict;
+
+	/** @var string */
+	private $value;
+
+
+	public function __construct($value)
+	{
+		$this->value = (string) $value;
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function __toString()
+	{
+		return $this->value;
+	}
+}

+ 75 - 0
api/vendor/dibi/dibi/src/Dibi/Loggers/FileLogger.php

@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Loggers;
+
+use Dibi;
+
+
+/**
+ * dibi file logger.
+ */
+class FileLogger
+{
+	use Dibi\Strict;
+
+	/** @var string  Name of the file where SQL errors should be logged */
+	public $file;
+
+	/** @var int */
+	public $filter;
+
+
+	public function __construct($file, $filter = null)
+	{
+		$this->file = $file;
+		$this->filter = $filter ? (int) $filter : Dibi\Event::QUERY;
+	}
+
+
+	/**
+	 * After event notification.
+	 * @return void
+	 */
+	public function logEvent(Dibi\Event $event)
+	{
+		if (($event->type & $this->filter) === 0) {
+			return;
+		}
+
+		$handle = fopen($this->file, 'a');
+		if (!$handle) {
+			return; // or throw exception?
+		}
+		flock($handle, LOCK_EX);
+
+		if ($event->result instanceof \Exception) {
+			$message = $event->result->getMessage();
+			if ($code = $event->result->getCode()) {
+				$message = "[$code] $message";
+			}
+			fwrite($handle,
+				"ERROR: $message"
+				. "\n-- SQL: " . $event->sql
+				. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
+				. ";\n-- " . date('Y-m-d H:i:s')
+				. "\n\n"
+			);
+		} else {
+			fwrite($handle,
+				'OK: ' . $event->sql
+				. ($event->count ? ";\n-- rows: " . $event->count : '')
+				. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
+				. "\n-- source: " . implode(':', $event->source)
+				. "\n-- driver: " . $event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name')
+				. "\n-- " . date('Y-m-d H:i:s')
+				. "\n\n"
+			);
+		}
+		fclose($handle);
+	}
+}

+ 94 - 0
api/vendor/dibi/dibi/src/Dibi/Loggers/FirePhpLogger.php

@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Loggers;
+
+use Dibi;
+
+
+/**
+ * dibi FirePHP logger.
+ */
+class FirePhpLogger
+{
+	use Dibi\Strict;
+
+	/** maximum number of rows */
+	public static $maxQueries = 30;
+
+	/** maximum SQL length */
+	public static $maxLength = 1000;
+
+	/** size of json stream chunk */
+	public static $streamChunkSize = 4990;
+
+	/** @var int */
+	public $filter;
+
+	/** @var int  Elapsed time for all queries */
+	public $totalTime = 0;
+
+	/** @var int  Number of all queries */
+	public $numOfQueries = 0;
+
+	/** @var array */
+	private static $fireTable = [['Time', 'SQL Statement', 'Rows', 'Connection']];
+
+
+	/**
+	 * @return bool
+	 */
+	public static function isAvailable()
+	{
+		return isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'FirePHP/');
+	}
+
+
+	public function __construct($filter = null)
+	{
+		$this->filter = $filter ? (int) $filter : Dibi\Event::QUERY;
+	}
+
+
+	/**
+	 * After event notification.
+	 * @return void
+	 */
+	public function logEvent(Dibi\Event $event)
+	{
+		if (headers_sent() || ($event->type & $this->filter) === 0 || count(self::$fireTable) > self::$maxQueries) {
+			return;
+		}
+
+		if (!$this->numOfQueries) {
+			header('X-Wf-Protocol-dibi: http://meta.wildfirehq.org/Protocol/JsonStream/0.2');
+			header('X-Wf-dibi-Plugin-1: http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.2.0');
+			header('X-Wf-dibi-Structure-1: http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1');
+		}
+		$this->totalTime += $event->time;
+		$this->numOfQueries++;
+		self::$fireTable[] = [
+			sprintf('%0.3f', $event->time * 1000),
+			strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql,
+			$event->result instanceof \Exception ? 'ERROR' : (string) $event->count,
+			$event->connection->getConfig('driver') . '/' . $event->connection->getConfig('name'),
+		];
+
+		$payload = json_encode([
+			[
+				'Type' => 'TABLE',
+				'Label' => 'dibi profiler (' . $this->numOfQueries . ' SQL queries took ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms)',
+			],
+			self::$fireTable,
+		]);
+		foreach (str_split($payload, self::$streamChunkSize) as $num => $s) {
+			$num++;
+			header("X-Wf-dibi-1-1-d$num: |$s|\\"); // protocol-, structure-, plugin-, message-index
+		}
+		header("X-Wf-dibi-1-1-d$num: |$s|");
+	}
+}

+ 165 - 0
api/vendor/dibi/dibi/src/Dibi/Reflection/Column.php

@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Reflection;
+
+use Dibi;
+use Dibi\Type;
+
+
+/**
+ * Reflection metadata class for a table or result set column.
+ *
+ * @property-read string $name
+ * @property-read string $fullName
+ * @property-read Table $table
+ * @property-read string $type
+ * @property-read mixed $nativeType
+ * @property-read int|null $size
+ * @property-read bool|null $unsigned
+ * @property-read bool|null $nullable
+ * @property-read bool|null $autoIncrement
+ * @property-read mixed $default
+ */
+class Column
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\Reflector|null when created by Result */
+	private $reflector;
+
+	/** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
+	private $info;
+
+
+	public function __construct(Dibi\Reflector $reflector = null, array $info)
+	{
+		$this->reflector = $reflector;
+		$this->info = $info;
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getName()
+	{
+		return $this->info['name'];
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getFullName()
+	{
+		return isset($this->info['fullname']) ? $this->info['fullname'] : null;
+	}
+
+
+	/**
+	 * @return bool
+	 */
+	public function hasTable()
+	{
+		return !empty($this->info['table']);
+	}
+
+
+	/**
+	 * @return Table
+	 */
+	public function getTable()
+	{
+		if (empty($this->info['table']) || !$this->reflector) {
+			throw new Dibi\Exception('Table is unknown or not available.');
+		}
+		return new Table($this->reflector, ['name' => $this->info['table']]);
+	}
+
+
+	/**
+	 * @return string|null
+	 */
+	public function getTableName()
+	{
+		return isset($this->info['table']) && $this->info['table'] != null ? $this->info['table'] : null; // intentionally ==
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getType()
+	{
+		return Dibi\Helpers::getTypeCache()->{$this->info['nativetype']};
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getNativeType()
+	{
+		return $this->info['nativetype'];
+	}
+
+
+	/**
+	 * @return int|null
+	 */
+	public function getSize()
+	{
+		return isset($this->info['size']) ? (int) $this->info['size'] : null;
+	}
+
+
+	/**
+	 * @return bool|null
+	 */
+	public function isUnsigned()
+	{
+		return isset($this->info['unsigned']) ? (bool) $this->info['unsigned'] : null;
+	}
+
+
+	/**
+	 * @return bool|null
+	 */
+	public function isNullable()
+	{
+		return isset($this->info['nullable']) ? (bool) $this->info['nullable'] : null;
+	}
+
+
+	/**
+	 * @return bool|null
+	 */
+	public function isAutoIncrement()
+	{
+		return isset($this->info['autoincrement']) ? (bool) $this->info['autoincrement'] : null;
+	}
+
+
+	/**
+	 * @return mixed
+	 */
+	public function getDefault()
+	{
+		return isset($this->info['default']) ? $this->info['default'] : null;
+	}
+
+
+	/**
+	 * @param  string
+	 * @return mixed
+	 */
+	public function getVendorInfo($key)
+	{
+		return isset($this->info['vendor'][$key]) ? $this->info['vendor'][$key] : null;
+	}
+}

+ 114 - 0
api/vendor/dibi/dibi/src/Dibi/Reflection/Database.php

@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Reflection;
+
+use Dibi;
+
+
+/**
+ * Reflection metadata class for a database.
+ *
+ * @property-read string $name
+ * @property-read array $tables
+ * @property-read array $tableNames
+ */
+class Database
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\Reflector */
+	private $reflector;
+
+	/** @var string */
+	private $name;
+
+	/** @var array */
+	private $tables;
+
+
+	public function __construct(Dibi\Reflector $reflector, $name)
+	{
+		$this->reflector = $reflector;
+		$this->name = $name;
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getName()
+	{
+		return $this->name;
+	}
+
+
+	/**
+	 * @return Table[]
+	 */
+	public function getTables()
+	{
+		$this->init();
+		return array_values($this->tables);
+	}
+
+
+	/**
+	 * @return string[]
+	 */
+	public function getTableNames()
+	{
+		$this->init();
+		$res = [];
+		foreach ($this->tables as $table) {
+			$res[] = $table->getName();
+		}
+		return $res;
+	}
+
+
+	/**
+	 * @param  string
+	 * @return Table
+	 */
+	public function getTable($name)
+	{
+		$this->init();
+		$l = strtolower($name);
+		if (isset($this->tables[$l])) {
+			return $this->tables[$l];
+
+		} else {
+			throw new Dibi\Exception("Database '$this->name' has no table '$name'.");
+		}
+	}
+
+
+	/**
+	 * @param  string
+	 * @return bool
+	 */
+	public function hasTable($name)
+	{
+		$this->init();
+		return isset($this->tables[strtolower($name)]);
+	}
+
+
+	/**
+	 * @return void
+	 */
+	protected function init()
+	{
+		if ($this->tables === null) {
+			$this->tables = [];
+			foreach ($this->reflector->getTables() as $info) {
+				$this->tables[strtolower($info['name'])] = new Table($this->reflector, $info);
+			}
+		}
+	}
+}

+ 53 - 0
api/vendor/dibi/dibi/src/Dibi/Reflection/ForeignKey.php

@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Reflection;
+
+use Dibi;
+
+
+/**
+ * Reflection metadata class for a foreign key.
+ *
+ * @property-read string $name
+ * @property-read array $references
+ */
+class ForeignKey
+{
+	use Dibi\Strict;
+
+	/** @var string */
+	private $name;
+
+	/** @var array of [local, foreign, onDelete, onUpdate] */
+	private $references;
+
+
+	public function __construct($name, array $references)
+	{
+		$this->name = $name;
+		$this->references = $references;
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getName()
+	{
+		return $this->name;
+	}
+
+
+	/**
+	 * @return array
+	 */
+	public function getReferences()
+	{
+		return $this->references;
+	}
+}

+ 69 - 0
api/vendor/dibi/dibi/src/Dibi/Reflection/Index.php

@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Reflection;
+
+use Dibi;
+
+
+/**
+ * Reflection metadata class for a index or primary key.
+ *
+ * @property-read string $name
+ * @property-read array $columns
+ * @property-read bool $unique
+ * @property-read bool $primary
+ */
+class Index
+{
+	use Dibi\Strict;
+
+	/** @var array (name, columns, [unique], [primary]) */
+	private $info;
+
+
+	public function __construct(array $info)
+	{
+		$this->info = $info;
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getName()
+	{
+		return $this->info['name'];
+	}
+
+
+	/**
+	 * @return array
+	 */
+	public function getColumns()
+	{
+		return $this->info['columns'];
+	}
+
+
+	/**
+	 * @return bool
+	 */
+	public function isUnique()
+	{
+		return !empty($this->info['unique']);
+	}
+
+
+	/**
+	 * @return bool
+	 */
+	public function isPrimary()
+	{
+		return !empty($this->info['primary']);
+	}
+}

+ 105 - 0
api/vendor/dibi/dibi/src/Dibi/Reflection/Result.php

@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Reflection;
+
+use Dibi;
+
+
+/**
+ * Reflection metadata class for a result set.
+ *
+ * @property-read array $columns
+ * @property-read array $columnNames
+ */
+class Result
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\ResultDriver */
+	private $driver;
+
+	/** @var array */
+	private $columns;
+
+	/** @var array */
+	private $names;
+
+
+	public function __construct(Dibi\ResultDriver $driver)
+	{
+		$this->driver = $driver;
+	}
+
+
+	/**
+	 * @return Column[]
+	 */
+	public function getColumns()
+	{
+		$this->initColumns();
+		return array_values($this->columns);
+	}
+
+
+	/**
+	 * @param  bool
+	 * @return string[]
+	 */
+	public function getColumnNames($fullNames = false)
+	{
+		$this->initColumns();
+		$res = [];
+		foreach ($this->columns as $column) {
+			$res[] = $fullNames ? $column->getFullName() : $column->getName();
+		}
+		return $res;
+	}
+
+
+	/**
+	 * @param  string
+	 * @return Column
+	 */
+	public function getColumn($name)
+	{
+		$this->initColumns();
+		$l = strtolower($name);
+		if (isset($this->names[$l])) {
+			return $this->names[$l];
+
+		} else {
+			throw new Dibi\Exception("Result set has no column '$name'.");
+		}
+	}
+
+
+	/**
+	 * @param  string
+	 * @return bool
+	 */
+	public function hasColumn($name)
+	{
+		$this->initColumns();
+		return isset($this->names[strtolower($name)]);
+	}
+
+
+	/**
+	 * @return void
+	 */
+	protected function initColumns()
+	{
+		if ($this->columns === null) {
+			$this->columns = [];
+			$reflector = $this->driver instanceof Dibi\Reflector ? $this->driver : null;
+			foreach ($this->driver->getResultColumns() as $info) {
+				$this->columns[] = $this->names[strtolower($info['name'])] = new Column($reflector, $info);
+			}
+		}
+	}
+}

+ 200 - 0
api/vendor/dibi/dibi/src/Dibi/Reflection/Table.php

@@ -0,0 +1,200 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi\Reflection;
+
+use Dibi;
+
+
+/**
+ * Reflection metadata class for a database table.
+ *
+ * @property-read string $name
+ * @property-read bool $view
+ * @property-read array $columns
+ * @property-read array $columnNames
+ * @property-read array $foreignKeys
+ * @property-read array $indexes
+ * @property-read Index $primaryKey
+ */
+class Table
+{
+	use Dibi\Strict;
+
+	/** @var Dibi\Reflector */
+	private $reflector;
+
+	/** @var string */
+	private $name;
+
+	/** @var bool */
+	private $view;
+
+	/** @var array */
+	private $columns;
+
+	/** @var array */
+	private $foreignKeys;
+
+	/** @var array */
+	private $indexes;
+
+	/** @var Index */
+	private $primaryKey;
+
+
+	public function __construct(Dibi\Reflector $reflector, array $info)
+	{
+		$this->reflector = $reflector;
+		$this->name = $info['name'];
+		$this->view = !empty($info['view']);
+	}
+
+
+	/**
+	 * @return string
+	 */
+	public function getName()
+	{
+		return $this->name;
+	}
+
+
+	/**
+	 * @return bool
+	 */
+	public function isView()
+	{
+		return $this->view;
+	}
+
+
+	/**
+	 * @return Column[]
+	 */
+	public function getColumns()
+	{
+		$this->initColumns();
+		return array_values($this->columns);
+	}
+
+
+	/**
+	 * @return string[]
+	 */
+	public function getColumnNames()
+	{
+		$this->initColumns();
+		$res = [];
+		foreach ($this->columns as $column) {
+			$res[] = $column->getName();
+		}
+		return $res;
+	}
+
+
+	/**
+	 * @param  string
+	 * @return Column
+	 */
+	public function getColumn($name)
+	{
+		$this->initColumns();
+		$l = strtolower($name);
+		if (isset($this->columns[$l])) {
+			return $this->columns[$l];
+
+		} else {
+			throw new Dibi\Exception("Table '$this->name' has no column '$name'.");
+		}
+	}
+
+
+	/**
+	 * @param  string
+	 * @return bool
+	 */
+	public function hasColumn($name)
+	{
+		$this->initColumns();
+		return isset($this->columns[strtolower($name)]);
+	}
+
+
+	/**
+	 * @return ForeignKey[]
+	 */
+	public function getForeignKeys()
+	{
+		$this->initForeignKeys();
+		return $this->foreignKeys;
+	}
+
+
+	/**
+	 * @return Index[]
+	 */
+	public function getIndexes()
+	{
+		$this->initIndexes();
+		return $this->indexes;
+	}
+
+
+	/**
+	 * @return Index
+	 */
+	public function getPrimaryKey()
+	{
+		$this->initIndexes();
+		return $this->primaryKey;
+	}
+
+
+	/**
+	 * @return void
+	 */
+	protected function initColumns()
+	{
+		if ($this->columns === null) {
+			$this->columns = [];
+			foreach ($this->reflector->getColumns($this->name) as $info) {
+				$this->columns[strtolower($info['name'])] = new Column($this->reflector, $info);
+			}
+		}
+	}
+
+
+	/**
+	 * @return void
+	 */
+	protected function initIndexes()
+	{
+		if ($this->indexes === null) {
+			$this->initColumns();
+			$this->indexes = [];
+			foreach ($this->reflector->getIndexes($this->name) as $info) {
+				foreach ($info['columns'] as $key => $name) {
+					$info['columns'][$key] = $this->columns[strtolower($name)];
+				}
+				$this->indexes[strtolower($info['name'])] = new Index($info);
+				if (!empty($info['primary'])) {
+					$this->primaryKey = $this->indexes[strtolower($info['name'])];
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * @return void
+	 */
+	protected function initForeignKeys()
+	{
+		throw new Dibi\NotImplementedException;
+	}
+}

+ 619 - 0
api/vendor/dibi/dibi/src/Dibi/Result.php

@@ -0,0 +1,619 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * dibi result set.
+ *
+ * <code>
+ * $result = dibi::query('SELECT * FROM [table]');
+ *
+ * $row   = $result->fetch();
+ * $value = $result->fetchSingle();
+ * $table = $result->fetchAll();
+ * $pairs = $result->fetchPairs();
+ * $assoc = $result->fetchAssoc('col1');
+ * $assoc = $result->fetchAssoc('col1[]col2->col3');
+ *
+ * unset($result);
+ * </code>
+ *
+ * @property-read int $rowCount
+ */
+class Result implements IDataSource
+{
+	use Strict;
+
+	/** @var array  ResultDriver */
+	private $driver;
+
+	/** @var array  Translate table */
+	private $types = [];
+
+	/** @var Reflection\Result */
+	private $meta;
+
+	/** @var bool  Already fetched? Used for allowance for first seek(0) */
+	private $fetched = false;
+
+	/** @var string  returned object class */
+	private $rowClass = 'Dibi\Row';
+
+	/** @var callable  returned object factory*/
+	private $rowFactory;
+
+	/** @var array  format */
+	private $formats = [];
+
+
+	/**
+	 * @param  ResultDriver
+	 */
+	public function __construct($driver)
+	{
+		$this->driver = $driver;
+		$this->detectTypes();
+	}
+
+
+	/**
+	 * @deprecated
+	 */
+	final public function getResource()
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use getResultDriver()->getResultResource().', E_USER_DEPRECATED);
+		return $this->getResultDriver()->getResultResource();
+	}
+
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @return void
+	 */
+	final public function free()
+	{
+		if ($this->driver !== null) {
+			$this->driver->free();
+			$this->driver = $this->meta = null;
+		}
+	}
+
+
+	/**
+	 * Safe access to property $driver.
+	 * @return ResultDriver
+	 * @throws \RuntimeException
+	 */
+	final public function getResultDriver()
+	{
+		if ($this->driver === null) {
+			throw new \RuntimeException('Result-set was released from memory.');
+		}
+
+		return $this->driver;
+	}
+
+
+	/********************* rows ****************d*g**/
+
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int      the 0-based cursor pos to seek to
+	 * @return bool     true on success, false if unable to seek to specified record
+	 * @throws Exception
+	 */
+	final public function seek($row)
+	{
+		return ($row != 0 || $this->fetched) // intentionally ==
+			? (bool) $this->getResultDriver()->seek($row)
+			: true;
+	}
+
+
+	/**
+	 * Required by the Countable interface.
+	 * @return int
+	 */
+	final public function count()
+	{
+		return $this->getResultDriver()->getRowCount();
+	}
+
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	final public function getRowCount()
+	{
+		return $this->getResultDriver()->getRowCount();
+	}
+
+
+	/**
+	 * Required by the IteratorAggregate interface.
+	 * @return ResultIterator
+	 */
+	final public function getIterator()
+	{
+		return new ResultIterator($this);
+	}
+
+
+	/********************* fetching rows ****************d*g**/
+
+
+	/**
+	 * Set fetched object class. This class should extend the Row class.
+	 * @param  string
+	 * @return self
+	 */
+	public function setRowClass($class)
+	{
+		$this->rowClass = $class;
+		return $this;
+	}
+
+
+	/**
+	 * Returns fetched object class name.
+	 * @return string
+	 */
+	public function getRowClass()
+	{
+		return $this->rowClass;
+	}
+
+
+	/**
+	 * Set a factory to create fetched object instances. These should extend the Row class.
+	 * @return self
+	 */
+	public function setRowFactory(callable $callback)
+	{
+		$this->rowFactory = $callback;
+		return $this;
+	}
+
+
+	/**
+	 * Fetches the row at current position, process optional type conversion.
+	 * and moves the internal cursor to the next position
+	 * @return Row|false
+	 */
+	final public function fetch()
+	{
+		$row = $this->getResultDriver()->fetch(true);
+		if (!is_array($row)) {
+			return false;
+		}
+		$this->fetched = true;
+		$this->normalize($row);
+		if ($this->rowFactory) {
+			return call_user_func($this->rowFactory, $row);
+		} elseif ($this->rowClass) {
+			$row = new $this->rowClass($row);
+		}
+		return $row;
+	}
+
+
+	/**
+	 * Like fetch(), but returns only first field.
+	 * @return mixed value on success, false if no next record
+	 */
+	final public function fetchSingle()
+	{
+		$row = $this->getResultDriver()->fetch(true);
+		if (!is_array($row)) {
+			return false;
+		}
+		$this->fetched = true;
+		$this->normalize($row);
+		return reset($row);
+	}
+
+
+	/**
+	 * Fetches all records from table.
+	 * @param  int  offset
+	 * @param  int  limit
+	 * @return Row[]
+	 */
+	final public function fetchAll($offset = null, $limit = null)
+	{
+		$limit = $limit === null ? -1 : Helpers::intVal($limit);
+		$this->seek($offset);
+		$row = $this->fetch();
+		if (!$row) {
+			return [];  // empty result set
+		}
+
+		$data = [];
+		do {
+			if ($limit === 0) {
+				break;
+			}
+			$limit--;
+			$data[] = $row;
+		} while ($row = $this->fetch());
+
+		return $data;
+	}
+
+
+	/**
+	 * Fetches all records from table and returns associative tree.
+	 * Examples:
+	 * - associative descriptor: col1[]col2->col3
+	 *   builds a tree:          $tree[$val1][$index][$val2]->col3[$val3] = {record}
+	 * - associative descriptor: col1|col2->col3=col4
+	 *   builds a tree:          $tree[$val1][$val2]->col3[$val3] = val4
+	 * @param  string  associative descriptor
+	 * @return array
+	 * @throws \InvalidArgumentException
+	 */
+	final public function fetchAssoc($assoc)
+	{
+		if (strpos($assoc, ',') !== false) {
+			return $this->oldFetchAssoc($assoc);
+		}
+
+		$this->seek(0);
+		$row = $this->fetch();
+		if (!$row) {
+			return [];  // empty result set
+		}
+
+		$data = null;
+		$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+		// check columns
+		foreach ($assoc as $as) {
+			// offsetExists ignores null in PHP 5.2.1, isset() surprisingly null accepts
+			if ($as !== '[]' && $as !== '=' && $as !== '->' && $as !== '|' && !property_exists($row, $as)) {
+				throw new \InvalidArgumentException("Unknown column '$as' in associative descriptor.");
+			}
+		}
+
+		if ($as === '->') { // must not be last
+			array_pop($assoc);
+		}
+
+		if (empty($assoc)) {
+			$assoc[] = '[]';
+		}
+
+		// make associative tree
+		do {
+			$x = &$data;
+
+			// iterative deepening
+			foreach ($assoc as $i => $as) {
+				if ($as === '[]') { // indexed-array node
+					$x = &$x[];
+
+				} elseif ($as === '=') { // "value" node
+					$x = $row->{$assoc[$i + 1]};
+					continue 2;
+
+				} elseif ($as === '->') { // "object" node
+					if ($x === null) {
+						$x = clone $row;
+						$x = &$x->{$assoc[$i + 1]};
+						$x = null; // prepare child node
+					} else {
+						$x = &$x->{$assoc[$i + 1]};
+					}
+
+				} elseif ($as !== '|') { // associative-array node
+					$x = &$x[$row->$as];
+				}
+			}
+
+			if ($x === null) { // build leaf
+				$x = $row;
+			}
+		} while ($row = $this->fetch());
+
+		unset($x);
+		return $data;
+	}
+
+
+	/**
+	 * @deprecated
+	 */
+	private function oldFetchAssoc($assoc)
+	{
+		$this->seek(0);
+		$row = $this->fetch();
+		if (!$row) {
+			return [];  // empty result set
+		}
+
+		$data = null;
+		$assoc = explode(',', $assoc);
+
+		// strip leading = and @
+		$leaf = '@';  // gap
+		$last = count($assoc) - 1;
+		while ($assoc[$last] === '=' || $assoc[$last] === '@') {
+			$leaf = $assoc[$last];
+			unset($assoc[$last]);
+			$last--;
+
+			if ($last < 0) {
+				$assoc[] = '#';
+				break;
+			}
+		}
+
+		do {
+			$x = &$data;
+
+			foreach ($assoc as $i => $as) {
+				if ($as === '#') { // indexed-array node
+					$x = &$x[];
+
+				} elseif ($as === '=') { // "record" node
+					if ($x === null) {
+						$x = $row->toArray();
+						$x = &$x[$assoc[$i + 1]];
+						$x = null; // prepare child node
+					} else {
+						$x = &$x[$assoc[$i + 1]];
+					}
+
+				} elseif ($as === '@') { // "object" node
+					if ($x === null) {
+						$x = clone $row;
+						$x = &$x->{$assoc[$i + 1]};
+						$x = null; // prepare child node
+					} else {
+						$x = &$x->{$assoc[$i + 1]};
+					}
+
+				} else { // associative-array node
+					$x = &$x[$row->$as];
+				}
+			}
+
+			if ($x === null) { // build leaf
+				if ($leaf === '=') {
+					$x = $row->toArray();
+				} else {
+					$x = $row;
+				}
+			}
+		} while ($row = $this->fetch());
+
+		unset($x);
+		return $data;
+	}
+
+
+	/**
+	 * Fetches all records from table like $key => $value pairs.
+	 * @param  string  associative key
+	 * @param  string  value
+	 * @return array
+	 * @throws \InvalidArgumentException
+	 */
+	final public function fetchPairs($key = null, $value = null)
+	{
+		$this->seek(0);
+		$row = $this->fetch();
+		if (!$row) {
+			return [];  // empty result set
+		}
+
+		$data = [];
+
+		if ($value === null) {
+			if ($key !== null) {
+				throw new \InvalidArgumentException('Either none or both columns must be specified.');
+			}
+
+			// autodetect
+			$tmp = array_keys($row->toArray());
+			$key = $tmp[0];
+			if (count($row) < 2) { // indexed-array
+				do {
+					$data[] = $row[$key];
+				} while ($row = $this->fetch());
+				return $data;
+			}
+
+			$value = $tmp[1];
+
+		} else {
+			if (!property_exists($row, $value)) {
+				throw new \InvalidArgumentException("Unknown value column '$value'.");
+			}
+
+			if ($key === null) { // indexed-array
+				do {
+					$data[] = $row[$value];
+				} while ($row = $this->fetch());
+				return $data;
+			}
+
+			if (!property_exists($row, $key)) {
+				throw new \InvalidArgumentException("Unknown key column '$key'.");
+			}
+		}
+
+		do {
+			$data[(string) $row[$key]] = $row[$value];
+		} while ($row = $this->fetch());
+
+		return $data;
+	}
+
+
+	/********************* column types ****************d*g**/
+
+
+	/**
+	 * Autodetect column types.
+	 * @return void
+	 */
+	private function detectTypes()
+	{
+		$cache = Helpers::getTypeCache();
+		try {
+			foreach ($this->getResultDriver()->getResultColumns() as $col) {
+				$this->types[$col['name']] = isset($col['type']) ? $col['type'] : $cache->{$col['nativetype']};
+			}
+		} catch (NotSupportedException $e) {
+		}
+	}
+
+
+	/**
+	 * Converts values to specified type and format.
+	 * @param  array
+	 * @return void
+	 */
+	private function normalize(array &$row)
+	{
+		foreach ($this->types as $key => $type) {
+			if (!isset($row[$key])) { // null
+				continue;
+			}
+			$value = $row[$key];
+			if ($type === Type::TEXT) {
+				$row[$key] = (string) $value;
+
+			} elseif ($type === Type::INTEGER) {
+				$row[$key] = is_float($tmp = $value * 1)
+					? (is_string($value) ? $value : (int) $value)
+					: $tmp;
+
+			} elseif ($type === Type::FLOAT) {
+				$value = ltrim((string) $value, '0');
+				$p = strpos($value, '.');
+				if ($p !== false) {
+					$value = rtrim(rtrim($value, '0'), '.');
+				}
+				if ($value === '' || $value[0] === '.') {
+					$value = '0' . $value;
+				}
+				$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
+					? $float
+					: $value;
+
+			} elseif ($type === Type::BOOL) {
+				$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
+
+			} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
+				if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ...
+					$value = new DateTime($value);
+					$row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]);
+				} else {
+					$row[$key] = null;
+				}
+
+			} elseif ($type === Type::TIME_INTERVAL) {
+				preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
+				$row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
+				$row[$key]->invert = (int) (bool) $m[1];
+
+			} elseif ($type === Type::BINARY) {
+				$row[$key] = $this->getResultDriver()->unescapeBinary($value);
+			}
+		}
+	}
+
+
+	/**
+	 * Define column type.
+	 * @param  string  column
+	 * @param  string  type (use constant Type::*)
+	 * @return self
+	 */
+	final public function setType($col, $type)
+	{
+		$this->types[$col] = $type;
+		return $this;
+	}
+
+
+	/**
+	 * Returns column type.
+	 * @return string
+	 */
+	final public function getType($col)
+	{
+		return isset($this->types[$col]) ? $this->types[$col] : null;
+	}
+
+
+	/**
+	 * Sets date format.
+	 * @param  string
+	 * @param  string|null  format
+	 * @return self
+	 */
+	final public function setFormat($type, $format)
+	{
+		$this->formats[$type] = $format;
+		return $this;
+	}
+
+
+	/**
+	 * Returns data format.
+	 * @return string|null
+	 */
+	final public function getFormat($type)
+	{
+		return isset($this->formats[$type]) ? $this->formats[$type] : null;
+	}
+
+
+	/********************* meta info ****************d*g**/
+
+
+	/**
+	 * Returns a meta information about the current result set.
+	 * @return Reflection\Result
+	 */
+	public function getInfo()
+	{
+		if ($this->meta === null) {
+			$this->meta = new Reflection\Result($this->getResultDriver());
+		}
+		return $this->meta;
+	}
+
+
+	/**
+	 * @return Reflection\Column[]
+	 */
+	final public function getColumns()
+	{
+		return $this->getInfo()->getColumns();
+	}
+
+
+	/********************* misc tools ****************d*g**/
+
+
+	/**
+	 * Displays complete result set as HTML or text table for debug purposes.
+	 * @return void
+	 */
+	final public function dump()
+	{
+		echo Helpers::dump($this);
+	}
+}

+ 107 - 0
api/vendor/dibi/dibi/src/Dibi/ResultIterator.php

@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * External result set iterator.
+ *
+ * This can be returned by Result::getIterator() method or using foreach
+ * <code>
+ * $result = dibi::query('SELECT * FROM table');
+ * foreach ($result as $row) {
+ *    print_r($row);
+ * }
+ * unset($result);
+ * </code>
+ */
+class ResultIterator implements \Iterator, \Countable
+{
+	use Strict;
+
+	/** @var Result */
+	private $result;
+
+	/** @var mixed */
+	private $row;
+
+	/** @var int */
+	private $pointer;
+
+
+	/**
+	 * @param  Result
+	 */
+	public function __construct(Result $result)
+	{
+		$this->result = $result;
+	}
+
+
+	/**
+	 * Rewinds the iterator to the first element.
+	 * @return void
+	 */
+	public function rewind()
+	{
+		$this->pointer = 0;
+		$this->result->seek(0);
+		$this->row = $this->result->fetch();
+	}
+
+
+	/**
+	 * Returns the key of the current element.
+	 * @return mixed
+	 */
+	public function key()
+	{
+		return $this->pointer;
+	}
+
+
+	/**
+	 * Returns the current element.
+	 * @return mixed
+	 */
+	public function current()
+	{
+		return $this->row;
+	}
+
+
+	/**
+	 * Moves forward to next element.
+	 * @return void
+	 */
+	public function next()
+	{
+		$this->row = $this->result->fetch();
+		$this->pointer++;
+	}
+
+
+	/**
+	 * Checks if there is a current element after calls to rewind() or next().
+	 * @return bool
+	 */
+	public function valid()
+	{
+		return !empty($this->row);
+	}
+
+
+	/**
+	 * Required by the Countable interface.
+	 * @return int
+	 */
+	public function count()
+	{
+		return $this->result->getRowCount();
+	}
+}

+ 93 - 0
api/vendor/dibi/dibi/src/Dibi/Row.php

@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * Result set single row.
+ */
+class Row implements \ArrayAccess, \IteratorAggregate, \Countable
+{
+	public function __construct($arr)
+	{
+		foreach ($arr as $k => $v) {
+			$this->$k = $v;
+		}
+	}
+
+
+	public function toArray()
+	{
+		return (array) $this;
+	}
+
+
+	/**
+	 * Converts value to DateTime object.
+	 * @param  string key
+	 * @param  string format
+	 * @return \DateTime
+	 */
+	public function asDateTime($key, $format = null)
+	{
+		$time = $this[$key];
+		if (!$time instanceof DateTime) {
+			if (!$time || substr((string) $time, 0, 3) === '000') { // '', null, false, '0000-00-00', ...
+				return null;
+			}
+			$time = new DateTime($time);
+		}
+		return $format === null ? $time : $time->format($format);
+	}
+
+
+	public function __get($key)
+	{
+		$hint = Helpers::getSuggestion(array_keys((array) $this), $key);
+		trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE);
+	}
+
+
+	/********************* interfaces ArrayAccess, Countable & IteratorAggregate ****************d*g**/
+
+
+	final public function count()
+	{
+		return count((array) $this);
+	}
+
+
+	final public function getIterator()
+	{
+		return new \ArrayIterator($this);
+	}
+
+
+	final public function offsetSet($nm, $val)
+	{
+		$this->$nm = $val;
+	}
+
+
+	final public function offsetGet($nm)
+	{
+		return $this->$nm;
+	}
+
+
+	final public function offsetExists($nm)
+	{
+		return isset($this->$nm);
+	}
+
+
+	final public function offsetUnset($nm)
+	{
+		unset($this->$nm);
+	}
+}

+ 151 - 0
api/vendor/dibi/dibi/src/Dibi/Strict.php

@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+use ReflectionClass;
+use ReflectionMethod;
+use ReflectionProperty;
+
+
+/**
+ * Better OOP experience.
+ */
+trait Strict
+{
+	/** @var array [method => [type => callback]] */
+	private static $extMethods;
+
+
+	/**
+	 * Call to undefined method.
+	 * @throws \LogicException
+	 */
+	public function __call($name, $args)
+	{
+		if ($cb = self::extensionMethod(get_class($this) . '::' . $name)) { // back compatiblity
+			array_unshift($args, $this);
+			return call_user_func_array($cb, $args);
+		}
+		$class = method_exists($this, $name) ? 'parent' : get_class($this);
+		$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
+		$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
+		throw new \LogicException("Call to undefined method $class::$name()$hint");
+	}
+
+
+	/**
+	 * Call to undefined static method.
+	 * @throws \LogicException
+	 */
+	public static function __callStatic($name, $args)
+	{
+		$rc = new ReflectionClass(get_called_class());
+		$items = array_intersect($rc->getMethods(ReflectionMethod::IS_PUBLIC), $rc->getMethods(ReflectionMethod::IS_STATIC));
+		$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.';
+		throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
+	}
+
+
+	/**
+	 * Access to undeclared property.
+	 * @throws \LogicException
+	 */
+	public function &__get($name)
+	{
+		if ((method_exists($this, $m = 'get' . $name) || method_exists($this, $m = 'is' . $name))
+			&& (new ReflectionMethod($this, $m))->isPublic()
+		) { // back compatiblity
+			$ret = $this->$m();
+			return $ret;
+		}
+		$rc = new ReflectionClass($this);
+		$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
+		$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
+		throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
+	}
+
+
+	/**
+	 * Access to undeclared property.
+	 * @throws \LogicException
+	 */
+	public function __set($name, $value)
+	{
+		$rc = new ReflectionClass($this);
+		$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC));
+		$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.';
+		throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
+	}
+
+
+	/**
+	 * @return bool
+	 */
+	public function __isset($name)
+	{
+		return false;
+	}
+
+
+	/**
+	 * Access to undeclared property.
+	 * @throws \LogicException
+	 */
+	public function __unset($name)
+	{
+		$class = get_class($this);
+		throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
+	}
+
+
+	/**
+	 * @param  string  method name
+	 * @param  callable
+	 * @return mixed
+	 */
+	public static function extensionMethod($name, $callback = null)
+	{
+		if (strpos($name, '::') === false) {
+			$class = get_called_class();
+		} else {
+			list($class, $name) = explode('::', $name);
+			$class = (new ReflectionClass($class))->getName();
+		}
+
+		if (self::$extMethods === null) { // for backwards compatibility
+			$list = get_defined_functions();
+			foreach ($list['user'] as $fce) {
+				$pair = explode('_prototype_', $fce);
+				if (count($pair) === 2) {
+					trigger_error("Extension method defined as $fce() is deprecated, use $class::extensionMethod('$name', ...).", E_USER_DEPRECATED);
+					self::$extMethods[$pair[1]][(new ReflectionClass($pair[0]))->getName()] = $fce;
+					self::$extMethods[$pair[1]][''] = null;
+				}
+			}
+		}
+
+		$list = &self::$extMethods[strtolower($name)];
+		if ($callback === null) { // getter
+			$cache = &$list[''][$class];
+			if (isset($cache)) {
+				return $cache;
+			}
+
+			foreach ([$class] + class_parents($class) + class_implements($class) as $cl) {
+				if (isset($list[$cl])) {
+					return $cache = $list[$cl];
+				}
+			}
+			return $cache = false;
+
+		} else { // setter
+			$list[$class] = $callback;
+			$list[''] = null;
+		}
+	}
+}

+ 619 - 0
api/vendor/dibi/dibi/src/Dibi/Translator.php

@@ -0,0 +1,619 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * dibi SQL translator.
+ */
+final class Translator
+{
+	use Strict;
+
+	/** @var Connection */
+	private $connection;
+
+	/** @var Driver */
+	private $driver;
+
+	/** @var int */
+	private $cursor = 0;
+
+	/** @var array */
+	private $args;
+
+	/** @var string[] */
+	private $errors;
+
+	/** @var bool */
+	private $comment = false;
+
+	/** @var int */
+	private $ifLevel = 0;
+
+	/** @var int */
+	private $ifLevelStart = 0;
+
+	/** @var int */
+	private $limit;
+
+	/** @var int */
+	private $offset;
+
+	/** @var HashMap */
+	private $identifiers;
+
+
+	public function __construct(Connection $connection)
+	{
+		$this->connection = $connection;
+		$this->driver = $connection->getDriver();
+		$this->identifiers = new HashMap([$this, 'delimite']);
+	}
+
+
+	/**
+	 * Generates SQL. Can be called only once.
+	 * @param  array
+	 * @return string
+	 * @throws Exception
+	 */
+	public function translate(array $args)
+	{
+		$args = array_values($args);
+		while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
+			$args = array_values($args[0]);
+		}
+		$this->args = $args;
+
+		$commandIns = null;
+		$lastArr = null;
+		$cursor = &$this->cursor;
+		$comment = &$this->comment;
+
+		// iterate
+		$sql = [];
+		while ($cursor < count($this->args)) {
+			$arg = $this->args[$cursor];
+			$cursor++;
+
+			// simple string means SQL
+			if (is_string($arg)) {
+				// speed-up - is regexp required?
+				$toSkip = strcspn($arg, '`[\'":%?');
+
+				if (strlen($arg) === $toSkip) { // needn't be translated
+					$sql[] = $arg;
+				} else {
+					$sql[] = substr($arg, 0, $toSkip)
+/*
+					. preg_replace_callback('/
+					(?=[`[\'":%?])                    ## speed-up
+					(?:
+						`(.+?)`|                     ## 1) `identifier`
+						\[(.+?)\]|                   ## 2) [identifier]
+						(\')((?:\'\'|[^\'])*)\'|     ## 3,4) 'string'
+						(")((?:""|[^"])*)"|          ## 5,6) "string"
+						(\'|")|                      ## 7) lone quote
+						:(\S*?:)([a-zA-Z0-9._]?)|    ## 8,9) :substitution:
+						%([a-zA-Z~][a-zA-Z0-9~]{0,5})|## 10) modifier
+						(\?)                         ## 11) placeholder
+					)/xs',
+*/                  // note: this can change $this->args & $this->cursor & ...
+					. preg_replace_callback('/(?=[`[\'":%?])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?)|%([a-zA-Z~][a-zA-Z0-9~]{0,5})|(\?))/s',
+							[$this, 'cb'],
+							substr($arg, $toSkip)
+					);
+					if (preg_last_error()) {
+						throw new PcreException;
+					}
+				}
+				continue;
+			}
+
+			if ($comment) {
+				$sql[] = '...';
+				continue;
+			}
+
+			if ($arg instanceof \Traversable) {
+				$arg = iterator_to_array($arg);
+			}
+
+			if (is_array($arg) && is_string(key($arg))) {
+				// associative array -> autoselect between SET or VALUES & LIST
+				if ($commandIns === null) {
+					$commandIns = strtoupper(substr(ltrim($this->args[0]), 0, 6));
+					$commandIns = $commandIns === 'INSERT' || $commandIns === 'REPLAC';
+					$sql[] = $this->formatValue($arg, $commandIns ? 'v' : 'a');
+				} else {
+					if ($lastArr === $cursor - 1) {
+						$sql[] = ',';
+					}
+					$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
+				}
+				$lastArr = $cursor;
+				continue;
+			}
+
+			// default processing
+			$sql[] = $this->formatValue($arg, false);
+		} // while
+
+
+		if ($comment) {
+			$sql[] = '*/';
+		}
+
+		$sql = implode(' ', $sql);
+
+		if ($this->errors) {
+			throw new Exception('SQL translate error: ' . trim(reset($this->errors), '*'), 0, $sql);
+		}
+
+		// apply limit
+		if ($this->limit !== null || $this->offset !== null) {
+			$this->driver->applyLimit($sql, $this->limit, $this->offset);
+		}
+
+		return $sql;
+	}
+
+
+	/**
+	 * Apply modifier to single value.
+	 * @param  mixed
+	 * @param  string
+	 * @return string
+	 */
+	public function formatValue($value, $modifier)
+	{
+		if ($this->comment) {
+			return '...';
+		}
+
+		// array processing (with or without modifier)
+		if ($value instanceof \Traversable) {
+			$value = iterator_to_array($value);
+		}
+
+		if (is_array($value)) {
+			$vx = $kx = [];
+			switch ($modifier) {
+				case 'and':
+				case 'or':  // key=val AND key IS NULL AND ...
+					if (empty($value)) {
+						return '1=1';
+					}
+
+					foreach ($value as $k => $v) {
+						if (is_string($k)) {
+							$pair = explode('%', $k, 2); // split into identifier & modifier
+							$k = $this->identifiers->{$pair[0]} . ' ';
+							if (!isset($pair[1])) {
+								$v = $this->formatValue($v, false);
+								$vx[] = $k . ($v === 'NULL' ? 'IS ' : '= ') . $v;
+
+							} elseif ($pair[1] === 'ex') {
+								$vx[] = $k . $this->formatValue($v, 'ex');
+
+							} else {
+								$v = $this->formatValue($v, $pair[1]);
+								if ($pair[1] === 'l' || $pair[1] === 'in') {
+									$op = 'IN ';
+								} elseif (strpos($pair[1], 'like') !== false) {
+									$op = 'LIKE ';
+								} elseif ($v === 'NULL') {
+									$op = 'IS ';
+								} else {
+									$op = '= ';
+								}
+								$vx[] = $k . $op . $v;
+							}
+
+						} else {
+							$vx[] = $this->formatValue($v, 'ex');
+						}
+					}
+					return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
+
+				case 'n':  // key, key, ... identifier names
+					foreach ($value as $k => $v) {
+						if (is_string($k)) {
+							$vx[] = $this->identifiers->$k . (empty($v) ? '' : ' AS ' . $this->driver->escapeIdentifier($v));
+						} else {
+							$pair = explode('%', $v, 2); // split into identifier & modifier
+							$vx[] = $this->identifiers->{$pair[0]};
+						}
+					}
+					return implode(', ', $vx);
+
+
+				case 'a': // key=val, key=val, ...
+					foreach ($value as $k => $v) {
+						$pair = explode('%', $k, 2); // split into identifier & modifier
+						$vx[] = $this->identifiers->{$pair[0]} . '='
+							. $this->formatValue($v, isset($pair[1]) ? $pair[1] : (is_array($v) ? 'ex' : false));
+					}
+					return implode(', ', $vx);
+
+
+				case 'in':// replaces scalar %in modifier!
+				case 'l': // (val, val, ...)
+					foreach ($value as $k => $v) {
+						$pair = explode('%', (string) $k, 2); // split into identifier & modifier
+						$vx[] = $this->formatValue($v, isset($pair[1]) ? $pair[1] : (is_array($v) ? 'ex' : false));
+					}
+					return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
+
+
+				case 'v': // (key, key, ...) VALUES (val, val, ...)
+					foreach ($value as $k => $v) {
+						$pair = explode('%', $k, 2); // split into identifier & modifier
+						$kx[] = $this->identifiers->{$pair[0]};
+						$vx[] = $this->formatValue($v, isset($pair[1]) ? $pair[1] : (is_array($v) ? 'ex' : false));
+					}
+					return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
+
+				case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
+					foreach ($value as $k => $v) {
+						if (is_array($v)) {
+							if (isset($proto)) {
+								if ($proto !== array_keys($v)) {
+									return $this->errors[] = '**Multi-insert array "' . $k . '" is different**';
+								}
+							} else {
+								$proto = array_keys($v);
+							}
+						} else {
+							return $this->errors[] = '**Unexpected type ' . (is_object($v) ? get_class($v) : gettype($v)) . '**';
+						}
+
+						$pair = explode('%', $k, 2); // split into identifier & modifier
+						$kx[] = $this->identifiers->{$pair[0]};
+						foreach ($v as $k2 => $v2) {
+							$vx[$k2][] = $this->formatValue($v2, isset($pair[1]) ? $pair[1] : (is_array($v2) ? 'ex' : false));
+						}
+					}
+					foreach ($vx as $k => $v) {
+						$vx[$k] = '(' . implode(', ', $v) . ')';
+					}
+					return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
+
+				case 'by': // key ASC, key DESC
+					foreach ($value as $k => $v) {
+						if (is_array($v)) {
+							$vx[] = $this->formatValue($v, 'ex');
+						} elseif (is_string($k)) {
+							$v = (is_string($v) && strncasecmp($v, 'd', 1)) || $v > 0 ? 'ASC' : 'DESC';
+							$vx[] = $this->identifiers->$k . ' ' . $v;
+						} else {
+							$vx[] = $this->identifiers->$v;
+						}
+					}
+					return implode(', ', $vx);
+
+				case 'ex':
+				case 'sql':
+					return call_user_func_array([$this->connection, 'translate'], $value);
+
+				default:  // value, value, value - all with the same modifier
+					foreach ($value as $v) {
+						$vx[] = $this->formatValue($v, $modifier);
+					}
+					return implode(', ', $vx);
+			}
+		}
+
+
+		// with modifier procession
+		if ($modifier) {
+			if ($value !== null && !is_scalar($value)) {  // array is already processed
+				if ($value instanceof Literal && ($modifier === 'sql' || $modifier === 'SQL')) {
+					$modifier = 'SQL';
+				} elseif (($value instanceof \DateTime || $value instanceof \DateTimeInterface) && ($modifier === 'd' || $modifier === 't' || $modifier === 'dt')) {
+					// continue
+				} else {
+					$type = is_object($value) ? get_class($value) : gettype($value);
+					return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
+				}
+			}
+
+			switch ($modifier) {
+				case 's':  // string
+					return $value === null ? 'NULL' : $this->driver->escapeText((string) $value);
+
+				case 'bin':// binary
+					return $value === null ? 'NULL' : $this->driver->escapeBinary($value);
+
+				case 'b':  // boolean
+					return $value === null ? 'NULL' : $this->driver->escapeBool($value);
+
+				case 'sN': // string or null
+				case 'sn':
+					return $value == '' ? 'NULL' : $this->driver->escapeText((string) $value); // notice two equal signs
+
+				case 'in': // deprecated
+					trigger_error('Modifier %in is deprecated, use %iN.', E_USER_DEPRECATED);
+					// break omitted
+				case 'iN': // signed int or null
+					if ($value == '') {
+						$value = null;
+					}
+					// break omitted
+				case 'i':  // signed int
+				case 'u':  // unsigned int, ignored
+					if ($value === null) {
+						return 'NULL';
+					} elseif (is_string($value)) {
+						if (preg_match('#[+-]?\d++(?:e\d+)?\z#A', $value)) {
+							return $value; // support for long numbers - keep them unchanged
+						} elseif (substr($value, 1, 1) === 'x' && is_numeric($value)) {
+							trigger_error('Support for hex strings has been deprecated.', E_USER_DEPRECATED);
+							return (string) hexdec($value);
+						} else {
+							throw new Exception("Expected number, '$value' given.");
+						}
+					} else {
+						return (string) (int) $value;
+					}
+					// break omitted
+				case 'f':  // float
+					if ($value === null) {
+						return 'NULL';
+					} elseif (is_string($value)) {
+						if (is_numeric($value) && substr($value, 1, 1) !== 'x') {
+							return $value; // support for long numbers - keep them unchanged
+						} else {
+							throw new Exception("Expected number, '$value' given.");
+						}
+					} else {
+						return rtrim(rtrim(number_format($value + 0, 10, '.', ''), '0'), '.');
+					}
+					// break omitted
+				case 'd':  // date
+				case 't':  // datetime
+				case 'dt': // datetime
+					if ($value === null) {
+						return 'NULL';
+					} else {
+						return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value);
+					}
+					// break omitted
+				case 'by':
+				case 'n':  // composed identifier name
+					return $this->identifiers->$value;
+
+				case 'N':  // identifier name
+					return $this->driver->escapeIdentifier($value);
+
+				case 'ex':
+				case 'sql': // preserve as dibi-SQL  (TODO: leave only %ex)
+					$value = (string) $value;
+					// speed-up - is regexp required?
+					$toSkip = strcspn($value, '`[\'":');
+					if (strlen($value) !== $toSkip) {
+						$value = substr($value, 0, $toSkip)
+						. preg_replace_callback(
+							'/(?=[`[\'":])(?:`(.+?)`|\[(.+?)\]|(\')((?:\'\'|[^\'])*)\'|(")((?:""|[^"])*)"|(\'|")|:(\S*?:)([a-zA-Z0-9._]?))/s',
+							[$this, 'cb'],
+							substr($value, $toSkip)
+						);
+						if (preg_last_error()) {
+							throw new PcreException;
+						}
+					}
+					return $value;
+
+				case 'SQL': // preserve as real SQL (TODO: rename to %sql)
+					return (string) $value;
+
+				case 'like~':  // LIKE string%
+					return $this->driver->escapeLike($value, 1);
+
+				case '~like':  // LIKE %string
+					return $this->driver->escapeLike($value, -1);
+
+				case '~like~': // LIKE %string%
+					return $this->driver->escapeLike($value, 0);
+
+				case 'and':
+				case 'or':
+				case 'a':
+				case 'l':
+				case 'v':
+					$type = gettype($value);
+					return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
+
+				default:
+					return $this->errors[] = "**Unknown or unexpected modifier %$modifier**";
+			}
+		}
+
+
+		// without modifier procession
+		if (is_string($value)) {
+			return $this->driver->escapeText($value);
+
+		} elseif (is_int($value)) {
+			return (string) $value;
+
+		} elseif (is_float($value)) {
+			return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
+
+		} elseif (is_bool($value)) {
+			return $this->driver->escapeBool($value);
+
+		} elseif ($value === null) {
+			return 'NULL';
+
+		} elseif ($value instanceof \DateTime || $value instanceof \DateTimeInterface) {
+			return $this->driver->escapeDateTime($value);
+
+		} elseif ($value instanceof Literal) {
+			return (string) $value;
+
+		} elseif ($value instanceof Expression) {
+			return call_user_func_array([$this->connection, 'translate'], $value->getValues());
+
+		} else {
+			$type = is_object($value) ? get_class($value) : gettype($value);
+			return $this->errors[] = "**Unexpected $type**";
+		}
+	}
+
+
+	/**
+	 * PREG callback from translate() or formatValue().
+	 * @param  array
+	 * @return string
+	 */
+	private function cb($matches)
+	{
+		//    [1] => `ident`
+		//    [2] => [ident]
+		//    [3] => '
+		//    [4] => string
+		//    [5] => "
+		//    [6] => string
+		//    [7] => lone-quote
+		//    [8] => substitution
+		//    [9] => substitution flag
+		//    [10] => modifier (when called from self::translate())
+		//    [11] => placeholder (when called from self::translate())
+
+
+		if (!empty($matches[11])) { // placeholder
+			$cursor = &$this->cursor;
+
+			if ($cursor >= count($this->args)) {
+				return $this->errors[] = '**Extra placeholder**';
+			}
+
+			$cursor++;
+			return $this->formatValue($this->args[$cursor - 1], false);
+		}
+
+		if (!empty($matches[10])) { // modifier
+			$mod = $matches[10];
+			$cursor = &$this->cursor;
+
+			if ($cursor >= count($this->args) && $mod !== 'else' && $mod !== 'end') {
+				return $this->errors[] = "**Extra modifier %$mod**";
+			}
+
+			if ($mod === 'if') {
+				$this->ifLevel++;
+				$cursor++;
+				if (!$this->comment && !$this->args[$cursor - 1]) {
+					// open comment
+					$this->ifLevelStart = $this->ifLevel;
+					$this->comment = true;
+					return '/*';
+				}
+				return '';
+
+			} elseif ($mod === 'else') {
+				if ($this->ifLevelStart === $this->ifLevel) {
+					$this->ifLevelStart = 0;
+					$this->comment = false;
+					return '*/';
+				} elseif (!$this->comment) {
+					$this->ifLevelStart = $this->ifLevel;
+					$this->comment = true;
+					return '/*';
+				}
+
+			} elseif ($mod === 'end') {
+				$this->ifLevel--;
+				if ($this->ifLevelStart === $this->ifLevel + 1) {
+					// close comment
+					$this->ifLevelStart = 0;
+					$this->comment = false;
+					return '*/';
+				}
+				return '';
+
+			} elseif ($mod === 'ex') { // array expansion
+				array_splice($this->args, $cursor, 1, $this->args[$cursor]);
+				return '';
+
+			} elseif ($mod === 'lmt') { // apply limit
+				$arg = $this->args[$cursor++];
+				if ($arg === null) {
+				} elseif ($this->comment) {
+					return "(limit $arg)";
+				} else {
+					$this->limit = Helpers::intVal($arg);
+				}
+				return '';
+
+			} elseif ($mod === 'ofs') { // apply offset
+				$arg = $this->args[$cursor++];
+				if ($arg === null) {
+				} elseif ($this->comment) {
+					return "(offset $arg)";
+				} else {
+					$this->offset = Helpers::intVal($arg);
+				}
+				return '';
+
+			} else { // default processing
+				$cursor++;
+				return $this->formatValue($this->args[$cursor - 1], $mod);
+			}
+		}
+
+		if ($this->comment) {
+			return '...';
+		}
+
+		if ($matches[1]) { // SQL identifiers: `ident`
+			return $this->identifiers->{$matches[1]};
+
+		} elseif ($matches[2]) { // SQL identifiers: [ident]
+			return $this->identifiers->{$matches[2]};
+
+		} elseif ($matches[3]) { // SQL strings: '...'
+			return $this->driver->escapeText(str_replace("''", "'", $matches[4]));
+
+		} elseif ($matches[5]) { // SQL strings: "..."
+			return $this->driver->escapeText(str_replace('""', '"', $matches[6]));
+
+		} elseif ($matches[7]) { // string quote
+			return $this->errors[] = '**Alone quote**';
+		}
+
+		if ($matches[8]) { // SQL identifier substitution
+			$m = substr($matches[8], 0, -1);
+			$m = $this->connection->getSubstitutes()->$m;
+			return $matches[9] == '' ? $this->formatValue($m, false) : $m . $matches[9]; // value or identifier
+		}
+
+		throw new \Exception('this should be never executed');
+	}
+
+
+	/**
+	 * Apply substitutions to indentifier and delimites it.
+	 * @param  string indentifier
+	 * @return string
+	 * @internal
+	 */
+	public function delimite($value)
+	{
+		$value = $this->connection->substitute($value);
+		$parts = explode('.', $value);
+		foreach ($parts as &$v) {
+			if ($v !== '*') {
+				$v = $this->driver->escapeIdentifier($v);
+			}
+		}
+		return implode('.', $parts);
+	}
+}

+ 32 - 0
api/vendor/dibi/dibi/src/Dibi/Type.php

@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * Data types.
+ */
+class Type
+{
+	const
+		TEXT = 's', // as 'string'
+		BINARY = 'bin',
+		BOOL = 'b',
+		INTEGER = 'i',
+		FLOAT = 'f',
+		DATE = 'd',
+		DATETIME = 'dt',
+		TIME = 't',
+		TIME_INTERVAL = 'ti';
+
+
+	final public function __construct()
+	{
+		throw new \LogicException('Cannot instantiate static class ' . __CLASS__);
+	}
+}

+ 450 - 0
api/vendor/dibi/dibi/src/Dibi/dibi.php

@@ -0,0 +1,450 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+use Dibi\Type;
+
+
+/**
+ * This class is static container class for creating DB objects and
+ * store connections info.
+ */
+class dibi
+{
+	use Dibi\Strict;
+
+	const
+		AFFECTED_ROWS = 'a',
+		IDENTIFIER = 'n';
+
+	/** version */
+	const
+		VERSION = '3.1.0',
+		REVISION = 'released on 2017-09-25';
+
+	/** sorting order */
+	const
+		ASC = 'ASC',
+		DESC = 'DESC';
+
+	/** @deprecated */
+	const
+		TEXT = Type::TEXT,
+		BINARY = Type::BINARY,
+		BOOL = Type::BOOL,
+		INTEGER = Type::INTEGER,
+		FLOAT = Type::FLOAT,
+		DATE = Type::DATE,
+		DATETIME = Type::DATETIME,
+		TIME = Type::TIME,
+		FIELD_TEXT = Type::TEXT,
+		FIELD_BINARY = Type::BINARY,
+		FIELD_BOOL = Type::BOOL,
+		FIELD_INTEGER = Type::INTEGER,
+		FIELD_FLOAT = Type::FLOAT,
+		FIELD_DATE = Type::DATE,
+		FIELD_DATETIME = Type::DATETIME,
+		FIELD_TIME = Type::TIME;
+
+	/** @var string  Last SQL command @see dibi::query() */
+	public static $sql;
+
+	/** @var int  Elapsed time for last query */
+	public static $elapsedTime;
+
+	/** @var int  Elapsed time for all queries */
+	public static $totalTime;
+
+	/** @var int  Number or queries */
+	public static $numOfQueries = 0;
+
+	/** @var string  Default dibi driver */
+	public static $defaultDriver = 'mysqli';
+
+	/** @var Dibi\Connection[]  Connection registry storage for DibiConnection objects */
+	private static $registry = [];
+
+	/** @var Dibi\Connection  Current connection */
+	private static $connection;
+
+
+	/**
+	 * Static class - cannot be instantiated.
+	 */
+	final public function __construct()
+	{
+		throw new LogicException('Cannot instantiate static class ' . get_class($this));
+	}
+
+
+	/********************* connections handling ****************d*g**/
+
+
+	/**
+	 * Creates a new Connection object and connects it to specified database.
+	 * @param  mixed   connection parameters
+	 * @param  string  connection name
+	 * @return Dibi\Connection
+	 * @throws Dibi\Exception
+	 */
+	public static function connect($config = [], $name = '0')
+	{
+		return self::$connection = self::$registry[$name] = new Dibi\Connection($config, $name);
+	}
+
+
+	/**
+	 * Disconnects from database (doesn't destroy Connection object).
+	 * @return void
+	 */
+	public static function disconnect()
+	{
+		self::getConnection()->disconnect();
+	}
+
+
+	/**
+	 * Returns true when connection was established.
+	 * @return bool
+	 */
+	public static function isConnected()
+	{
+		return (self::$connection !== null) && self::$connection->isConnected();
+	}
+
+
+	/**
+	 * Retrieve active connection.
+	 * @param  string   connection registy name
+	 * @return Dibi\Connection
+	 * @throws Dibi\Exception
+	 */
+	public static function getConnection($name = null)
+	{
+		if ($name === null) {
+			if (self::$connection === null) {
+				throw new Dibi\Exception('Dibi is not connected to database.');
+			}
+
+			return self::$connection;
+		}
+
+		if (!isset(self::$registry[$name])) {
+			throw new Dibi\Exception("There is no connection named '$name'.");
+		}
+
+		return self::$registry[$name];
+	}
+
+
+	/**
+	 * Sets connection.
+	 * @param  Dibi\Connection
+	 * @return Dibi\Connection
+	 */
+	public static function setConnection(Dibi\Connection $connection)
+	{
+		return self::$connection = $connection;
+	}
+
+
+	/**
+	 * @deprecated
+	 */
+	public static function activate($name)
+	{
+		trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
+		self::$connection = self::getConnection($name);
+	}
+
+
+	/********************* monostate for active connection ****************d*g**/
+
+
+	/**
+	 * Generates and executes SQL query - Monostate for Dibi\Connection::query().
+	 * @param  array|mixed      one or more arguments
+	 * @return Dibi\Result|int   result set or number of affected rows
+	 * @throws Dibi\Exception
+	 */
+	public static function query($args)
+	{
+		$args = func_get_args();
+		return self::getConnection()->query($args);
+	}
+
+
+	/**
+	 * Executes the SQL query - Monostate for Dibi\Connection::nativeQuery().
+	 * @param  string           SQL statement.
+	 * @return Dibi\Result|int   result set or number of affected rows
+	 */
+	public static function nativeQuery($sql)
+	{
+		return self::getConnection()->nativeQuery($sql);
+	}
+
+
+	/**
+	 * Generates and prints SQL query - Monostate for Dibi\Connection::test().
+	 * @param  array|mixed  one or more arguments
+	 * @return bool
+	 */
+	public static function test($args)
+	{
+		$args = func_get_args();
+		return self::getConnection()->test($args);
+	}
+
+
+	/**
+	 * Generates and returns SQL query as DataSource - Monostate for Dibi\Connection::test().
+	 * @param  array|mixed      one or more arguments
+	 * @return Dibi\DataSource
+	 */
+	public static function dataSource($args)
+	{
+		$args = func_get_args();
+		return self::getConnection()->dataSource($args);
+	}
+
+
+	/**
+	 * Executes SQL query and fetch result - Monostate for Dibi\Connection::query() & fetch().
+	 * @param  array|mixed    one or more arguments
+	 * @return Dibi\Row
+	 * @throws Dibi\Exception
+	 */
+	public static function fetch($args)
+	{
+		$args = func_get_args();
+		return self::getConnection()->query($args)->fetch();
+	}
+
+
+	/**
+	 * Executes SQL query and fetch results - Monostate for Dibi\Connection::query() & fetchAll().
+	 * @param  array|mixed    one or more arguments
+	 * @return Dibi\Row[]
+	 * @throws Dibi\Exception
+	 */
+	public static function fetchAll($args)
+	{
+		$args = func_get_args();
+		return self::getConnection()->query($args)->fetchAll();
+	}
+
+
+	/**
+	 * Executes SQL query and fetch first column - Monostate for Dibi\Connection::query() & fetchSingle().
+	 * @param  array|mixed    one or more arguments
+	 * @return mixed
+	 * @throws Dibi\Exception
+	 */
+	public static function fetchSingle($args)
+	{
+		$args = func_get_args();
+		return self::getConnection()->query($args)->fetchSingle();
+	}
+
+
+	/**
+	 * Executes SQL query and fetch pairs - Monostate for Dibi\Connection::query() & fetchPairs().
+	 * @param  array|mixed    one or more arguments
+	 * @return array
+	 * @throws Dibi\Exception
+	 */
+	public static function fetchPairs($args)
+	{
+		$args = func_get_args();
+		return self::getConnection()->query($args)->fetchPairs();
+	}
+
+
+	/**
+	 * Gets the number of affected rows.
+	 * Monostate for Dibi\Connection::getAffectedRows()
+	 * @return int  number of rows
+	 * @throws Dibi\Exception
+	 */
+	public static function getAffectedRows()
+	{
+		return self::getConnection()->getAffectedRows();
+	}
+
+
+	/**
+	 * @deprecated
+	 */
+	public static function affectedRows()
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use getAffectedRows()', E_USER_DEPRECATED);
+		return self::getConnection()->getAffectedRows();
+	}
+
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * Monostate for Dibi\Connection::getInsertId()
+	 * @param  string     optional sequence name
+	 * @return int
+	 * @throws Dibi\Exception
+	 */
+	public static function getInsertId($sequence = null)
+	{
+		return self::getConnection()->getInsertId($sequence);
+	}
+
+
+	/**
+	 * @deprecated
+	 */
+	public static function insertId($sequence = null)
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use getInsertId()', E_USER_DEPRECATED);
+		return self::getConnection()->getInsertId($sequence);
+	}
+
+
+	/**
+	 * Begins a transaction - Monostate for Dibi\Connection::begin().
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public static function begin($savepoint = null)
+	{
+		self::getConnection()->begin($savepoint);
+	}
+
+
+	/**
+	 * Commits statements in a transaction - Monostate for Dibi\Connection::commit($savepoint = null).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public static function commit($savepoint = null)
+	{
+		self::getConnection()->commit($savepoint);
+	}
+
+
+	/**
+	 * Rollback changes in a transaction - Monostate for Dibi\Connection::rollback().
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws Dibi\Exception
+	 */
+	public static function rollback($savepoint = null)
+	{
+		self::getConnection()->rollback($savepoint);
+	}
+
+
+	/**
+	 * Gets a information about the current database - Monostate for Dibi\Connection::getDatabaseInfo().
+	 * @return Dibi\Reflection\Database
+	 */
+	public static function getDatabaseInfo()
+	{
+		return self::getConnection()->getDatabaseInfo();
+	}
+
+
+	/**
+	 * Import SQL dump from file - extreme fast!
+	 * @param  string  filename
+	 * @return int  count of sql commands
+	 */
+	public static function loadFile($file)
+	{
+		return Dibi\Helpers::loadFromFile(self::getConnection(), $file);
+	}
+
+
+	/********************* fluent SQL builders ****************d*g**/
+
+
+	/**
+	 * @return Dibi\Fluent
+	 */
+	public static function command()
+	{
+		return self::getConnection()->command();
+	}
+
+
+	/**
+	 * @param  mixed    column name
+	 * @return Dibi\Fluent
+	 */
+	public static function select($args)
+	{
+		$args = func_get_args();
+		return call_user_func_array([self::getConnection(), 'select'], $args);
+	}
+
+
+	/**
+	 * @param  string   table
+	 * @param  array
+	 * @return Dibi\Fluent
+	 */
+	public static function update($table, $args)
+	{
+		return self::getConnection()->update($table, $args);
+	}
+
+
+	/**
+	 * @param  string   table
+	 * @param  array
+	 * @return Dibi\Fluent
+	 */
+	public static function insert($table, $args)
+	{
+		return self::getConnection()->insert($table, $args);
+	}
+
+
+	/**
+	 * @param  string   table
+	 * @return Dibi\Fluent
+	 */
+	public static function delete($table)
+	{
+		return self::getConnection()->delete($table);
+	}
+
+
+	/********************* substitutions ****************d*g**/
+
+
+	/**
+	 * Returns substitution hashmap - Monostate for Dibi\Connection::getSubstitutes().
+	 * @return Dibi\HashMap
+	 */
+	public static function getSubstitutes()
+	{
+		return self::getConnection()->getSubstitutes();
+	}
+
+
+	/********************* misc tools ****************d*g**/
+
+
+	/**
+	 * Prints out a syntax highlighted version of the SQL command or Result.
+	 * @param  string|Result
+	 * @param  bool  return output instead of printing it?
+	 * @return string
+	 */
+	public static function dump($sql = null, $return = false)
+	{
+		return Dibi\Helpers::dump($sql, $return);
+	}
+}

+ 153 - 0
api/vendor/dibi/dibi/src/Dibi/exceptions.php

@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * dibi common exception.
+ */
+class Exception extends \Exception
+{
+	/** @var string|null */
+	private $sql;
+
+
+	/**
+	 * Construct a dibi exception.
+	 * @param  string  Message describing the exception
+	 * @param  mixed
+	 * @param  string  SQL command
+	 */
+	public function __construct($message = '', $code = 0, $sql = null)
+	{
+		parent::__construct($message);
+		$this->code = $code;
+		$this->sql = $sql;
+	}
+
+
+	/**
+	 * @return string  The SQL passed to the constructor
+	 */
+	final public function getSql()
+	{
+		return $this->sql;
+	}
+
+
+	/**
+	 * @return string  string represenation of exception with SQL command
+	 */
+	public function __toString()
+	{
+		return parent::__toString() . ($this->sql ? "\nSQL: " . $this->sql : '');
+	}
+}
+
+
+/**
+ * database server exception.
+ */
+class DriverException extends Exception
+{
+}
+
+
+/**
+ * PCRE exception.
+ */
+class PcreException extends Exception
+{
+	public function __construct($message = '%msg.')
+	{
+		static $messages = [
+			PREG_INTERNAL_ERROR => 'Internal error',
+			PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
+			PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
+			PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
+			5 => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point', // PREG_BAD_UTF8_OFFSET_ERROR
+		];
+		$code = preg_last_error();
+		parent::__construct(str_replace('%msg', isset($messages[$code]) ? $messages[$code] : 'Unknown error', $message), $code);
+	}
+}
+
+
+class NotImplementedException extends Exception
+{
+}
+
+
+class NotSupportedException extends Exception
+{
+}
+
+
+/**
+ * Database procedure exception.
+ */
+class ProcedureException extends Exception
+{
+	/** @var string */
+	protected $severity;
+
+
+	/**
+	 * Construct the exception.
+	 * @param  string  Message describing the exception
+	 * @param  int     Some code
+	 * @param  string SQL command
+	 */
+	public function __construct($message = null, $code = 0, $severity = null, $sql = null)
+	{
+		parent::__construct($message, (int) $code, $sql);
+		$this->severity = $severity;
+	}
+
+
+	/**
+	 * Gets the exception severity.
+	 * @return string
+	 */
+	public function getSeverity()
+	{
+		$this->severity;
+	}
+}
+
+
+/**
+ * Base class for all constraint violation related exceptions.
+ */
+class ConstraintViolationException extends DriverException
+{
+}
+
+
+/**
+ * Exception for a foreign key constraint violation.
+ */
+class ForeignKeyConstraintViolationException extends ConstraintViolationException
+{
+}
+
+
+/**
+ * Exception for a NOT NULL constraint violation.
+ */
+class NotNullConstraintViolationException extends ConstraintViolationException
+{
+}
+
+
+/**
+ * Exception for a unique constraint violation.
+ */
+class UniqueConstraintViolationException extends ConstraintViolationException
+{
+}

+ 242 - 0
api/vendor/dibi/dibi/src/Dibi/interfaces.php

@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+namespace Dibi;
+
+
+/**
+ * Provides an interface between a dataset and data-aware components.
+ */
+interface IDataSource extends \Countable, \IteratorAggregate
+{
+	//function \IteratorAggregate::getIterator();
+	//function \Countable::count();
+}
+
+
+/**
+ * dibi driver interface.
+ */
+interface Driver
+{
+
+	/**
+	 * Connects to a database.
+	 * @param  array
+	 * @return void
+	 * @throws Exception
+	 */
+	function connect(array &$config);
+
+	/**
+	 * Disconnects from a database.
+	 * @return void
+	 * @throws Exception
+	 */
+	function disconnect();
+
+	/**
+	 * Internal: Executes the SQL query.
+	 * @param  string      SQL statement.
+	 * @return ResultDriver|null
+	 * @throws DriverException
+	 */
+	function query($sql);
+
+	/**
+	 * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
+	 * @return int|false  number of rows or false on error
+	 */
+	function getAffectedRows();
+
+	/**
+	 * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
+	 * @return int|false  int on success or false on failure
+	 */
+	function getInsertId($sequence);
+
+	/**
+	 * Begins a transaction (if supported).
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws DriverException
+	 */
+	function begin($savepoint = null);
+
+	/**
+	 * Commits statements in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws DriverException
+	 */
+	function commit($savepoint = null);
+
+	/**
+	 * Rollback changes in a transaction.
+	 * @param  string  optional savepoint name
+	 * @return void
+	 * @throws DriverException
+	 */
+	function rollback($savepoint = null);
+
+	/**
+	 * Returns the connection resource.
+	 * @return mixed
+	 */
+	function getResource();
+
+	/**
+	 * Returns the connection reflector.
+	 * @return Reflector
+	 */
+	function getReflector();
+
+	/**
+	 * Encodes data for use in a SQL statement.
+	 * @param  string    value
+	 * @return string    encoded value
+	 */
+	function escapeText($value);
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	function escapeBinary($value);
+
+	/**
+	 * @param  string
+	 * @return string
+	 */
+	function escapeIdentifier($value);
+
+	/**
+	 * @param  bool
+	 * @return string
+	 */
+	function escapeBool($value);
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	function escapeDate($value);
+
+	/**
+	 * @param  \DateTime|\DateTimeInterface|string|int
+	 * @return string
+	 */
+	function escapeDateTime($value);
+
+	/**
+	 * Encodes string for use in a LIKE statement.
+	 * @param  string
+	 * @param  int
+	 * @return string
+	 */
+	function escapeLike($value, $pos);
+
+	/**
+	 * Injects LIMIT/OFFSET to the SQL query.
+	 * @param  string
+	 * @param  int|null
+	 * @param  int|null
+	 * @return void
+	 */
+	function applyLimit(&$sql, $limit, $offset);
+}
+
+
+/**
+ * dibi result set driver interface.
+ */
+interface ResultDriver
+{
+
+	/**
+	 * Returns the number of rows in a result set.
+	 * @return int
+	 */
+	function getRowCount();
+
+	/**
+	 * Moves cursor position without fetching row.
+	 * @param  int      the 0-based cursor pos to seek to
+	 * @return bool     true on success, false if unable to seek to specified record
+	 * @throws Exception
+	 */
+	function seek($row);
+
+	/**
+	 * Fetches the row at current position and moves the internal cursor to the next position.
+	 * @param  bool     true for associative array, false for numeric
+	 * @return array    array on success, nonarray if no next record
+	 * @internal
+	 */
+	function fetch($type);
+
+	/**
+	 * Frees the resources allocated for this result set.
+	 * @param  resource  result set resource
+	 * @return void
+	 */
+	function free();
+
+	/**
+	 * Returns metadata for all columns in a result set.
+	 * @return array of {name, nativetype [, table, fullname, (int) size, (bool) nullable, (mixed) default, (bool) autoincrement, (array) vendor ]}
+	 */
+	function getResultColumns();
+
+	/**
+	 * Returns the result set resource.
+	 * @return mixed
+	 */
+	function getResultResource();
+
+	/**
+	 * Decodes data from result set.
+	 * @param  string
+	 * @return string
+	 */
+	function unescapeBinary($value);
+}
+
+
+/**
+ * dibi driver reflection.
+ */
+interface Reflector
+{
+
+	/**
+	 * Returns list of tables.
+	 * @return array of {name [, (bool) view ]}
+	 */
+	function getTables();
+
+	/**
+	 * Returns metadata for all columns in a table.
+	 * @param  string
+	 * @return array of {name, nativetype [, table, fullname, (int) size, (bool) nullable, (mixed) default, (bool) autoincrement, (array) vendor ]}
+	 */
+	function getColumns($table);
+
+	/**
+	 * Returns metadata for all indexes in a table.
+	 * @param  string
+	 * @return array of {name, (array of names) columns [, (bool) unique, (bool) primary ]}
+	 */
+	function getIndexes($table);
+
+	/**
+	 * Returns metadata for all foreign keys in a table.
+	 * @param  string
+	 * @return array
+	 */
+	function getForeignKeys($table);
+}

+ 140 - 0
api/vendor/dibi/dibi/src/loader.php

@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * This file is part of the "dibi" - smart database abstraction layer.
+ * Copyright (c) 2005 David Grudl (https://davidgrudl.com)
+ */
+
+
+if (PHP_VERSION_ID < 50404) {
+	throw new Exception('Dibi requires PHP 5.4.4 or newer.');
+}
+
+
+spl_autoload_register(function ($class) {
+	static $map = [
+		'dibi' => 'dibi.php',
+		'Dibi\Bridges\Nette\DibiExtension22' => 'Bridges/Nette/DibiExtension22.php',
+		'Dibi\Bridges\Tracy\Panel' => 'Bridges/Tracy/Panel.php',
+		'Dibi\Connection' => 'Connection.php',
+		'Dibi\DataSource' => 'DataSource.php',
+		'Dibi\DateTime' => 'DateTime.php',
+		'Dibi\Driver' => 'interfaces.php',
+		'Dibi\DriverException' => 'exceptions.php',
+		'Dibi\Drivers\FirebirdDriver' => 'Drivers/FirebirdDriver.php',
+		'Dibi\Drivers\SqlsrvDriver' => 'Drivers/SqlsrvDriver.php',
+		'Dibi\Drivers\SqlsrvReflector' => 'Drivers/SqlsrvReflector.php',
+		'Dibi\Drivers\MsSqlDriver' => 'Drivers/MsSqlDriver.php',
+		'Dibi\Drivers\MsSqlReflector' => 'Drivers/MsSqlReflector.php',
+		'Dibi\Drivers\MySqlDriver' => 'Drivers/MySqlDriver.php',
+		'Dibi\Drivers\MySqliDriver' => 'Drivers/MySqliDriver.php',
+		'Dibi\Drivers\MySqlReflector' => 'Drivers/MySqlReflector.php',
+		'Dibi\Drivers\OdbcDriver' => 'Drivers/OdbcDriver.php',
+		'Dibi\Drivers\OracleDriver' => 'Drivers/OracleDriver.php',
+		'Dibi\Drivers\PdoDriver' => 'Drivers/PdoDriver.php',
+		'Dibi\Drivers\PostgreDriver' => 'Drivers/PostgreDriver.php',
+		'Dibi\Drivers\Sqlite3Driver' => 'Drivers/Sqlite3Driver.php',
+		'Dibi\Drivers\SqliteReflector' => 'Drivers/SqliteReflector.php',
+		'Dibi\Event' => 'Event.php',
+		'Dibi\Exception' => 'exceptions.php',
+		'Dibi\Fluent' => 'Fluent.php',
+		'Dibi\HashMap' => 'HashMap.php',
+		'Dibi\HashMapBase' => 'HashMap.php',
+		'Dibi\Helpers' => 'Helpers.php',
+		'Dibi\IDataSource' => 'interfaces.php',
+		'Dibi\Literal' => 'Literal.php',
+		'Dibi\Loggers\FileLogger' => 'Loggers/FileLogger.php',
+		'Dibi\Loggers\FirePhpLogger' => 'Loggers/FirePhpLogger.php',
+		'Dibi\NotImplementedException' => 'exceptions.php',
+		'Dibi\NotSupportedException' => 'exceptions.php',
+		'Dibi\PcreException' => 'exceptions.php',
+		'Dibi\ProcedureException' => 'exceptions.php',
+		'Dibi\Reflection\Column' => 'Reflection/Column.php',
+		'Dibi\Reflection\Database' => 'Reflection/Database.php',
+		'Dibi\Reflection\ForeignKey' => 'Reflection/ForeignKey.php',
+		'Dibi\Reflection\Index' => 'Reflection/Index.php',
+		'Dibi\Reflection\Result' => 'Reflection/Result.php',
+		'Dibi\Reflection\Table' => 'Reflection/Table.php',
+		'Dibi\Reflector' => 'interfaces.php',
+		'Dibi\Result' => 'Result.php',
+		'Dibi\ResultDriver' => 'interfaces.php',
+		'Dibi\ResultIterator' => 'ResultIterator.php',
+		'Dibi\Row' => 'Row.php',
+		'Dibi\Strict' => 'Strict.php',
+		'Dibi\Translator' => 'Translator.php',
+		'Dibi\Type' => 'Type.php',
+	], $old2new = [
+		'Dibi' => 'dibi.php',
+		'DibiColumnInfo' => 'Dibi\Reflection\Column',
+		'DibiConnection' => 'Dibi\Connection',
+		'DibiDatabaseInfo' => 'Dibi\Reflection\Database',
+		'DibiDataSource' => 'Dibi\DataSource',
+		'DibiDateTime' => 'Dibi\DateTime',
+		'DibiDriverException' => 'Dibi\DriverException',
+		'DibiEvent' => 'Dibi\Event',
+		'DibiException' => 'Dibi\Exception',
+		'DibiFileLogger' => 'Dibi\Loggers\FileLogger',
+		'DibiFirebirdDriver' => 'Dibi\Drivers\FirebirdDriver',
+		'DibiFirePhpLogger' => 'Dibi\Loggers\FirePhpLogger',
+		'DibiFluent' => 'Dibi\Fluent',
+		'DibiForeignKeyInfo' => 'Dibi\Reflection\ForeignKey',
+		'DibiHashMap' => 'Dibi\HashMap',
+		'DibiHashMapBase' => 'Dibi\HashMapBase',
+		'DibiIndexInfo' => 'Dibi\Reflection\Index',
+		'DibiLiteral' => 'Dibi\Literal',
+		'DibiMsSql2005Driver' => 'Dibi\Drivers\SqlsrvDriver',
+		'DibiMsSql2005Reflector' => 'Dibi\Drivers\SqlsrvReflector',
+		'DibiMsSqlDriver' => 'Dibi\Drivers\MsSqlDriver',
+		'DibiMsSqlReflector' => 'Dibi\Drivers\MsSqlReflector',
+		'DibiMySqlDriver' => 'Dibi\Drivers\MySqlDriver',
+		'DibiMySqliDriver' => 'Dibi\Drivers\MySqliDriver',
+		'DibiMySqlReflector' => 'Dibi\Drivers\MySqlReflector',
+		'DibiNotImplementedException' => 'Dibi\NotImplementedException',
+		'DibiNotSupportedException' => 'Dibi\NotSupportedException',
+		'DibiOdbcDriver' => 'Dibi\Drivers\OdbcDriver',
+		'DibiOracleDriver' => 'Dibi\Drivers\OracleDriver',
+		'DibiPcreException' => 'Dibi\PcreException',
+		'DibiPdoDriver' => 'Dibi\Drivers\PdoDriver',
+		'DibiPostgreDriver' => 'Dibi\Drivers\PostgreDriver',
+		'DibiProcedureException' => 'Dibi\ProcedureException',
+		'DibiResult' => 'Dibi\Result',
+		'DibiResultInfo' => 'Dibi\Reflection\Result',
+		'DibiResultIterator' => 'Dibi\ResultIterator',
+		'DibiRow' => 'Dibi\Row',
+		'DibiSqlite3Driver' => 'Dibi\Drivers\Sqlite3Driver',
+		'DibiSqliteReflector' => 'Dibi\Drivers\SqliteReflector',
+		'DibiTableInfo' => 'Dibi\Reflection\Table',
+		'DibiTranslator' => 'Dibi\Translator',
+		'IDataSource' => 'Dibi\IDataSource',
+		'IDibiDriver' => 'Dibi\Driver',
+		'IDibiReflector' => 'Dibi\Reflector',
+		'IDibiResultDriver' => 'Dibi\ResultDriver',
+		'Dibi\Drivers\MsSql2005Driver' => 'Dibi\Drivers\SqlsrvDriver',
+		'Dibi\Drivers\MsSql2005Reflector' => 'Dibi\Drivers\SqlsrvReflector',
+	];
+	if (isset($map[$class])) {
+		require __DIR__ . '/Dibi/' . $map[$class];
+	} elseif (isset($old2new[$class])) {
+		class_alias($old2new[$class], $class);
+	}
+});
+
+
+// preload for compatiblity
+array_map('class_exists', [
+	'DibiConnection',
+	'DibiDateTime',
+	'DibiDriverException',
+	'DibiEvent',
+	'DibiException',
+	'DibiFluent',
+	'DibiLiteral',
+	'DibiNotImplementedException',
+	'DibiNotSupportedException',
+	'DibiPcreException',
+	'DibiProcedureException',
+	'DibiResult',
+	'DibiRow',
+	'IDataSource',
+	'IDibiDriver',
+]);

+ 2 - 0
api/vendor/lcobucci/jwt/.gitignore

@@ -0,0 +1,2 @@
+vendor
+phpunit.xml

+ 56 - 0
api/vendor/lcobucci/jwt/.scrutinizer.yml

@@ -0,0 +1,56 @@
+build:
+    environment:
+        mysql: false
+        postgresql: false
+        redis: false
+        rabbitmq: false
+        php:
+            version: 5.6
+tools:
+    php_sim: true
+    php_pdepend: true
+    php_analyzer: true
+    php_changetracking: true
+    php_code_sniffer:
+        config:
+            standard: "PSR2"
+    php_mess_detector: true
+checks:
+    php:
+        code_rating: true
+        duplication: true
+        argument_type_checks: true
+        assignment_of_null_return: true
+        avoid_conflicting_incrementers: true
+        avoid_useless_overridden_methods: true
+        catch_class_exists: true
+        closure_use_modifiable: true
+        closure_use_not_conflicting: true
+        deprecated_code_usage: true
+        method_calls_on_non_object: true
+        missing_arguments: true
+        no_duplicate_arguments: true
+        no_non_implemented_abstract_methods: true
+        no_property_on_interface: true
+        parameter_non_unique: true
+        precedence_in_conditions: true
+        precedence_mistakes: true
+        require_php_tag_first: true
+        security_vulnerabilities: true
+        sql_injection_vulnerabilities: true
+        too_many_arguments: true
+        unreachable_code: true
+        unused_methods: true
+        unused_parameters: true
+        unused_properties: true
+        unused_variables: true
+        use_statement_alias_conflict: true
+        useless_calls: true
+        variable_existence: true
+        verify_access_scope_valid: true
+        verify_argument_usable_as_reference: true
+        verify_property_names: true
+
+filter:
+    excluded_paths:
+        - test/*

+ 15 - 0
api/vendor/lcobucci/jwt/.travis.yml

@@ -0,0 +1,15 @@
+language: php
+php:
+  - 5.5
+  - 5.6
+  - 7.0
+  - hhvm
+  - hhvm-nightly
+
+matrix:
+  allow_failures:
+    - php: hhvm-nightly
+
+before_script:
+  - composer selfupdate
+  - composer install --prefer-dist -o

+ 27 - 0
api/vendor/lcobucci/jwt/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2014-2015, Luís Otávio Cobucci Oblonczyk
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the {organization} nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio