├── .gitignore ├── support ├── flex_forms_error.png ├── install.css ├── sdk_cloud_storage_server_feeds.php ├── bb_feeds_api_base.php.template ├── csdb │ ├── db_mysql_lite.php │ ├── db_sqlite_lite.php │ ├── db_pgsql_lite.php │ ├── db_oci_lite.php │ ├── db_pgsql.php │ ├── db_sqlite.php │ ├── db_oci.php │ └── db.php ├── flex_forms.css ├── str_basics.php ├── sdk_cloud_storage_server_api_base.php ├── utf8.php ├── random.php └── utf_utils.php ├── README.md └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | db/* 2 | config.php 3 | future_cms_api.php 4 | -------------------------------------------------------------------------------- /support/flex_forms_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/barebones-cms-api/master/support/flex_forms_error.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Barebones CMS API 2 | ================= 3 | 4 | This is the Barebones CMS API development repository for contributors. Changes made here propagate to the [Barebones CMS release distribution](https://github.com/cubiclesoft/barebones-cms). 5 | 6 | Deploying the software in this repository directly to production servers is not supported. 7 | 8 | Barebones CMS is MIT or LGPL, your choice. 9 | 10 | Contributing 11 | ------------ 12 | 13 | Open an issue on the issue tracker OR fork the project, make your changes, and start a pull request. 14 | 15 | For changes that only affect a couple of lines of code, using the issue tracker is easier. 16 | -------------------------------------------------------------------------------- /support/install.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | background-color: #FFFFFF; 5 | color: #222222; 6 | font-family: Verdana, Arial, Helvetica, sans-serif; 7 | } 8 | 9 | #headerwrap { 10 | padding: 0.5em; 11 | background-color: #222222; 12 | color: #FEFEFE; 13 | font-size: 1.7em; 14 | -webkit-box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); 15 | -moz-box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); 16 | box-shadow: 0px 3px 3px 0px rgba(0,0,0,0.15); 17 | } 18 | 19 | #header { 20 | max-width: 800px; 21 | margin-left: auto; 22 | margin-right: auto; 23 | } 24 | 25 | #contentwrap { 26 | padding: 0em 0.5em; 27 | } 28 | 29 | #content { 30 | max-width: 800px; 31 | margin-left: auto; 32 | margin-right: auto; 33 | } 34 | 35 | #footerwrap { 36 | margin-top: 3.0em; 37 | border-top: 1px dashed #CCCCCC; 38 | padding: 1.0em 0.5em 1.0em; 39 | } 40 | 41 | #footer { 42 | max-width: 800px; 43 | margin-left: auto; 44 | margin-right: auto; 45 | } -------------------------------------------------------------------------------- /support/sdk_cloud_storage_server_feeds.php: -------------------------------------------------------------------------------- 1 | apiprefix = "/feeds/v1"; 16 | } 17 | 18 | public function Notify($name, $type, $id, $data = array(), $queue = false, $queuesize = -1) 19 | { 20 | $options = array( 21 | "name" => $name, 22 | "type" => $type, 23 | "id" => (string)$id, 24 | "data" => $data, 25 | "queuesize" => $queuesize 26 | ); 27 | 28 | if ($queue !== false) $options["queue"] = (int)$queue; 29 | 30 | return $this->RunAPI("POST", "notify", $options); 31 | } 32 | 33 | public function InitMonitor() 34 | { 35 | return $this->InitWebSocket(); 36 | } 37 | 38 | public static function AddMonitor($ws, $sequence, $name, $filters = array()) 39 | { 40 | $options = array( 41 | "api_method" => "GET", 42 | "api_path" => $this->apiprefix . "/monitor", 43 | "api_sequence" => $sequence, 44 | "name" => $name, 45 | "filters" => $filters 46 | ); 47 | 48 | return $ws->Write(json_encode($options), WebSocket::FRAMETYPE_TEXT); 49 | } 50 | 51 | public function CreateGuest($name, $notify, $monitor, $expires) 52 | { 53 | $options = array( 54 | "name" => $name, 55 | "notify" => (int)(bool)$notify, 56 | "monitor" => (int)(bool)$monitor, 57 | "expires" => (int)$expires 58 | ); 59 | 60 | return $this->RunAPI("POST", "guest/create", $options); 61 | } 62 | 63 | public function GetGuestList() 64 | { 65 | return $this->RunAPI("GET", "guest/list"); 66 | } 67 | 68 | public function DeleteGuest($id) 69 | { 70 | return $this->RunAPI("DELETE", "guest/delete/" . $id); 71 | } 72 | } 73 | ?> -------------------------------------------------------------------------------- /support/bb_feeds_api_base.php.template: -------------------------------------------------------------------------------- 1 | db = new $dbclassname($config["db_select"] . ":" . $config["db_master_dsn"], ($config["db_login"] ? $config["db_master_user"] : false), ($config["db_login"] ? $config["db_master_pass"] : false)); 18 | else $this->db = new $dbclassname($config["db_select"] . ":" . $config["db_dsn"], ($config["db_login"] ? $config["db_user"] : false), ($config["db_login"] ? $config["db_pass"] : false)); 19 | 20 | $this->db->Query("USE", $config["db_name"]); 21 | 22 | $dbprefix = $config["db_table_prefix"]; 23 | $this->api_db_assets = $dbprefix . "assets"; 24 | } 25 | 26 | public function AddItems(&$itemsinfo) 27 | { 28 | if (!isset($itemsinfo["lastts"])) $itemsinfo["lastts"] = time(); 29 | if (!isset($itemsinfo["canadd"])) $itemsinfo["canadd"] = true; 30 | 31 | if ($itemsinfo["canadd"]) 32 | { 33 | $prevts = $itemsinfo["lastts"]; 34 | 35 | $nextpub = $this->db->GetOne("SELECT", array( 36 | "MIN(publish)", 37 | "FROM" => "?", 38 | "WHERE" => "publish > ?", 39 | ), $this->api_db_assets, $prevts); 40 | 41 | if ($nextpub) 42 | { 43 | $itemsinfo["lastts"] = (int)$nextpub; 44 | 45 | if (!isset($itemsinfo["items"][$itemsinfo["lastts"]])) $itemsinfo["items"][$itemsinfo["lastts"]] = array(); 46 | 47 | $result = $this->db->Query("SELECT", array( 48 | "*", 49 | "FROM" => "?", 50 | "WHERE" => "publish = ?" 51 | ), $this->api_db_assets, $nextpub); 52 | 53 | while ($row = $result->NextRow()) 54 | { 55 | $row->info = json_decode($row->info, true); 56 | 57 | $itemsinfo["items"][$itemsinfo["lastts"]][$row->id] = array("type" => "insert", "data" => array("asset" => (array)$row, "prevasset" => false)); 58 | } 59 | } 60 | 61 | 62 | $nextremove = $this->db->GetOne("SELECT", array( 63 | "MIN(unpublish)", 64 | "FROM" => "?", 65 | "WHERE" => "unpublish > ?", 66 | ), $this->api_db_assets, $prevts); 67 | 68 | if ($nextremove) 69 | { 70 | $ts = (int)$nextremove; 71 | if ($ts <= $itemsinfo["lastts"]) 72 | { 73 | $itemsinfo["lastts"] = $ts; 74 | 75 | if (!isset($itemsinfo["items"][$itemsinfo["lastts"]])) $itemsinfo["items"][$itemsinfo["lastts"]] = array(); 76 | 77 | $result = $this->db->Query("SELECT", array( 78 | "*", 79 | "FROM" => "?", 80 | "WHERE" => "unpublish = ?" 81 | ), $this->api_db_assets, $nextremove); 82 | 83 | while ($row = $result->NextRow()) 84 | { 85 | $row->info = json_decode($row->info, true); 86 | 87 | $itemsinfo["items"][$itemsinfo["lastts"]][$row->id] = array("type" => "delete", "data" => array("asset" => (array)$row, "prevasset" => false)); 88 | } 89 | } 90 | } 91 | 92 | $this->canadd = false; 93 | } 94 | } 95 | 96 | public function RemovedItems(&$itemsinfo, $ts, $sent) 97 | { 98 | if (!$itemsinfo["canadd"] && $ts === $itemsinfo["lastts"]) 99 | { 100 | if (!$sent) $itemsinfo["lastts"]--; 101 | $itemsinfo["canadd"] = true; 102 | } 103 | } 104 | } 105 | ?> -------------------------------------------------------------------------------- /support/csdb/db_mysql_lite.php: -------------------------------------------------------------------------------- 1 | Query("SET", "NAMES 'utf8mb4'"); 25 | } 26 | 27 | public function GetInsertID($name = null) 28 | { 29 | return $this->GetOne("SELECT", array("LAST_INSERT_ID()")); 30 | } 31 | 32 | public function QuoteIdentifier($str) 33 | { 34 | return "`" . str_replace(array("`", "?"), array("``", ""), $str) . "`"; 35 | } 36 | 37 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 38 | { 39 | switch ($cmd) 40 | { 41 | case "SELECT": 42 | { 43 | $supported = array( 44 | "PRECOLUMN" => array("DISTINCT" => "bool", "HIGH_PRIORITY" => "bool", "SUBQUERIES" => true), 45 | "FROM" => array("SUBQUERIES" => true), 46 | "WHERE" => array("SUBQUERIES" => true), 47 | "GROUP BY" => true, 48 | "HAVING" => true, 49 | "ORDER BY" => true, 50 | "LIMIT" => ", " 51 | ); 52 | 53 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 54 | } 55 | case "INSERT": 56 | { 57 | $supported = array( 58 | "PREINTO" => array("LOW_PRIORITY" => "bool", "DELAYED" => "bool", "HIGH_PRIORITY" => "bool", "IGNORE" => "bool"), 59 | "SELECT" => true, 60 | "BULKINSERT" => true 61 | ); 62 | 63 | return $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 64 | } 65 | case "UPDATE": 66 | { 67 | $supported = array( 68 | "PRETABLE" => array("LOW_PRIORITY" => "bool", "IGNORE" => "bool"), 69 | "WHERE" => array("SUBQUERIES" => true), 70 | "ORDER BY" => true, 71 | "LIMIT" => ", " 72 | ); 73 | 74 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 75 | } 76 | case "DELETE": 77 | { 78 | $supported = array( 79 | "PREFROM" => array("LOW_PRIORITY" => "bool", "QUICK" => "bool", "IGNORE" => "bool"), 80 | "WHERE" => array("SUBQUERIES" => true), 81 | "ORDER BY" => true, 82 | "LIMIT" => ", " 83 | ); 84 | 85 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 86 | } 87 | case "SET": 88 | { 89 | $sql = "SET " . $queryinfo; 90 | 91 | return array("success" => true); 92 | } 93 | case "USE": 94 | { 95 | $sql = "USE " . $this->QuoteIdentifier($queryinfo); 96 | 97 | return array("success" => true); 98 | } 99 | case "TRUNCATE TABLE": 100 | { 101 | $master = true; 102 | 103 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 104 | 105 | return array("success" => true); 106 | } 107 | } 108 | 109 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 110 | } 111 | } 112 | ?> -------------------------------------------------------------------------------- /support/csdb/db_sqlite_lite.php: -------------------------------------------------------------------------------- 1 | dbprefix = ""; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | } 27 | 28 | public function GetInsertID($name = null) 29 | { 30 | return $this->GetOne("SELECT", array("LAST_INSERT_ROWID()")); 31 | } 32 | 33 | public function QuoteIdentifier($str) 34 | { 35 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 36 | } 37 | 38 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 39 | { 40 | switch ($cmd) 41 | { 42 | case "SELECT": 43 | { 44 | $supported = array( 45 | "DBPREFIX" => $this->dbprefix, 46 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 47 | "FROM" => array("SUBQUERIES" => true), 48 | "WHERE" => array("SUBQUERIES" => true), 49 | "GROUP BY" => true, 50 | "HAVING" => true, 51 | "ORDER BY" => true, 52 | "LIMIT" => ", " 53 | ); 54 | 55 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 56 | } 57 | case "INSERT": 58 | { 59 | $supported = array( 60 | "DBPREFIX" => $this->dbprefix, 61 | "PREINTO" => array("LOW_PRIORITY" => "bool", "DELAYED" => "bool", "HIGH_PRIORITY" => "bool", "IGNORE" => "bool"), 62 | "SELECT" => true, 63 | "BULKINSERT" => true, 64 | "BULKINSERTLIMIT" => 900, 65 | ); 66 | 67 | return $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 68 | } 69 | case "UPDATE": 70 | { 71 | $supported = array( 72 | "DBPREFIX" => $this->dbprefix, 73 | "PRETABLE" => array("LOW_PRIORITY" => "bool", "IGNORE" => "bool"), 74 | "WHERE" => array("SUBQUERIES" => true), 75 | "ORDER BY" => true, 76 | "LIMIT" => ", " 77 | ); 78 | 79 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 80 | } 81 | case "DELETE": 82 | { 83 | $supported = array( 84 | "DBPREFIX" => $this->dbprefix, 85 | "PREFROM" => array("LOW_PRIORITY" => "bool", "QUICK" => "bool", "IGNORE" => "bool"), 86 | "WHERE" => array("SUBQUERIES" => true), 87 | "ORDER BY" => true, 88 | "LIMIT" => ", " 89 | ); 90 | 91 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 92 | } 93 | case "SET": 94 | { 95 | return array("success" => false, "errorcode" => "skip_sql_query"); 96 | } 97 | case "USE": 98 | { 99 | $this->dbprefix = $this->GetDBPrefix($queryinfo); 100 | 101 | return array("success" => false, "errorcode" => "skip_sql_query"); 102 | } 103 | case "TRUNCATE TABLE": 104 | { 105 | $supported = array( 106 | "DBPREFIX" => $this->dbprefix, 107 | "PREFROM" => array() 108 | ); 109 | 110 | $queryinfo = array($queryinfo[0]); 111 | 112 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 113 | } 114 | } 115 | 116 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 117 | } 118 | 119 | private function GetDBPrefix($str) 120 | { 121 | $str = preg_replace('/\s+/', "_", trim(str_replace("_", " ", $str))); 122 | 123 | return ($str != "" ? $str . "__" : ""); 124 | } 125 | } 126 | ?> -------------------------------------------------------------------------------- /support/flex_forms.css: -------------------------------------------------------------------------------- 1 | .ff_formwrap .ff_formwrapinner .formitem { 2 | margin-top: 1.0em; 3 | } 4 | 5 | .ff_formwrap .ff_formwrapinner .formitemtitle { 6 | color: #222222; 7 | margin-bottom: 0.2em; 8 | font-weight: bold; 9 | } 10 | 11 | .ff_formwrap .ff_formwrapinner .formfields > hr { 12 | border: 0 none; 13 | border-top: 1px dashed #BBBBBB; 14 | } 15 | 16 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input, .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea, .ff_formwrap .ff_formwrapinner .formitem .formitemdata select { 17 | outline: none; 18 | } 19 | 20 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input.text { 21 | box-sizing: border-box; 22 | width: 100%; 23 | font-size: 0.9em; 24 | padding: 0.3em; 25 | border: 1px solid #BBBBBB; 26 | } 27 | 28 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input.text:focus { 29 | border: 1px solid #888888; 30 | } 31 | 32 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata input.text:hover { 33 | border: 1px solid #888888; 34 | } 35 | 36 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea { 37 | box-sizing: border-box; 38 | width: 100%; 39 | font-size: 0.9em; 40 | padding: 0.3em; 41 | border: 1px solid #BBBBBB; 42 | resize: vertical; 43 | } 44 | 45 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea:focus { 46 | border: 1px solid #888888; 47 | } 48 | 49 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata textarea:hover { 50 | border: 1px solid #888888; 51 | } 52 | 53 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata .radioitemwrap { 54 | margin-left: 1.7em; 55 | text-indent: -1.7em; 56 | margin-bottom: 0.3em; 57 | } 58 | 59 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata .checkboxitemwrap { 60 | margin-left: 1.7em; 61 | text-indent: -1.7em; 62 | margin-bottom: 0.3em; 63 | } 64 | 65 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata select { 66 | box-sizing: border-box; 67 | width: 100%; 68 | font-size: 0.9em; 69 | border: 1px solid #BBBBBB; 70 | } 71 | 72 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata select:focus { 73 | border: 1px solid #888888; 74 | } 75 | 76 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata select:hover { 77 | border: 1px solid #888888; 78 | } 79 | 80 | .ff_formwrap .ff_formwrapinner .formitem .formitemdata .staticwrap { 81 | margin-left: 0.5em; 82 | } 83 | 84 | .ff_formwrap .ff_formwrapinner .formitem .formitemdesc { 85 | color: #333333; 86 | margin-bottom: 0.2em; 87 | margin-left: 0.5em; 88 | font-size: 0.9em; 89 | } 90 | 91 | .ff_formwrap .ff_formwrapinner .formitem .formitemresult { 92 | margin-left: 0.5em; 93 | } 94 | 95 | .ff_formwrap .ff_formwrapinner .formitem .formitemresult .formitemerror { 96 | background: url('flex_forms_error.png') 0 0.1em no-repeat; 97 | padding-left: 25px; 98 | color: #A94442; 99 | font-weight: bold; 100 | } 101 | 102 | .ff_formwrap .ff_formwrapinner .fieldtablewrap > table.rowwrap { 103 | border-collapse: collapse; 104 | } 105 | 106 | .ff_formwrap .ff_formwrapinner .fieldtablewrap > table.rowwrap > tbody > tr > td { 107 | padding-right: 1.0em; 108 | vertical-align: top; 109 | } 110 | 111 | .ff_formwrap .ff_formwrapinner .fieldtablewrap .formitemtitle { 112 | white-space: nowrap; 113 | } 114 | 115 | .ff_formwrap .ff_formwrapinner .nowrap { 116 | white-space: nowrap; 117 | } 118 | 119 | .ff_formwrap .ff_formwrapinner .formsubmit { 120 | margin-top: 1.2em; 121 | } 122 | 123 | .ff_formwrap .ff_formwrapinner .formsubmit input { 124 | padding: 0.2em 0.5em; 125 | font-weight: bold; 126 | font-size: 1.0em; 127 | color: #1F1F1F; 128 | outline: none; 129 | } 130 | 131 | .ff_formmessagewrap .ff_formmessagewrapinner .message { 132 | border: 1px solid transparent; 133 | border-radius: 4px; 134 | margin-top: 1.0em; 135 | padding: 0.5em 0.7em; 136 | } 137 | 138 | .ff_formmessagewrap .ff_formmessagewrapinner .messagesuccess { 139 | border-color: #B2DBA1; 140 | background-color: #DFF0D8; 141 | background-image: linear-gradient(to bottom, #DFF0D8 0px, #C8E5BC 100%); 142 | background-repeat: repeat-x; 143 | color: #3C763D; 144 | } 145 | 146 | .ff_formmessagewrap .ff_formmessagewrapinner .messagewarning { 147 | border-color: #F5E79E; 148 | background-color: #FCF8E3; 149 | background-image: linear-gradient(to bottom, #FCF8E3 0px, #F8EFC0 100%); 150 | background-repeat: repeat-x; 151 | color: #8A6D3B; 152 | } 153 | 154 | .ff_formmessagewrap .ff_formmessagewrapinner .messageerror { 155 | border-color: #DCA7A7; 156 | background-color: #F2DEDE; 157 | background-image: linear-gradient(to bottom, #F2DEDE 0px, #E7C3C3 100%); 158 | background-repeat: repeat-x; 159 | color: #A94442; 160 | } 161 | 162 | .ff_formmessagewrap .ff_formmessagewrapinner .messageinfo { 163 | border-color: #9ACFEA; 164 | background-color: #D9EDF7; 165 | background-image: linear-gradient(to bottom, #D9EDF7 0px, #B9DEF0 100%); 166 | background-repeat: repeat-x; 167 | color: #31708F; 168 | } 169 | -------------------------------------------------------------------------------- /support/str_basics.php: -------------------------------------------------------------------------------- 1 | $val) 10 | { 11 | if (is_string($val)) $_REQUEST[$key] = trim($val); 12 | else if (is_array($val)) 13 | { 14 | $_REQUEST[$key] = array(); 15 | foreach ($val as $key2 => $val2) $_REQUEST[$key][$key2] = (is_string($val2) ? trim($val2) : $val2); 16 | } 17 | else $_REQUEST[$key] = $val; 18 | } 19 | } 20 | 21 | // Cleans up all PHP input issues so that $_REQUEST may be used as expected. 22 | public static function ProcessAllInput() 23 | { 24 | self::ProcessSingleInput($_COOKIE); 25 | self::ProcessSingleInput($_GET); 26 | self::ProcessSingleInput($_POST); 27 | } 28 | 29 | public static function ExtractPathname($dirfile) 30 | { 31 | $dirfile = str_replace("\\", "/", $dirfile); 32 | $pos = strrpos($dirfile, "/"); 33 | if ($pos === false) $dirfile = ""; 34 | else $dirfile = substr($dirfile, 0, $pos + 1); 35 | 36 | return $dirfile; 37 | } 38 | 39 | public static function ExtractFilename($dirfile) 40 | { 41 | $dirfile = str_replace("\\", "/", $dirfile); 42 | $pos = strrpos($dirfile, "/"); 43 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 44 | 45 | return $dirfile; 46 | } 47 | 48 | public static function ExtractFileExtension($dirfile) 49 | { 50 | $dirfile = self::ExtractFilename($dirfile); 51 | $pos = strrpos($dirfile, "."); 52 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 53 | else $dirfile = ""; 54 | 55 | return $dirfile; 56 | } 57 | 58 | public static function ExtractFilenameNoExtension($dirfile) 59 | { 60 | $dirfile = self::ExtractFilename($dirfile); 61 | $pos = strrpos($dirfile, "."); 62 | if ($pos !== false) $dirfile = substr($dirfile, 0, $pos); 63 | 64 | return $dirfile; 65 | } 66 | 67 | // Makes an input filename safe for use. 68 | // Allows a very limited number of characters through. 69 | public static function FilenameSafe($filename) 70 | { 71 | return preg_replace('/\s+/', "-", trim(trim(preg_replace('/[^A-Za-z0-9_.\-]/', " ", $filename), "."))); 72 | } 73 | 74 | public static function ReplaceNewlines($replacewith, $data) 75 | { 76 | $data = str_replace("\r\n", "\n", $data); 77 | $data = str_replace("\r", "\n", $data); 78 | $data = str_replace("\n", $replacewith, $data); 79 | 80 | return $data; 81 | } 82 | 83 | public static function LineInput($data, &$pos) 84 | { 85 | $CR = ord("\r"); 86 | $LF = ord("\n"); 87 | 88 | $result = ""; 89 | $y = strlen($data); 90 | if ($pos > $y) $pos = $y; 91 | while ($pos < $y && ord($data[$pos]) != $CR && ord($data[$pos]) != $LF) 92 | { 93 | $result .= $data[$pos]; 94 | $pos++; 95 | } 96 | if ($pos + 1 < $y && ord($data[$pos]) == $CR && ord($data[$pos + 1]) == $LF) $pos++; 97 | if ($pos < $y) $pos++; 98 | 99 | return $result; 100 | } 101 | 102 | // Constant-time string comparison. Ported from CubicleSoft C++ code. 103 | public static function CTstrcmp($secret, $userinput) 104 | { 105 | $sx = 0; 106 | $sy = strlen($secret); 107 | $uy = strlen($userinput); 108 | $result = $sy - $uy; 109 | for ($ux = 0; $ux < $uy; $ux++) 110 | { 111 | $result |= ord($userinput[$ux]) ^ ord($secret[$sx]); 112 | $sx = ($sx + 1) % $sy; 113 | } 114 | 115 | return $result; 116 | } 117 | 118 | public static function ConvertUserStrToBytes($str) 119 | { 120 | $str = trim($str); 121 | $num = (double)$str; 122 | if (strtoupper(substr($str, -1)) == "B") $str = substr($str, 0, -1); 123 | switch (strtoupper(substr($str, -1))) 124 | { 125 | case "P": $num *= 1024; 126 | case "T": $num *= 1024; 127 | case "G": $num *= 1024; 128 | case "M": $num *= 1024; 129 | case "K": $num *= 1024; 130 | } 131 | 132 | return $num; 133 | } 134 | 135 | public static function ConvertBytesToUserStr($num) 136 | { 137 | $num = (double)$num; 138 | 139 | if ($num < 0) return "0 B"; 140 | if ($num < 1024) return number_format($num, 0) . " B"; 141 | if ($num < 1048576) return str_replace(".0 ", "", number_format($num / 1024, 1)) . " KB"; 142 | if ($num < 1073741824) return str_replace(".0 ", "", number_format($num / 1048576, 1)) . " MB"; 143 | if ($num < 1099511627776.0) return str_replace(".0 ", "", number_format($num / 1073741824.0, 1)) . " GB"; 144 | if ($num < 1125899906842624.0) return str_replace(".0 ", "", number_format($num / 1099511627776.0, 1)) . " TB"; 145 | 146 | return str_replace(".0 ", "", number_format($num / 1125899906842624.0, 1)) . " PB"; 147 | } 148 | 149 | public static function JSSafe($data) 150 | { 151 | return str_replace(array("'", "\r", "\n"), array("\\'", "\\r", "\\n"), $data); 152 | } 153 | } 154 | ?> -------------------------------------------------------------------------------- /support/csdb/db_pgsql_lite.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | 27 | // Set Unicode support. 28 | $this->Query("SET", "client_encoding TO 'UTF-8'"); 29 | } 30 | 31 | public function GetInsertID($name = null) 32 | { 33 | return $this->lastid; 34 | } 35 | 36 | public function QuoteIdentifier($str) 37 | { 38 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 39 | } 40 | 41 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 42 | { 43 | switch ($cmd) 44 | { 45 | case "SELECT": 46 | { 47 | $supported = array( 48 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 49 | "FROM" => array("SUBQUERIES" => true), 50 | "WHERE" => array("SUBQUERIES" => true), 51 | "GROUP BY" => true, 52 | "HAVING" => true, 53 | "ORDER BY" => true, 54 | "LIMIT" => " OFFSET " 55 | ); 56 | 57 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 58 | } 59 | case "INSERT": 60 | { 61 | $supported = array( 62 | "PREINTO" => array(), 63 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 64 | "SELECT" => true, 65 | "BULKINSERT" => true 66 | ); 67 | 68 | // To get the last insert ID via GetInsertID(), the field that contains a 'serial' (auto increment) field must be specified. 69 | if (isset($queryinfo["AUTO INCREMENT"])) $queryinfo["RETURNING"] = $queryinfo["AUTO INCREMENT"]; 70 | 71 | $this->lastid = 0; 72 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 73 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 74 | 75 | return $result; 76 | } 77 | case "UPDATE": 78 | { 79 | // No ORDER BY or LIMIT support. 80 | $supported = array( 81 | "PRETABLE" => array("ONLY" => "bool"), 82 | "WHERE" => array("SUBQUERIES" => true) 83 | ); 84 | 85 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 86 | } 87 | case "DELETE": 88 | { 89 | // No ORDER BY or LIMIT support. 90 | $supported = array( 91 | "PREFROM" => array("ONLY" => "bool"), 92 | "WHERE" => array("SUBQUERIES" => true) 93 | ); 94 | 95 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 96 | } 97 | case "SET": 98 | { 99 | $sql = "SET " . $queryinfo; 100 | 101 | return array("success" => true); 102 | } 103 | case "USE": 104 | { 105 | // Fake multiple databases with PostgreSQL schemas. 106 | // http://www.postgresql.org/docs/7.3/static/ddl-schemas.html 107 | $sql = "SET search_path TO " . ($queryinfo != "" ? $this->QuoteIdentifier($queryinfo) . "," : "") . "\"\$user\",public"; 108 | 109 | return array("success" => true); 110 | } 111 | case "TRUNCATE TABLE": 112 | { 113 | $master = true; 114 | 115 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 116 | 117 | return array("success" => true); 118 | } 119 | } 120 | 121 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 122 | } 123 | 124 | protected function RunStatementFilter(&$stmt, &$filteropts) 125 | { 126 | if ($filteropts["mode"] == "INSERT") 127 | { 128 | // Force the last ID value to be extracted for INSERT queries. 129 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 130 | $row = $result->NextRow(); 131 | 132 | $stmt = false; 133 | } 134 | 135 | parent::RunStatementFilter($stmt, $filteropts); 136 | } 137 | 138 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 139 | { 140 | switch ($filteropts["mode"]) 141 | { 142 | case "INSERT": 143 | { 144 | if ($row !== false) 145 | { 146 | $key = $filteropts["queryinfo"]["AUTO INCREMENT"]; 147 | $this->lastid = $row->$key; 148 | } 149 | 150 | break; 151 | } 152 | } 153 | 154 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 155 | } 156 | } 157 | ?> -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | false, 21 | "error" => $msg, 22 | "errorcode" => $msgcode 23 | ); 24 | if ($info !== false) $result["info"] = $info; 25 | 26 | $opts = false; 27 | SendOutput($result, false, $opts); 28 | 29 | exit(); 30 | } 31 | 32 | // Load data to verify. 33 | $reqmethod = strtoupper($_SERVER["REQUEST_METHOD"]); 34 | if (isset($_GET["rest_data"])) $data = @json_decode($_GET["rest_data"], true); 35 | else if ($reqmethod === "GET") $data = $_GET; 36 | else if (isset($_POST["rest_data"])) $data = @json_decode($_POST["rest_data"], true); 37 | else if (isset($_SERVER["CONTENT_TYPE"]) && strtolower(substr($_SERVER["CONTENT_TYPE"], 0, 16)) === "application/json") $data = @json_decode(file_get_contents("php://input"), true); 38 | else if (isset($_SERVER["HTTP_CONTENT_TYPE"]) && strtolower(substr($_SERVER["HTTP_CONTENT_TYPE"], 0, 16)) === "application/json") $data = @json_decode(file_get_contents("php://input"), true); 39 | else $data = @array_merge($_GET, $_POST); 40 | 41 | if (!is_array($data)) SendError("Invalid input.", "invalid_input"); 42 | 43 | // Credentials check. 44 | if (!isset($_SERVER["HTTP_X_APIKEY"]) || !is_string($_SERVER["HTTP_X_APIKEY"])) $_SERVER["HTTP_X_APIKEY"] = ""; 45 | if (BB_CTstrcmp($config["read_apikey"], $_SERVER["HTTP_X_APIKEY"]) == 0) 46 | { 47 | $write = false; 48 | } 49 | else if (BB_CTstrcmp($config["readwrite_apikey"], $_SERVER["HTTP_X_APIKEY"]) == 0) 50 | { 51 | // Check signature for write access. Allow for system clock drift of 5 minutes. 52 | if (!isset($_SERVER["HTTP_X_SIGNATURE"]) || !is_string($_SERVER["HTTP_X_SIGNATURE"])) $_SERVER["HTTP_X_SIGNATURE"] = ""; 53 | $sig = base64_encode(hash_hmac("sha256", json_encode($data, JSON_UNESCAPED_SLASHES), $config["readwrite_secret"], true)); 54 | $write = (BB_CTstrcmp($sig, $_SERVER["HTTP_X_SIGNATURE"]) == 0 && isset($data["ts"]) && (int)$data["ts"] >= time() - 300 && (int)$data["ts"] < time() + 300); 55 | unset($sig); 56 | } 57 | else 58 | { 59 | SendError("Missing or invalid API key.", "missing_or_invalid_apikey"); 60 | } 61 | 62 | // Verify version compatibility. 63 | if (!isset($data["ver"])) SendError("Missing API version.", "missing_ver"); 64 | if ($data["ver"] != 1) SendError("Expected API version 1.", "expected_ver_1"); 65 | 66 | // Verify that the 'api' variable exists and is a string. 67 | if (!isset($data["api"]) || !is_string($data["api"])) SendError("Missing 'api' or not a string.", "missing_or_invalid_api"); 68 | 69 | // Instantiate the reusable API Helper. 70 | if (file_exists($config["rootpath"] . "/bb_api_helper_override.php")) 71 | { 72 | require_once $config["rootpath"] . "/bb_api_helper_override.php"; 73 | 74 | $apihelper = new BB_API_Helper_Override($config, $write); 75 | } 76 | else 77 | { 78 | $apihelper = new BB_API_Helper($config, $write); 79 | } 80 | 81 | // Connect to the database. 82 | $result = $apihelper->ConnectDB(); 83 | if (!$result["success"]) SendError($result["error"], $result["errorcode"], $result["info"]); 84 | 85 | $opts = false; 86 | if ($reqmethod === "GET") 87 | { 88 | switch ($data["api"]) 89 | { 90 | case "assets": $result = $apihelper->GetAssets($data, "SendOutput", $opts); break; 91 | case "tags": $result = $apihelper->GetTags($data, "SendOutput", $opts); break; 92 | case "revisions": 93 | { 94 | if (!$write) SendError("Access denied for revisions list. Possible causes: Using a read only API key or missing/using an invalid signature or timestamp.", "access_denied"); 95 | 96 | $result = $apihelper->GetRevisions($data, "SendOutput", $opts); 97 | 98 | break; 99 | } 100 | case "file": 101 | { 102 | $result = $apihelper->DownloadFile($data); 103 | if (!$result["success"]) http_response_code(404); 104 | 105 | break; 106 | } 107 | case "access": $result = $apihelper->GetAccessInfo("SendOutput", $opts); break; 108 | case "delete_upload": 109 | { 110 | if (!$write) SendError("Access denied for delete operation. Possible causes: Using a read only API key or missing/using an invalid signature or timestamp.", "access_denied"); 111 | 112 | $result = $apihelper->DeleteUpload($data, "SendOutput", $opts); 113 | 114 | break; 115 | } 116 | case "delete_asset": 117 | { 118 | if (!$write) SendError("Access denied for delete operation. Possible causes: Using a read only API key or missing/using an invalid signature or timestamp.", "access_denied"); 119 | 120 | $result = $apihelper->DeleteAsset($data, "SendOutput", $opts); 121 | 122 | break; 123 | } 124 | default: 125 | { 126 | $result = $apihelper->UnknownAPI($reqmethod, $data, "SendOutput", $opts); 127 | 128 | break; 129 | } 130 | } 131 | 132 | if (!$result["success"]) SendError($result["error"], $result["errorcode"], $result["info"]); 133 | } 134 | else if ($reqmethod === "DELETE") 135 | { 136 | if (!$write) SendError("Access denied for delete operation. Possible causes: Using a read only API key or missing/using an invalid signature or timestamp.", "access_denied"); 137 | 138 | switch ($data["api"]) 139 | { 140 | case "upload": $result = $apihelper->DeleteUpload($data, "SendOutput", $opts); break; 141 | case "asset": $result = $apihelper->DeleteAsset($data, "SendOutput", $opts); break; 142 | default: 143 | { 144 | $result = $apihelper->UnknownAPI($reqmethod, $data, "SendOutput", $opts); 145 | 146 | break; 147 | } 148 | } 149 | 150 | if (!$result["success"]) SendError($result["error"], $result["errorcode"]); 151 | } 152 | else if ($reqmethod === "POST") 153 | { 154 | if (!$write) SendError("Write access denied. Possible causes: Using a read only API key or missing/using an invalid signature or timestamp.", "access_denied"); 155 | 156 | switch ($data["api"]) 157 | { 158 | case "asset": $result = $apihelper->StoreAsset($data, "SendOutput", $opts); break; 159 | case "upload_start": $result = $apihelper->StartUpload($data, "SendOutput", $opts); break; 160 | case "upload": $result = $apihelper->ProcessUpload($data, "SendOutput", $opts); break; 161 | case "upload_done": $result = $apihelper->UploadDone($data, "SendOutput", $opts); break; 162 | default: 163 | { 164 | $result = $apihelper->UnknownAPI($reqmethod, $data, "SendOutput", $opts); 165 | 166 | break; 167 | } 168 | } 169 | 170 | if (!$result["success"]) SendError($result["error"], $result["errorcode"], (isset($result["info"]) ? $result["info"] : false)); 171 | } 172 | else 173 | { 174 | SendError("Unrecognized HTTP method.", "unrecognized_http_method"); 175 | } 176 | ?> -------------------------------------------------------------------------------- /support/csdb/db_oci_lite.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 43 | 44 | parent::Connect($dsn, $username, $password, $options); 45 | 46 | // Convert DB NULL values into empty strings for use in code. 47 | $this->dbobj->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING); 48 | 49 | // Converts all uppercase table names into lowercase table names. 50 | $this->dbobj->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 51 | 52 | // Set Oracle session variables to use standard date formats. 53 | $this->Query("SET", "NLS_DATE_FORMAT='YYYY-MM-DD'"); 54 | $this->Query("SET", "NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'"); 55 | 56 | // Set Unicode support. 57 | // TODO: Figure out unicode support for Oracle 58 | //$this->Query("SET", "NLS_LANGUAGE='UTF8'"); 59 | } 60 | 61 | public function GetVersion() 62 | { 63 | $tableExists = $this->TableExists("SSO_USER"); 64 | 65 | return $this->GetOne("SELECT",array( 66 | "banner", 67 | "FROM" => "v\$version", 68 | "WHERE" => "banner LIKE 'Oracle%'" 69 | )); 70 | } 71 | 72 | public function GetInsertID($name = null) 73 | { 74 | return $this->lastid; 75 | } 76 | 77 | public function TableExists($name) 78 | { 79 | return ($this->GetOne("SHOW TABLES", array("LIKE" => $name)) === false ? false : true); 80 | } 81 | 82 | public function QuoteIdentifier($str) 83 | { 84 | return preg_replace('/[^A-Za-z0-9_]/', "_", $str); 85 | } 86 | 87 | // This function is used to get the last inserted sequence value by table name. 88 | // 89 | // Uses automatically geneerated sequences as part of the Oracle 12c IDENTITY 90 | // column. This is not available in 11g and older Oracle databases. 91 | // See the ProcessColumnDefinition() function for more detail. 92 | private function GetOracleInsertID($tableName) 93 | { 94 | // Query the "all_tab_columns" for the oracle IDENTITY column and identify the sequence 95 | $seqName = $this->GetOne("SELECT", array( 96 | "data_default", 97 | "FROM" => "all_tab_columns", 98 | "WHERE" => "identity_column = 'YES' AND table_name = ?" 99 | ), strtoupper($tableName)); 100 | 101 | // The previous query returned "nextval" with the sequence name, 102 | // however we need the current sequence value 103 | $seqName = str_replace(".nextval", ".CURRVAL", $seqName); 104 | 105 | // This grabs the current value from the sequence identified above 106 | $retVal = $this->GetOne("SELECT", array( 107 | $seqName, 108 | "FROM" => "DUAL" 109 | )); 110 | 111 | // Return the current sequence value 112 | return $retVal; 113 | } 114 | 115 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 116 | { 117 | $mystr = "test"; 118 | switch ($cmd) 119 | { 120 | case "SELECT": 121 | { 122 | $supported = array( 123 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 124 | "FROM" => array("SUBQUERIES" => true), 125 | "WHERE" => array("SUBQUERIES" => true), 126 | "GROUP BY" => true, 127 | "HAVING" => true, 128 | "ORDER BY" => true, 129 | // Haven't figured out the LIMIT problem yet 130 | // TODO: Figure out how to use Oracle's ROWNUM where clause functionalitty 131 | // instead of the LIMIT function 132 | //"LIMIT" => " OFFSET " 133 | ); 134 | 135 | // Oracle does not support aliasing table names in the FROM clause. 136 | // However, alias' are supported in COLUMN names. 137 | // AS is used in the Oracle FROM clause to process nested queries, 138 | // but does not support alias'. 139 | $queryinfo["FROM"] = str_replace("? AS ", "? ", $queryinfo["FROM"]); 140 | 141 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 142 | } 143 | case "INSERT": 144 | { 145 | $supported = array( 146 | "PREINTO" => array(), 147 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 148 | "SELECT" => true, 149 | "BULKINSERT" => false 150 | ); 151 | 152 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 153 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 154 | 155 | // Handle bulk insert by rewriting the queries because, well, Oracle. 156 | // http://stackoverflow.com/questions/39576/best-way-to-do-multi-row-insert-in-oracle 157 | if ($result["success"] && is_array($sql)) 158 | { 159 | $sql2 = "INSERT ALL"; 160 | foreach ($sql as $entry) $sql2 .= substr($entry, 6); 161 | $sql2 .= " SELECT 1 FROM DUAL"; 162 | $sql = $sql2; 163 | } 164 | 165 | return $result; 166 | } 167 | case "UPDATE": 168 | { 169 | // No ORDER BY or LIMIT support. 170 | $supported = array( 171 | "PRETABLE" => array("ONLY" => "bool"), 172 | "WHERE" => array("SUBQUERIES" => true) 173 | ); 174 | 175 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 176 | } 177 | case "DELETE": 178 | { 179 | // No ORDER BY or LIMIT support. 180 | $supported = array( 181 | "PREFROM" => array("ONLY" => "bool"), 182 | "WHERE" => array("SUBQUERIES" => true) 183 | ); 184 | 185 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 186 | } 187 | case "SET": 188 | { 189 | $sql = "ALTER SESSION SET " . $queryinfo; 190 | 191 | return array("success" => true); 192 | } 193 | 194 | case "USE": 195 | { 196 | // Fake multiple databases with Oracle schemas. 197 | // SCHEMA is already selected with user 198 | // $sql = "SELECT 1 FROM DUAL"; 199 | 200 | return array("success" => false, "errorcode" => "skip_sql_query"); 201 | } 202 | case "TRUNCATE TABLE": 203 | { 204 | $master = true; 205 | 206 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 207 | 208 | return array("success" => true); 209 | } 210 | } 211 | 212 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 213 | } 214 | 215 | protected function RunStatementFilter(&$stmt, &$filteropts) 216 | { 217 | if ($filteropts["mode"] == "INSERT") 218 | { 219 | // Force the last ID value to be extracted for INSERT queries. 220 | // Unable to find a way to get Oracle to return a row without 221 | // Using PL/SQL functions. 222 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 223 | $row = $result->NextRow(); 224 | 225 | $stmt = false; 226 | } 227 | 228 | parent::RunStatementFilter($stmt, $filteropts); 229 | } 230 | 231 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 232 | { 233 | switch ($filteropts["mode"]) 234 | { 235 | case "INSERT": 236 | { 237 | // Use the private function provided above to get the Last Inserted ID 238 | $this->lastid = $this->GetOracleInsertID($filteropts["queryinfo"][0]); 239 | 240 | break; 241 | } 242 | } 243 | 244 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 245 | } 246 | } 247 | ?> -------------------------------------------------------------------------------- /support/sdk_cloud_storage_server_api_base.php: -------------------------------------------------------------------------------- 1 | web = new WebBrowser(); 19 | $this->fp = false; 20 | $this->host = false; 21 | $this->apikey = false; 22 | $this->cafile = false; 23 | $this->cacert = false; 24 | $this->cert = false; 25 | $this->apiprefix = false; 26 | } 27 | 28 | public function SetAccessInfo($host, $apikey, $cafile, $cert) 29 | { 30 | $this->web = new WebBrowser(); 31 | if (is_resource($this->fp)) @fclose($this->fp); 32 | $this->fp = false; 33 | if (substr($host, -1) === "/") $host = substr($host, 0, -1); 34 | $this->host = $host; 35 | $this->apikey = $apikey; 36 | $this->cafile = $cafile; 37 | $this->cert = $cert; 38 | } 39 | 40 | public function GetSSLInfo() 41 | { 42 | if ($this->host === false) return array("success" => false, "error" => self::CSS_Translate("Missing host."), "errorcode" => "no_access_info"); 43 | 44 | if ($this->cafile !== false && $this->cacert === false) $this->cacert = @file_get_contents($this->cafile); 45 | 46 | if (substr($this->host, 0, 7) === "http://" || RemotedAPI::IsRemoted($this->host)) 47 | { 48 | $this->cacert = ""; 49 | $this->cert = ""; 50 | } 51 | else if ($this->cacert === false || $this->cert === false) 52 | { 53 | $this->cacert = false; 54 | $this->cert = false; 55 | 56 | $this->neterror = ""; 57 | 58 | $options = array( 59 | "peer_cert_callback" => array($this, "Internal_PeerCertificateCheck"), 60 | "peer_cert_callback_opts" => "", 61 | "sslopts" => self::InitSSLOpts(array("verify_peer" => false, "capture_peer_cert_chain" => true)) 62 | ); 63 | 64 | $result = $this->web->Process($this->host . "/", $options); 65 | 66 | if (!$result["success"]) 67 | { 68 | $result["error"] .= " " . $this->neterror; 69 | 70 | return $result; 71 | } 72 | } 73 | 74 | if ($this->cert === false) return array("success" => false, "error" => self::CSS_Translate("Unable to retrieve server certificate."), "errorcode" => "cert_retrieval_failed"); 75 | if ($this->cacert === false) return array("success" => false, "error" => self::CSS_Translate("Unable to retrieve server CA certificate."), "errorcode" => "cacert_retrieval_failed"); 76 | 77 | return array("success" => true, "cacert" => $this->cacert, "cert" => $this->cert); 78 | } 79 | 80 | public function InitSSLCache($host, $cafile, $certfile) 81 | { 82 | if (!file_exists($cafile) || !file_exists($certfile)) 83 | { 84 | $this->SetAccessInfo($host, false, false, false); 85 | 86 | $result = $this->GetSSLInfo(); 87 | if (!$result["success"]) return array("success" => false, "error" => "Unable to get SSL information.", "errorcode" => "get_ssl_info_failed", "info" => $result); 88 | 89 | file_put_contents($cafile, $result["cacert"]); 90 | file_put_contents($certfile, $result["cert"]); 91 | } 92 | 93 | return array("success" => true); 94 | } 95 | 96 | protected static function CSS_Translate() 97 | { 98 | $args = func_get_args(); 99 | if (!count($args)) return ""; 100 | 101 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 102 | } 103 | 104 | // Internal function to retrieve a X509 SSL certificate during the initial connection to confirm that this is the correct target server. 105 | public function Internal_PeerCertificateCheck($type, $cert, $opts) 106 | { 107 | if (is_array($cert)) 108 | { 109 | // The server is incorrectly configured if it doesn't have the self-signed root certificate in the chain. 110 | if (count($cert) < 2) 111 | { 112 | $this->neterror = "Certificate chain is missing the root certificate. Remote host is incorrectly configured."; 113 | 114 | return false; 115 | } 116 | 117 | // The last entry is the root cert. 118 | if (!openssl_x509_export($cert[count($cert) - 1], $str)) 119 | { 120 | $this->neterror = "Certificate chain contains an invalid root certificate. Corrupted on remote host?"; 121 | 122 | return false; 123 | } 124 | 125 | $this->cacert = $str; 126 | } 127 | else 128 | { 129 | if (!openssl_x509_export($cert, $str)) 130 | { 131 | $this->neterror = "Server certificate is invalid. Corrupted on remote host?"; 132 | 133 | return false; 134 | } 135 | 136 | // Initial setup automatically trusts the SSL/TLS certificate of the host. 137 | if ($this->cert === false) $this->cert = $str; 138 | else if ($str !== $this->cert) 139 | { 140 | $this->neterror = "Certificate does not exactly match local certificate. Your client is either under a MITM attack or the remote host changed certificates."; 141 | 142 | return false; 143 | } 144 | } 145 | 146 | return true; 147 | } 148 | 149 | protected static function InitSSLOpts($options) 150 | { 151 | $result = array_merge(array( 152 | "ciphers" => "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS", 153 | "disable_compression" => true, 154 | "allow_self_signed" => false, 155 | "verify_peer_name" => false, 156 | "verify_depth" => 3, 157 | "capture_peer_cert" => true, 158 | ), $options); 159 | 160 | return $result; 161 | } 162 | 163 | protected function InitWebSocket() 164 | { 165 | if ($this->host === false || $this->apikey === false) return array("success" => false, "error" => self::CSS_Translate("Missing host or API key."), "errorcode" => "no_access_info"); 166 | if ($this->cafile === false || $this->cert === false) return array("success" => false, "error" => self::CSS_Translate("Missing SSL Certificate or Certificate Authority filename. Call GetSSLInfo() to initialize for the first time and be sure to save the results."), "errorcode" => "critical_ssl_info_missing"); 167 | 168 | $url = $this->host; 169 | 170 | // Handle Remoted API connections. 171 | if ($this->fp === false && RemotedAPI::IsRemoted($this->host)) 172 | { 173 | $result = RemotedAPI::Connect($this->host); 174 | if (!$result["success"]) return $result; 175 | 176 | $this->fp = $result["fp"]; 177 | $url = $result["url"]; 178 | 179 | $options = array( 180 | "fp" => $this->fp, 181 | "headers" => array( 182 | "X-APIKey" => $this->apikey 183 | ) 184 | ); 185 | } 186 | else 187 | { 188 | $options = array( 189 | "headers" => array( 190 | "X-APIKey" => $this->apikey 191 | ), 192 | "peer_cert_callback" => array($this, "Internal_PeerCertificateCheck"), 193 | "peer_cert_callback_opts" => "", 194 | "sslopts" => self::InitSSLOpts(array("cafile" => $this->cafile, "verify_peer" => true)) 195 | ); 196 | } 197 | 198 | if (!class_exists("WebSocket", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/websocket.php"; 199 | 200 | $ws = new WebSocket(); 201 | 202 | $wsurl = str_replace(array("https://", "http://"), array("wss://", "ws://"), $url); 203 | $result = $ws->Connect($wsurl . $this->apiprefix, $url, $options); 204 | if (!$result["success"]) return $result; 205 | 206 | $result["ws"] = $ws; 207 | 208 | return $result; 209 | } 210 | 211 | protected function RunAPI($method, $apipath, $options = array(), $expected = 200, $encodejson = true, $decodebody = true) 212 | { 213 | if ($this->host === false || $this->apikey === false) return array("success" => false, "error" => self::CSS_Translate("Missing host or API key."), "errorcode" => "no_access_info"); 214 | if ($this->cafile === false || $this->cert === false) return array("success" => false, "error" => self::CSS_Translate("Missing SSL Certificate or Certificate Authority filename. Call GetSSLInfo() to initialize for the first time and be sure to save the results."), "errorcode" => "critical_ssl_info_missing"); 215 | 216 | $url = $this->host; 217 | 218 | // Handle Remoted API connections. 219 | if ($this->fp === false && RemotedAPI::IsRemoted($this->host)) 220 | { 221 | $result = RemotedAPI::Connect($this->host); 222 | if (!$result["success"]) return $result; 223 | 224 | $this->fp = $result["fp"]; 225 | $url = $result["url"]; 226 | 227 | $options2 = array( 228 | "fp" => $this->fp, 229 | "method" => $method, 230 | "headers" => array( 231 | "Connection" => "keep-alive", 232 | "X-APIKey" => $this->apikey 233 | ) 234 | ); 235 | } 236 | else if ($this->fp !== false) 237 | { 238 | $url = RemotedAPI::ExtractRealHost($url); 239 | 240 | $options2 = array( 241 | "fp" => $this->fp, 242 | "method" => $method, 243 | "headers" => array( 244 | "Connection" => "keep-alive" 245 | ) 246 | ); 247 | } 248 | else 249 | { 250 | $options2 = array( 251 | "method" => $method, 252 | "headers" => array( 253 | "Connection" => "keep-alive", 254 | "X-APIKey" => $this->apikey 255 | ), 256 | "peer_cert_callback" => array($this, "Internal_PeerCertificateCheck"), 257 | "peer_cert_callback_opts" => "", 258 | "sslopts" => self::InitSSLOpts(array("cafile" => $this->cafile, "verify_peer" => true)) 259 | ); 260 | } 261 | 262 | if ($encodejson && $method !== "GET") 263 | { 264 | $options2["headers"]["Content-Type"] = "application/json"; 265 | $options2["body"] = json_encode($options); 266 | } 267 | else 268 | { 269 | $options2 = array_merge($options2, $options); 270 | } 271 | 272 | $result = $this->web->Process($url . $this->apiprefix . "/" . $apipath, $options2); 273 | 274 | if (!$result["success"] && $this->fp !== false) 275 | { 276 | // If the server terminated the connection, then re-establish the connection and rerun the request. 277 | if (is_resource($this->fp)) @fclose($this->fp); 278 | $this->fp = false; 279 | 280 | return $this->RunAPI($method, $apipath, $options, $expected, $encodejson, $decodebody); 281 | } 282 | 283 | if (!$result["success"]) return $result; 284 | 285 | if (isset($result["fp"]) && is_resource($result["fp"])) $this->fp = $result["fp"]; 286 | else $this->fp = false; 287 | 288 | // Cloud Storage Server always responds with 400 Bad Request for errors. Attempt to decode the error. 289 | if ($result["response"]["code"] == 400) 290 | { 291 | $error = @json_decode($result["body"], true); 292 | if (is_array($error) && isset($error["success"]) && !$error["success"]) return $error; 293 | } 294 | 295 | if ($result["response"]["code"] != $expected) return array("success" => false, "error" => self::CSS_Translate("Expected a %d response from Cloud Storage Server. Received '%s'.", $expected, $result["response"]["line"]), "errorcode" => "unexpected_cloud_storage_server_response", "info" => $result); 296 | 297 | if ($decodebody) $result["body"] = json_decode($result["body"], true); 298 | 299 | return $result; 300 | } 301 | } 302 | ?> -------------------------------------------------------------------------------- /support/utf8.php: -------------------------------------------------------------------------------- 1 | = 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) 20 | { 21 | // ASCII minus control and special characters. 22 | $result .= chr($tempchr); 23 | $x++; 24 | } 25 | else 26 | { 27 | if ($y - $x > 1) $tempchr2 = ord($data[$x + 1]); 28 | else $tempchr2 = 0x00; 29 | if ($y - $x > 2) $tempchr3 = ord($data[$x + 2]); 30 | else $tempchr3 = 0x00; 31 | if ($y - $x > 3) $tempchr4 = ord($data[$x + 3]); 32 | else $tempchr4 = 0x00; 33 | 34 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 35 | { 36 | // Non-overlong (2 bytes). 37 | $result .= chr($tempchr); 38 | $result .= chr($tempchr2); 39 | $x += 2; 40 | } 41 | else if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 42 | { 43 | // Non-overlong (3 bytes). 44 | $result .= chr($tempchr); 45 | $result .= chr($tempchr2); 46 | $result .= chr($tempchr3); 47 | $x += 3; 48 | } 49 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 50 | { 51 | // Normal/straight (3 bytes). 52 | $result .= chr($tempchr); 53 | $result .= chr($tempchr2); 54 | $result .= chr($tempchr3); 55 | $x += 3; 56 | } 57 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 58 | { 59 | // Non-surrogates (3 bytes). 60 | $result .= chr($tempchr); 61 | $result .= chr($tempchr2); 62 | $result .= chr($tempchr3); 63 | $x += 3; 64 | } 65 | else if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 66 | { 67 | // Planes 1-3 (4 bytes). 68 | $result .= chr($tempchr); 69 | $result .= chr($tempchr2); 70 | $result .= chr($tempchr3); 71 | $result .= chr($tempchr4); 72 | $x += 4; 73 | } 74 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 75 | { 76 | // Planes 4-15 (4 bytes). 77 | $result .= chr($tempchr); 78 | $result .= chr($tempchr2); 79 | $result .= chr($tempchr3); 80 | $result .= chr($tempchr4); 81 | $x += 4; 82 | } 83 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 84 | { 85 | // Plane 16 (4 bytes). 86 | $result .= chr($tempchr); 87 | $result .= chr($tempchr2); 88 | $result .= chr($tempchr3); 89 | $result .= chr($tempchr4); 90 | $x += 4; 91 | } 92 | else if ($open && $x + 4 > $y) break; 93 | else $x++; 94 | } 95 | } 96 | 97 | $prefix = substr($data, $x); 98 | 99 | return $result; 100 | } 101 | 102 | public static function MakeValid($data) 103 | { 104 | $prefix = ""; 105 | 106 | if (!is_string($data)) $data = (string)$data; 107 | 108 | return self::MakeValidStream($prefix, $data, false); 109 | } 110 | 111 | public static function IsValid($data) 112 | { 113 | $x = 0; 114 | $y = strlen($data); 115 | while ($x < $y) 116 | { 117 | $tempchr = ord($data[$x]); 118 | if (($tempchr >= 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) $x++; 119 | else if ($tempchr < 0xC2) return false; 120 | else 121 | { 122 | $left = $y - $x; 123 | if ($left > 1) $tempchr2 = ord($data[$x + 1]); 124 | else return false; 125 | 126 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 2; 127 | else 128 | { 129 | if ($left > 2) $tempchr3 = ord($data[$x + 2]); 130 | else return false; 131 | 132 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 133 | 134 | if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) $x += 3; 135 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 3; 136 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F)) $x += 3; 137 | else 138 | { 139 | if ($left > 3) $tempchr4 = ord($data[$x + 3]); 140 | else return false; 141 | 142 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 143 | 144 | if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) $x += 4; 145 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 4; 146 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F)) $x += 4; 147 | else return false; 148 | } 149 | } 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | // Locates the next UTF8 character in a UTF8 string. 157 | // Set Pos and Size to 0 to start at the beginning. 158 | // Returns false at the end of the string or bad UTF8 character. Otherwise, returns true. 159 | public static function NextChrPos(&$data, $datalen, &$pos, &$size) 160 | { 161 | $pos += $size; 162 | $size = 0; 163 | $x = $pos; 164 | $y = $datalen; 165 | if ($x >= $y) return false; 166 | 167 | $tempchr = ord($data[$x]); 168 | if (($tempchr >= 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) $size = 1; 169 | else if ($tempchr < 0xC2) return false; 170 | else 171 | { 172 | $left = $y - $x; 173 | if ($left > 1) $tempchr2 = ord($data[$x + 1]); 174 | else return false; 175 | 176 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 2; 177 | else 178 | { 179 | if ($left > 2) $tempchr3 = ord($data[$x + 2]); 180 | else return false; 181 | 182 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 183 | 184 | if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) $size = 3; 185 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 3; 186 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F)) $size = 3; 187 | else 188 | { 189 | if ($left > 3) $tempchr4 = ord($data[$x + 3]); 190 | else return false; 191 | 192 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 193 | 194 | if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) $size = 4; 195 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 4; 196 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F)) $size = 4; 197 | else return false; 198 | } 199 | } 200 | } 201 | 202 | return true; 203 | } 204 | 205 | // Converts a numeric value to a UTF8 character (code point). 206 | public static function chr($num) 207 | { 208 | if ($num < 0 || ($num >= 0xD800 && $num <= 0xDFFF) || ($num >= 0xFDD0 && $num <= 0xFDEF) || ($num & 0xFFFE) == 0xFFFE) return ""; 209 | 210 | if ($num <= 0x7F) $result = chr($num); 211 | else if ($num <= 0x7FF) $result = chr(0xC0 | ($num >> 6)) . chr(0x80 | ($num & 0x3F)); 212 | else if ($num <= 0xFFFF) $result = chr(0xE0 | ($num >> 12)) . chr(0x80 | (($num >> 6) & 0x3F)) . chr(0x80 | ($num & 0x3F)); 213 | else if ($num <= 0x10FFFF) $result = chr(0xF0 | ($num >> 18)) . chr(0x80 | (($num >> 12) & 0x3F)) . chr(0x80 | (($num >> 6) & 0x3F)) . chr(0x80 | ($num & 0x3F)); 214 | else $result = ""; 215 | 216 | return $result; 217 | } 218 | 219 | public static function MakeChr($num) 220 | { 221 | return self::chr($num); 222 | } 223 | 224 | // Converts a UTF8 code point to a numeric value. 225 | public static function ord($str) 226 | { 227 | $tempchr = ord($str[0]); 228 | if ($tempchr <= 0x7F) return $tempchr; 229 | else if ($tempchr < 0xC2) return false; 230 | else 231 | { 232 | $y = strlen($str); 233 | if ($y > 1) $tempchr2 = ord($str[1]); 234 | else return false; 235 | 236 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) return (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 237 | else 238 | { 239 | if ($y > 2) $tempchr3 = ord($str[2]); 240 | else return false; 241 | 242 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 243 | 244 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 245 | { 246 | return (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 247 | } 248 | else 249 | { 250 | if ($y > 3) $tempchr4 = ord($str[3]); 251 | else return false; 252 | 253 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 254 | 255 | if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 256 | { 257 | return (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 258 | } 259 | } 260 | } 261 | } 262 | 263 | return false; 264 | } 265 | 266 | // Checks a numeric value to see if it is a combining code point. 267 | public static function IsCombiningCodePoint($val) 268 | { 269 | return (($val >= 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 270 | } 271 | 272 | // Determines if a UTF8 string can also be viewed as ASCII. 273 | public static function IsASCII($data) 274 | { 275 | $pos = 0; 276 | $size = 0; 277 | $y = strlen($data); 278 | while (self::NextChrPos($data, $y, $pos, $size) && $size == 1) {} 279 | if ($pos < $y || $size > 1) return false; 280 | 281 | return true; 282 | } 283 | 284 | // Returns the number of characters in a UTF8 string. 285 | public static function strlen($data) 286 | { 287 | $num = 0; 288 | $pos = 0; 289 | $size = 0; 290 | $y = strlen($data); 291 | while (self::NextChrPos($data, $y, $pos, $size)) $num++; 292 | 293 | return $num; 294 | } 295 | 296 | // Converts a UTF8 string to ASCII and drops bad UTF8 and non-ASCII characters in the process. 297 | public static function ConvertToASCII($data) 298 | { 299 | $result = ""; 300 | 301 | $pos = 0; 302 | $size = 0; 303 | $y = strlen($data); 304 | while ($pos < $y) 305 | { 306 | if (self::NextChrPos($data, $y, $pos, $size) && $size == 1) $result .= $data[$pos]; 307 | else if (!$size) $size = 1; 308 | } 309 | 310 | return $result; 311 | } 312 | 313 | // Converts UTF8 characters in a string to HTML entities. 314 | public static function ConvertToHTML($data) 315 | { 316 | return preg_replace_callback('/([\xC0-\xF7]{1,1}[\x80-\xBF]+)/', __CLASS__ . '::ConvertToHTML__Callback', $data); 317 | } 318 | 319 | protected static function ConvertToHTML__Callback($data) 320 | { 321 | $data = $data[1]; 322 | $num = 0; 323 | $data = str_split(strrev(chr((ord(substr($data, 0, 1)) % 252 % 248 % 240 % 224 % 192) + 128) . substr($data, 1))); 324 | foreach ($data as $k => $v) $num += (ord($v) % 128) * pow(64, $k); 325 | 326 | return "&#" . $num . ";"; 327 | } 328 | } 329 | ?> -------------------------------------------------------------------------------- /support/random.php: -------------------------------------------------------------------------------- 1 | mode = false; 15 | $this->fp = false; 16 | $this->cryptosafe = $cryptosafe; 17 | 18 | // Native first (PHP 7 and later). 19 | if (function_exists("random_bytes")) $this->mode = "native"; 20 | 21 | // OpenSSL fallback. 22 | if ($this->mode === false && function_exists("openssl_random_pseudo_bytes")) 23 | { 24 | // PHP 5.4.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for performance. 25 | @openssl_random_pseudo_bytes(4, $strong); 26 | if ($strong) $this->mode = "openssl"; 27 | } 28 | 29 | // Locate a (relatively) suitable source of entropy or raise an exception. 30 | if (strtoupper(substr(PHP_OS, 0, 3)) === "WIN") 31 | { 32 | // PHP 5.3.0 introduced native Windows CryptGenRandom() integration via php_win32_get_random_bytes() for functionality. 33 | if ($this->mode === false && PHP_VERSION_ID > 50300 && function_exists("mcrypt_create_iv")) $this->mode = "mcrypt"; 34 | } 35 | else 36 | { 37 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/arandom")) 38 | { 39 | // OpenBSD. mcrypt doesn't attempt to use this despite claims of higher quality entropy with performance. 40 | $this->fp = @fopen("/dev/arandom", "rb"); 41 | if ($this->fp !== false) $this->mode = "file"; 42 | } 43 | 44 | if ($cryptosafe && $this->mode === false && file_exists("/dev/random")) 45 | { 46 | // Everything else. 47 | $this->fp = @fopen("/dev/random", "rb"); 48 | if ($this->fp !== false) $this->mode = "file"; 49 | } 50 | 51 | if (!$cryptosafe && $this->mode === false && file_exists("/dev/urandom")) 52 | { 53 | // Everything else. 54 | $this->fp = @fopen("/dev/urandom", "rb"); 55 | if ($this->fp !== false) $this->mode = "file"; 56 | } 57 | 58 | if ($this->mode === false && function_exists("mcrypt_create_iv")) 59 | { 60 | // mcrypt_create_iv() is last because it opens and closes a file handle every single call. 61 | $this->mode = "mcrypt"; 62 | } 63 | } 64 | 65 | // Throw an exception if unable to find a suitable entropy source. 66 | if ($this->mode === false) 67 | { 68 | throw new Exception(self::RNG_Translate("Unable to locate a suitable entropy source.")); 69 | exit(); 70 | } 71 | } 72 | 73 | public function __destruct() 74 | { 75 | if ($this->mode === "file") fclose($this->fp); 76 | } 77 | 78 | public function GetBytes($length) 79 | { 80 | if ($this->mode === false) return false; 81 | 82 | $length = (int)$length; 83 | if ($length < 1) return false; 84 | 85 | $result = ""; 86 | do 87 | { 88 | switch ($this->mode) 89 | { 90 | case "native": $data = @random_bytes($length); break; 91 | case "openssl": $data = @openssl_random_pseudo_bytes($length, $strong); if (!$strong) $data = false; break; 92 | case "mcrypt": $data = @mcrypt_create_iv($length, ($this->cryptosafe ? MCRYPT_DEV_RANDOM : MCRYPT_DEV_URANDOM)); break; 93 | case "file": $data = @fread($this->fp, $length); break; 94 | default: $data = false; 95 | } 96 | if ($data === false) return false; 97 | 98 | $result .= $data; 99 | } while (strlen($result) < $length); 100 | 101 | return substr($result, 0, $length); 102 | } 103 | 104 | public function GenerateToken($length = 64) 105 | { 106 | $data = $this->GetBytes($length); 107 | if ($data === false) return false; 108 | 109 | return bin2hex($data); 110 | } 111 | 112 | // Get a random number between $min and $max (inclusive). 113 | public function GetInt($min, $max) 114 | { 115 | $min = (int)$min; 116 | $max = (int)$max; 117 | if ($max < $min) return false; 118 | if ($min == $max) return $min; 119 | 120 | $range = $max - $min + 1; 121 | 122 | $bits = 1; 123 | while ((1 << $bits) <= $range) $bits++; 124 | 125 | $numbytes = (int)(($bits + 7) / 8); 126 | $mask = (1 << $bits) - 1; 127 | 128 | do 129 | { 130 | $data = $this->GetBytes($numbytes); 131 | if ($data === false) return false; 132 | 133 | $result = 0; 134 | for ($x = 0; $x < $numbytes; $x++) 135 | { 136 | $result = ($result * 256) + ord($data[$x]); 137 | } 138 | 139 | $result = $result & $mask; 140 | } while ($result >= $range); 141 | 142 | return $result + $min; 143 | } 144 | 145 | // Convenience method to generate a random alphanumeric string. 146 | public function GenerateString($size = 32) 147 | { 148 | $result = ""; 149 | for ($x = 0; $x < $size; $x++) 150 | { 151 | $data = $this->GetInt(0, 61); 152 | if ($data === false) return false; 153 | 154 | $result .= self::$alphanum[$data]; 155 | } 156 | 157 | return $result; 158 | } 159 | 160 | public function GenerateWordLite(&$freqmap, $len) 161 | { 162 | $totalc = 0; 163 | $totalv = 0; 164 | foreach ($freqmap["consonants"] as $chr => $num) $totalc += $num; 165 | foreach ($freqmap["vowels"] as $chr => $num) $totalv += $num; 166 | 167 | if ($totalc <= 0 || $totalv <= 0) return false; 168 | 169 | $result = ""; 170 | for ($x = 0; $x < $len; $x++) 171 | { 172 | if ($x % 2) 173 | { 174 | $data = $this->GetInt(0, $totalv - 1); 175 | if ($data === false) return false; 176 | 177 | foreach ($freqmap["vowels"] as $chr => $num) 178 | { 179 | if ($num > $data) 180 | { 181 | $result .= $chr; 182 | 183 | break; 184 | } 185 | 186 | $data -= $num; 187 | } 188 | } 189 | else 190 | { 191 | $data = $this->GetInt(0, $totalc - 1); 192 | if ($data === false) return false; 193 | 194 | foreach ($freqmap["consonants"] as $chr => $num) 195 | { 196 | if ($num > $data) 197 | { 198 | $result .= $chr; 199 | 200 | break; 201 | } 202 | 203 | $data -= $num; 204 | } 205 | } 206 | } 207 | 208 | return $result; 209 | } 210 | 211 | public function GenerateWord(&$freqmap, $len, $separator = "-") 212 | { 213 | $result = ""; 214 | $queue = array(); 215 | $threshold = $freqmap["threshold"]; 216 | $state = "start"; 217 | while ($len) 218 | { 219 | //echo $state . " - " . $len . ": " . $result . "\n"; 220 | switch ($state) 221 | { 222 | case "start": 223 | { 224 | // The start of the word (or restart). 225 | $path = &$freqmap["start"]; 226 | while (count($queue) < $threshold && $len) 227 | { 228 | if ($len > 1 || !$path["*"]) 229 | { 230 | // Some part of the word. 231 | $found = false; 232 | if ($path[""]) 233 | { 234 | $pos = $this->GetInt(0, $path[""] - 1); 235 | 236 | foreach ($path as $chr => &$info) 237 | { 238 | if (!is_array($info)) continue; 239 | 240 | if ($info["+"] > $pos) 241 | { 242 | $result .= $chr; 243 | $queue[] = $chr; 244 | $path = &$path[$chr]; 245 | $len--; 246 | 247 | $found = true; 248 | 249 | break; 250 | } 251 | 252 | $pos -= $info["+"]; 253 | } 254 | } 255 | 256 | if (!$found) 257 | { 258 | $state = (count($queue) ? "recovery" : "restart"); 259 | 260 | break; 261 | } 262 | } 263 | else 264 | { 265 | // Last letter of the word. 266 | $found = false; 267 | if ($path["*"]) 268 | { 269 | $pos = $this->GetInt(0, $path["*"] - 1); 270 | 271 | foreach ($path as $chr => &$info) 272 | { 273 | if (!is_array($info)) continue; 274 | 275 | if ($info["-"] > $pos) 276 | { 277 | $result .= $chr; 278 | $queue[] = $chr; 279 | $path = &$path[$chr]; 280 | $len--; 281 | 282 | $found = true; 283 | 284 | break; 285 | } 286 | 287 | $pos -= $info["-"]; 288 | } 289 | } 290 | 291 | if (!$found) 292 | { 293 | $state = (count($queue) ? "end" : "restart"); 294 | 295 | break; 296 | } 297 | } 298 | } 299 | 300 | if (count($queue) >= $threshold) $state = ($len >= $threshold ? "middle" : "end"); 301 | 302 | break; 303 | } 304 | case "middle": 305 | { 306 | // The middle of the word. 307 | $str = implode("", $queue); 308 | 309 | if (!isset($freqmap["middle"][$str])) $state = "recovery"; 310 | else 311 | { 312 | $found = false; 313 | 314 | if ($freqmap["middle"][$str][""]) 315 | { 316 | $pos = $this->GetInt(0, $freqmap["middle"][$str][""] - 1); 317 | 318 | foreach ($freqmap["middle"][$str] as $chr => $num) 319 | { 320 | if ($chr === "") continue; 321 | 322 | if ($num > $pos) 323 | { 324 | $result .= $chr; 325 | $queue[] = $chr; 326 | array_shift($queue); 327 | $len--; 328 | 329 | if ($len < $threshold) $state = "end"; 330 | 331 | $found = true; 332 | 333 | break; 334 | } 335 | 336 | $pos -= $num; 337 | } 338 | } 339 | 340 | if (!$found) $state = "recovery"; 341 | } 342 | 343 | break; 344 | } 345 | case "end": 346 | { 347 | if (!isset($freqmap["end"][$len]) || !count($queue) || !isset($freqmap["end"][$len][$queue[count($queue) - 1]])) $state = "restart"; 348 | else 349 | { 350 | $path = &$freqmap["end"][$len][$queue[count($queue) - 1]]; 351 | 352 | $found = false; 353 | 354 | if ($path[""]) 355 | { 356 | $pos = $this->GetInt(0, $path[""] - 1); 357 | 358 | foreach ($path as $str => $num) 359 | { 360 | if ($str === "") continue; 361 | 362 | if ($num > $pos) 363 | { 364 | $result .= $str; 365 | $len = 0; 366 | 367 | $found = true; 368 | 369 | break; 370 | } 371 | 372 | $pos -= $num; 373 | } 374 | } 375 | 376 | if (!$found) $state = "restart"; 377 | } 378 | 379 | break; 380 | } 381 | case "recovery": 382 | { 383 | if (!count($queue) || !isset($freqmap["recovery"][$queue[count($queue) - 1]])) $state = "restart"; 384 | else 385 | { 386 | $path = &$freqmap["recovery"][$queue[count($queue) - 1]]; 387 | 388 | $found = false; 389 | 390 | if ($path[""]) 391 | { 392 | $pos = $this->GetInt(0, $path[""] - 1); 393 | 394 | foreach ($path as $chr => $num) 395 | { 396 | if ($chr === "") continue; 397 | 398 | if ($num > $pos) 399 | { 400 | $result .= $chr; 401 | $queue[] = $chr; 402 | array_shift($queue); 403 | $len--; 404 | 405 | $state = ($len >= $threshold ? "middle" : "end"); 406 | 407 | $found = true; 408 | 409 | break; 410 | } 411 | 412 | $pos -= $num; 413 | } 414 | } 415 | 416 | if (!$found) $state = "restart"; 417 | } 418 | 419 | break; 420 | } 421 | case "restart": 422 | { 423 | $result .= $separator; 424 | $queue = array(); 425 | $len -= strlen($separator); 426 | 427 | $state = "start"; 428 | 429 | break; 430 | } 431 | } 432 | } 433 | 434 | return $result; 435 | } 436 | 437 | public function GetMode() 438 | { 439 | return $this->mode; 440 | } 441 | 442 | protected static function RNG_Translate() 443 | { 444 | $args = func_get_args(); 445 | if (!count($args)) return ""; 446 | 447 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 448 | } 449 | } 450 | ?> -------------------------------------------------------------------------------- /support/utf_utils.php: -------------------------------------------------------------------------------- 1 | = 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 21 | } 22 | 23 | public static function Convert($data, $srctype, $desttype) 24 | { 25 | $arr = is_array($data); 26 | if ($arr) $srctype = self::UTF32_ARRAY; 27 | $x = 0; 28 | $y = ($arr ? count($data) : strlen($data)); 29 | $result = ($desttype === self::UTF32_ARRAY ? array() : ""); 30 | if (!$arr && $srctype === self::UTF32_ARRAY) return $result; 31 | 32 | $first = true; 33 | 34 | if ($srctype === self::UTF8_BOM) 35 | { 36 | if (substr($data, 0, 3) === "\xEF\xBB\xBF") $x = 3; 37 | 38 | $srctype = self::UTF8; 39 | } 40 | 41 | if ($srctype === self::UTF16_BOM) 42 | { 43 | if (substr($data, 0, 2) === "\xFE\xFF") 44 | { 45 | $srctype = self::UTF16_BE; 46 | $x = 2; 47 | } 48 | else if (substr($data, 0, 2) === "\xFF\xFE") 49 | { 50 | $srctype = self::UTF16_LE; 51 | $x = 2; 52 | } 53 | else 54 | { 55 | $srctype = self::UTF16_LE; 56 | } 57 | } 58 | 59 | if ($srctype === self::UTF32_BOM) 60 | { 61 | if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") 62 | { 63 | $srctype = self::UTF32_BE; 64 | $x = 4; 65 | } 66 | else if (substr($data, 0, 4) === "\xFF\xFE\x00\x00") 67 | { 68 | $srctype = self::UTF32_LE; 69 | $x = 4; 70 | } 71 | else 72 | { 73 | $srctype = self::UTF32_LE; 74 | } 75 | } 76 | 77 | while ($x < $y) 78 | { 79 | // Read the next valid code point. 80 | $val = false; 81 | 82 | switch ($srctype) 83 | { 84 | case self::UTF8: 85 | { 86 | $tempchr = ord($data[$x]); 87 | if ($tempchr <= 0x7F) 88 | { 89 | $val = $tempchr; 90 | $x++; 91 | } 92 | else if ($tempchr < 0xC2) $x++; 93 | else 94 | { 95 | $left = $y - $x; 96 | if ($left < 2) $x++; 97 | else 98 | { 99 | $tempchr2 = ord($data[$x + 1]); 100 | 101 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 102 | { 103 | $val = (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 104 | $x += 2; 105 | } 106 | else if ($left < 3) $x++; 107 | else 108 | { 109 | $tempchr3 = ord($data[$x + 2]); 110 | 111 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) $x++; 112 | else 113 | { 114 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 115 | { 116 | $val = (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 117 | $x += 3; 118 | } 119 | else if ($left < 4) $x++; 120 | else 121 | { 122 | $tempchr4 = ord($data[$x + 3]); 123 | 124 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) $x++; 125 | else if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 126 | { 127 | $val = (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 128 | $x += 4; 129 | } 130 | else 131 | { 132 | $x++; 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | break; 141 | } 142 | case self::UTF16_LE: 143 | { 144 | if ($x + 1 >= $y) $x = $y; 145 | else 146 | { 147 | $val = unpack("v", substr($data, $x, 2))[1]; 148 | $x += 2; 149 | 150 | if ($val >= 0xD800 && $val <= 0xDBFF) 151 | { 152 | if ($x + 1 >= $y) 153 | { 154 | $x = $y; 155 | $val = false; 156 | } 157 | else 158 | { 159 | $val2 = unpack("v", substr($data, $x, 2))[1]; 160 | 161 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 162 | else 163 | { 164 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 165 | $x += 2; 166 | } 167 | } 168 | } 169 | } 170 | 171 | break; 172 | } 173 | case self::UTF16_BE: 174 | { 175 | if ($x + 1 >= $y) $x = $y; 176 | else 177 | { 178 | $val = unpack("n", substr($data, $x, 2))[1]; 179 | $x += 2; 180 | 181 | if ($val >= 0xD800 && $val <= 0xDBFF) 182 | { 183 | if ($x + 1 >= $y) 184 | { 185 | $x = $y; 186 | $val = false; 187 | } 188 | else 189 | { 190 | $val2 = unpack("n", substr($data, $x, 2))[1]; 191 | 192 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 193 | else 194 | { 195 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 196 | $x += 2; 197 | } 198 | } 199 | } 200 | } 201 | 202 | break; 203 | } 204 | case self::UTF32_LE: 205 | { 206 | if ($x + 3 >= $y) $x = $y; 207 | else 208 | { 209 | $val = unpack("V", substr($data, $x, 4))[1]; 210 | $x += 4; 211 | } 212 | 213 | break; 214 | } 215 | case self::UTF32_BE: 216 | { 217 | if ($x + 3 >= $y) $x = $y; 218 | else 219 | { 220 | $val = unpack("N", substr($data, $x, 4))[1]; 221 | $x += 4; 222 | } 223 | 224 | break; 225 | } 226 | case self::UTF32_ARRAY: 227 | { 228 | $val = (int)$data[$x]; 229 | $x++; 230 | 231 | break; 232 | } 233 | default: $x = $y; break; 234 | } 235 | 236 | // Make sure it is a valid Unicode value. 237 | // 0xD800-0xDFFF are for UTF-16 surrogate pairs. Invalid characters. 238 | // 0xFDD0-0xFDEF are non-characters. 239 | // 0x*FFFE and 0x*FFFF are reserved. 240 | // The largest possible character is 0x10FFFF. 241 | // First character can't be a combining code point. 242 | if ($val !== false && !($val < 0 || ($val >= 0xD800 && $val <= 0xDFFF) || ($val >= 0xFDD0 && $val <= 0xFDEF) || ($val & 0xFFFE) == 0xFFFE || $val > 0x10FFFF || ($first && self::IsCombiningCodePoint($val)))) 243 | { 244 | if ($first) 245 | { 246 | if ($desttype === self::UTF8_BOM) 247 | { 248 | $result .= "\xEF\xBB\xBF"; 249 | 250 | $desttype = self::UTF8; 251 | } 252 | 253 | if ($desttype === self::UTF16_BOM) 254 | { 255 | $result .= "\xFF\xFE"; 256 | 257 | $desttype = self::UTF16_LE; 258 | } 259 | 260 | if ($srctype === self::UTF32_BOM) 261 | { 262 | $result .= "\xFF\xFE\x00\x00"; 263 | 264 | $desttype = self::UTF32_LE; 265 | } 266 | 267 | $first = false; 268 | } 269 | 270 | switch ($desttype) 271 | { 272 | case self::UTF8: 273 | { 274 | if ($val <= 0x7F) $result .= chr($val); 275 | else if ($val <= 0x7FF) $result .= chr(0xC0 | ($val >> 6)) . chr(0x80 | ($val & 0x3F)); 276 | else if ($val <= 0xFFFF) $result .= chr(0xE0 | ($val >> 12)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 277 | else if ($val <= 0x10FFFF) $result .= chr(0xF0 | ($val >> 18)) . chr(0x80 | (($val >> 12) & 0x3F)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 278 | 279 | break; 280 | } 281 | case self::UTF16_LE: 282 | { 283 | if ($val <= 0xFFFF) $result .= pack("v", $val); 284 | else 285 | { 286 | $val -= 0x10000; 287 | $result .= pack("v", ((($val >> 10) & 0x3FF) + 0xD800)); 288 | $result .= pack("v", (($val & 0x3FF) + 0xDC00)); 289 | } 290 | 291 | break; 292 | } 293 | case self::UTF16_BE: 294 | { 295 | if ($val <= 0xFFFF) $result .= pack("n", $val); 296 | else 297 | { 298 | $val -= 0x10000; 299 | $result .= pack("n", ((($val >> 10) & 0x3FF) + 0xD800)); 300 | $result .= pack("n", (($val & 0x3FF) + 0xDC00)); 301 | } 302 | 303 | break; 304 | } 305 | case self::UTF32_LE: 306 | { 307 | $result .= pack("V", $val); 308 | 309 | break; 310 | } 311 | case self::UTF32_BE: 312 | { 313 | $result .= pack("N", $val); 314 | 315 | break; 316 | } 317 | case self::UTF32_ARRAY: 318 | { 319 | $result[] = $val; 320 | 321 | break; 322 | } 323 | default: $x = $y; break; 324 | } 325 | } 326 | } 327 | 328 | return $result; 329 | } 330 | 331 | 332 | protected const PUNYCODE_BASE = 36; 333 | protected const PUNYCODE_TMIN = 1; 334 | protected const PUNYCODE_TMAX = 26; 335 | protected const PUNYCODE_SKEW = 38; 336 | protected const PUNYCODE_DAMP = 700; 337 | protected const PUNYCODE_INITIAL_BIAS = 72; 338 | protected const PUNYCODE_INITIAL_N = 0x80; 339 | protected const PUNYCODE_DIGIT_MAP = "abcdefghijklmnopqrstuvwxyz0123456789"; 340 | 341 | public static function ConvertToPunycode($domain) 342 | { 343 | // Reject invalid domain name lengths. 344 | if (strlen($domain) > 255) return false; 345 | 346 | $parts = explode(".", $domain); 347 | 348 | foreach ($parts as $num => $part) 349 | { 350 | // Reject invalid label lengths. 351 | $y = strlen($part); 352 | if ($y > 63) return false; 353 | 354 | // Skip already encoded portions. 355 | if (substr($part, 0, 4) === "xn--") continue; 356 | 357 | // Convert UTF-8 to UTF-32 code points. 358 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 359 | 360 | // Handle ASCII code points. 361 | $part2 = ""; 362 | foreach ($data as $cp) 363 | { 364 | if ($cp <= 0x7F) $part2 .= strtolower(chr($cp)); 365 | } 366 | 367 | $numhandled = strlen($part2); 368 | $y = count($data); 369 | 370 | if ($numhandled >= $y) 371 | { 372 | $parts[$num] = $part2; 373 | 374 | continue; 375 | } 376 | 377 | if ($numhandled) $part2 .= "-"; 378 | 379 | $part2 = "xn--" . $part2; 380 | 381 | if (strlen($part2) > 63) return false; 382 | 383 | $bias = self::PUNYCODE_INITIAL_BIAS; 384 | $n = self::PUNYCODE_INITIAL_N; 385 | $delta = 0; 386 | $first = true; 387 | 388 | while ($numhandled < $y) 389 | { 390 | // Find the next largest unhandled code point. 391 | $cp2 = 0x01000000; 392 | foreach ($data as $cp) 393 | { 394 | if ($cp >= $n && $cp2 > $cp) $cp2 = $cp; 395 | } 396 | 397 | // Increase delta but prevent overflow. 398 | $delta += ($cp2 - $n) * ($numhandled + 1); 399 | if ($delta < 0) return false; 400 | $n = $cp2; 401 | 402 | foreach ($data as $cp) 403 | { 404 | if ($cp < $n) 405 | { 406 | $delta++; 407 | 408 | if ($delta < 0) return false; 409 | } 410 | else if ($cp === $n) 411 | { 412 | // Calculate and encode a variable length integer from the delta. 413 | $q = $delta; 414 | $x = 0; 415 | do 416 | { 417 | $x += self::PUNYCODE_BASE; 418 | 419 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 420 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 421 | else $t = $x - $bias; 422 | 423 | if ($q < $t) break; 424 | 425 | $part2 .= self::PUNYCODE_DIGIT_MAP[$t + (($q - $t) % (self::PUNYCODE_BASE - $t))]; 426 | 427 | $q = (int)(($q - $t) / (self::PUNYCODE_BASE - $t)); 428 | 429 | if (strlen($part2) > 63) return false; 430 | } while (1); 431 | 432 | $part2 .= self::PUNYCODE_DIGIT_MAP[$q]; 433 | if (strlen($part2) > 63) return false; 434 | 435 | // Adapt bias. 436 | $numhandled++; 437 | $bias = self::InternalPunycodeAdapt($delta, $numhandled, $first); 438 | $delta = 0; 439 | $first = false; 440 | } 441 | } 442 | 443 | $delta++; 444 | $n++; 445 | } 446 | 447 | $parts[$num] = $part2; 448 | } 449 | 450 | return implode(".", $parts); 451 | } 452 | 453 | public static function ConvertFromPunycode($domain) 454 | { 455 | // Reject invalid domain name lengths. 456 | if (strlen($domain) > 255) return false; 457 | 458 | $parts = explode(".", $domain); 459 | 460 | foreach ($parts as $num => $part) 461 | { 462 | // Reject invalid label lengths. 463 | $y = strlen($part); 464 | if ($y > 63) return false; 465 | 466 | // Skip unencoded portions. 467 | if (substr($part, 0, 4) !== "xn--") continue; 468 | 469 | $part = substr($part, 4); 470 | 471 | // Convert UTF-8 to UTF-32 code points. 472 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 473 | 474 | // Handle ASCII code points. 475 | $hyphen = ord("-"); 476 | for ($x = count($data); $x && $data[$x - 1] !== $hyphen; $x--); 477 | if (!$x) $data2 = array(); 478 | else 479 | { 480 | $data2 = array_splice($data, 0, $x - 1); 481 | 482 | array_shift($data); 483 | } 484 | 485 | $numhandled = count($data2); 486 | 487 | $bias = self::PUNYCODE_INITIAL_BIAS; 488 | $n = self::PUNYCODE_INITIAL_N; 489 | $delta = 0; 490 | $first = true; 491 | 492 | $pos = 0; 493 | $y = count($data); 494 | while ($pos < $y) 495 | { 496 | // Calculate and decode a delta from the variable length integer. 497 | $olddelta = $delta; 498 | $w = 1; 499 | $x = 0; 500 | do 501 | { 502 | $x += self::PUNYCODE_BASE; 503 | 504 | $cp = $data[$pos]; 505 | $pos++; 506 | 507 | if ($cp >= ord("a") && $cp <= ord("z")) $digit = $cp - ord("a"); 508 | else if ($cp >= ord("A") && $cp <= ord("Z")) $digit = $cp - ord("A"); 509 | else if ($cp >= ord("0") && $cp <= ord("9")) $digit = $cp - ord("0") + 26; 510 | else return false; 511 | 512 | $delta += $digit * $w; 513 | if ($delta < 0) return false; 514 | 515 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 516 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 517 | else $t = $x - $bias; 518 | 519 | if ($digit < $t) break; 520 | 521 | $w *= (self::PUNYCODE_BASE - $t); 522 | if ($w < 0) return false; 523 | } while (1); 524 | 525 | // Adapt bias. 526 | $numhandled++; 527 | $bias = self::InternalPunycodeAdapt($delta - $olddelta, $numhandled, $first); 528 | $first = false; 529 | 530 | // Delta was supposed to wrap around from $numhandled to 0, incrementing $n each time, so fix that now. 531 | $n += (int)($delta / $numhandled); 532 | $delta %= $numhandled; 533 | 534 | // Insert $n (the code point) at the delta position. 535 | array_splice($data2, $delta, 0, array($n)); 536 | $delta++; 537 | } 538 | 539 | $parts[$num] = self::Convert($data2, self::UTF32_ARRAY, self::UTF8); 540 | } 541 | 542 | return implode(".", $parts); 543 | } 544 | 545 | // RFC3492 adapt() function. 546 | protected static function InternalPunycodeAdapt($delta, $numpoints, $first) 547 | { 548 | $delta = ($first ? (int)($delta / self::PUNYCODE_DAMP) : $delta >> 1); 549 | $delta += (int)($delta / $numpoints); 550 | 551 | $y = self::PUNYCODE_BASE - self::PUNYCODE_TMIN; 552 | 553 | $condval = (int)(($y * self::PUNYCODE_TMAX) / 2); 554 | for ($x = 0; $delta > $condval; $x += self::PUNYCODE_BASE) $delta = (int)($delta / $y); 555 | 556 | return (int)($x + ((($y + 1) * $delta) / ($delta + self::PUNYCODE_SKEW))); 557 | } 558 | } 559 | ?> -------------------------------------------------------------------------------- /support/csdb/db_pgsql.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | 27 | // Set Unicode support. 28 | $this->Query("SET", "client_encoding TO 'UTF-8'"); 29 | } 30 | 31 | public function GetVersion() 32 | { 33 | return $this->GetOne("SELECT", array("VERSION()")); 34 | } 35 | 36 | public function GetInsertID($name = null) 37 | { 38 | return $this->lastid; 39 | } 40 | 41 | public function TableExists($name) 42 | { 43 | return ($this->GetOne("SHOW TABLES", array("LIKE" => $name)) === false ? false : true); 44 | } 45 | 46 | public function QuoteIdentifier($str) 47 | { 48 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 49 | } 50 | 51 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 52 | { 53 | switch ($cmd) 54 | { 55 | case "SELECT": 56 | { 57 | $supported = array( 58 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 59 | "FROM" => array("SUBQUERIES" => true), 60 | "WHERE" => array("SUBQUERIES" => true), 61 | "GROUP BY" => true, 62 | "HAVING" => true, 63 | "ORDER BY" => true, 64 | "LIMIT" => " OFFSET " 65 | ); 66 | 67 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 68 | } 69 | case "INSERT": 70 | { 71 | $supported = array( 72 | "PREINTO" => array(), 73 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 74 | "SELECT" => true, 75 | "BULKINSERT" => true 76 | ); 77 | 78 | // To get the last insert ID via GetInsertID(), the field that contains a 'serial' (auto increment) field must be specified. 79 | if (isset($queryinfo["AUTO INCREMENT"])) $queryinfo["RETURNING"] = $queryinfo["AUTO INCREMENT"]; 80 | 81 | $this->lastid = 0; 82 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 83 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 84 | 85 | return $result; 86 | } 87 | case "UPDATE": 88 | { 89 | // No ORDER BY or LIMIT support. 90 | $supported = array( 91 | "PRETABLE" => array("ONLY" => "bool"), 92 | "WHERE" => array("SUBQUERIES" => true) 93 | ); 94 | 95 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 96 | } 97 | case "DELETE": 98 | { 99 | // No ORDER BY or LIMIT support. 100 | $supported = array( 101 | "PREFROM" => array("ONLY" => "bool"), 102 | "WHERE" => array("SUBQUERIES" => true) 103 | ); 104 | 105 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 106 | } 107 | case "SET": 108 | { 109 | $sql = "SET " . $queryinfo; 110 | 111 | return array("success" => true); 112 | } 113 | case "USE": 114 | { 115 | // Fake multiple databases with PostgreSQL schemas. 116 | // http://www.postgresql.org/docs/7.3/static/ddl-schemas.html 117 | $sql = "SET search_path TO " . ($queryinfo != "" ? $this->QuoteIdentifier($queryinfo) . "," : "") . "\"\$user\",public"; 118 | 119 | return array("success" => true); 120 | } 121 | case "CREATE DATABASE": 122 | { 123 | $master = true; 124 | 125 | $sql = "CREATE SCHEMA " . $this->QuoteIdentifier($queryinfo[0]); 126 | 127 | return array("success" => true); 128 | } 129 | case "DROP DATABASE": 130 | { 131 | $master = true; 132 | 133 | $sql = "DROP SCHEMA " . $this->QuoteIdentifier($queryinfo[0]); 134 | 135 | return array("success" => true); 136 | } 137 | case "CREATE TABLE": 138 | { 139 | // UTF-8 support has to be declared at the database (not schema or table) level. 140 | // CREATE DATABASE whatever WITH ENCODING 'UTF8'; 141 | // See also: http://stackoverflow.com/questions/9961795/ 142 | $supported = array( 143 | "TEMPORARY" => "CREATE TEMPORARY TABLE", 144 | "AS_SELECT" => true, 145 | "PRE_AS" => array(), 146 | "PROCESSKEYS" => true, 147 | "POSTCREATE" => array() 148 | ); 149 | 150 | $result = $this->ProcessCREATE_TABLE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 151 | if (!$result["success"]) return $result; 152 | 153 | // Handle named keys and fulltext searches. 154 | $sql = array($sql); 155 | $opts = array($opts); 156 | if (isset($queryinfo[2]) && is_array($queryinfo[2])) 157 | { 158 | foreach ($queryinfo[2] as $info) 159 | { 160 | if (isset($info["NAME"])) 161 | { 162 | if (strtoupper($info[0]) == "KEY" || strtoupper($info[0]) == "FULLTEXT") 163 | { 164 | $sql2 = ""; 165 | $opts2 = array(); 166 | $result = $this->GenerateSQL($master, $sql2, $opts2, "ADD INDEX", array($queryinfo[0], $info), array(), false); 167 | if (!$result["success"]) return $result; 168 | 169 | $sql[] = $sql2; 170 | $opts[] = $opts2; 171 | } 172 | } 173 | } 174 | } 175 | 176 | return $result; 177 | } 178 | case "ADD COLUMN": 179 | { 180 | $master = true; 181 | 182 | $result = $this->ProcessColumnDefinition($queryinfo[2]); 183 | if (!$result["success"]) return $result; 184 | 185 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " ADD COLUMN " . $this->QuoteIdentifier($queryinfo[1]) . " " . $result["sql"]; 186 | 187 | return array("success" => true); 188 | } 189 | case "DROP COLUMN": 190 | { 191 | $master = true; 192 | 193 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " DROP COLUMN " . $this->QuoteIdentifier($queryinfo[1]); 194 | 195 | return array("success" => true); 196 | } 197 | case "ADD INDEX": 198 | { 199 | $master = true; 200 | 201 | $keyinfo = $queryinfo[1]; 202 | $type = strtoupper($keyinfo[0]); 203 | foreach ($keyinfo[1] as $num => $field) $keyinfo[1][$num] = $this->QuoteIdentifier($field); 204 | 205 | if (!isset($keyinfo["NAME"])) return array("success" => false, "errorcode" => "skip_sql_query"); 206 | 207 | if ($type == "PRIMARY" || $type == "UNIQUE") $sql = "CREATE UNIQUE INDEX "; 208 | else if ($type == "FOREIGN") return array("success" => false, "errorcode" => "skip_sql_query"); 209 | else $sql = "CREATE INDEX "; 210 | 211 | $sql .= $this->QuoteIdentifier($keyinfo["NAME"]) . " ON " . $this->QuoteIdentifier($queryinfo[0]) . ($type == "FULLTEXT" ? " USING GIN" : "") . " (" . implode(", ", $keyinfo[1]) . ")"; 212 | 213 | return array("success" => true); 214 | } 215 | case "DROP INDEX": 216 | { 217 | $master = true; 218 | 219 | if (!isset($queryinfo[2])) return array("success" => false, "errorcode" => "skip_sql_query"); 220 | 221 | $sql = "DROP INDEX " . $this->QuoteIdentifier($queryinfo[2]); 222 | 223 | return array("success" => true); 224 | } 225 | case "TRUNCATE TABLE": 226 | { 227 | $master = true; 228 | 229 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 230 | 231 | return array("success" => true); 232 | } 233 | case "DROP TABLE": 234 | { 235 | $master = true; 236 | 237 | $sql = "DROP TABLE " . $this->QuoteIdentifier($queryinfo[0]); 238 | if (isset($queryinfo["RESTRICT"]) && $queryinfo["RESTRICT"]) $sql .= " RESTRICT"; 239 | else if (isset($queryinfo["CASCADE"]) && $queryinfo["CASCADE"]) $sql .= " CASCADE"; 240 | 241 | return array("success" => true); 242 | } 243 | case "SHOW DATABASES": 244 | { 245 | // Not a perfect query. Any schema starting with 'pg_' will be skipped. 246 | $sql = "SELECT schema_name AS name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg_%' AND schema_name <> 'information_schema'"; 247 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 248 | 249 | return array("success" => true, "filter_opts" => array("mode" => "SHOW DATABASES", "queryinfo" => $queryinfo)); 250 | } 251 | case "SHOW TABLES": 252 | { 253 | $sql = "SELECT " . (isset($queryinfo["FULL"]) && $queryinfo["FULL"] ? "*" : "table_name AS name") . " FROM information_schema.tables WHERE (table_schema = ? OR table_type = 'LOCAL TEMPORARY')" . (isset($queryinfo["LIKE"]) ? " AND table_name LIKE ?" : "") . " ORDER BY table_name"; 254 | $opts[] = (isset($queryinfo["FROM"]) ? $queryinfo["FROM"] : ($this->currdb !== false ? $this->currdb : "public")); 255 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 256 | if (isset($queryinfo["LIKE"])) $opts[] = $queryinfo["LIKE"]; 257 | 258 | return array("success" => true, "filter_opts" => array("mode" => "SHOW TABLES", "queryinfo" => $queryinfo)); 259 | } 260 | case "SHOW CREATE DATABASE": 261 | { 262 | // Not a perfect query. Any schema starting with 'pg_' will be skipped. 263 | $sql = "SELECT schema_name AS name FROM information_schema.schemata WHERE schema_name NOT LIKE 'pg_%' AND schema_name <> 'information_schema' AND schema_name = ?"; 264 | $opts[] = $queryinfo[0]; 265 | 266 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE DATABASE")); 267 | } 268 | case "SHOW CREATE TABLE": 269 | { 270 | $sql = "SELECT table_schema, table_name, table_type FROM information_schema.tables WHERE (table_schema = ? OR table_type = 'LOCAL TEMPORARY') AND table_name = ?"; 271 | $opts[] = ($this->currdb !== false ? $this->currdb : "public"); 272 | $opts[] = $queryinfo[0]; 273 | 274 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE TABLE", "hints" => (isset($queryinfo["EXPORT HINTS"]) ? $queryinfo["EXPORT HINTS"] : array()))); 275 | } 276 | case "BULK IMPORT MODE": 277 | { 278 | $master = true; 279 | 280 | if ($queryinfo) $sql = "SET synchronous_commit TO OFF"; 281 | else $sql = "SET synchronous_commit TO DEFAULT"; 282 | 283 | return array("success" => true); 284 | } 285 | } 286 | 287 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 288 | } 289 | 290 | protected function RunStatementFilter(&$stmt, &$filteropts) 291 | { 292 | if ($filteropts["mode"] == "INSERT") 293 | { 294 | // Force the last ID value to be extracted for INSERT queries. 295 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 296 | $row = $result->NextRow(); 297 | 298 | $stmt = false; 299 | } 300 | 301 | parent::RunStatementFilter($stmt, $filteropts); 302 | } 303 | 304 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 305 | { 306 | switch ($filteropts["mode"]) 307 | { 308 | case "INSERT": 309 | { 310 | if ($row !== false) 311 | { 312 | $key = $filteropts["queryinfo"]["AUTO INCREMENT"]; 313 | $this->lastid = $row->$key; 314 | } 315 | 316 | break; 317 | } 318 | case "SHOW CREATE DATABASE": 319 | { 320 | if ($row !== false) 321 | { 322 | $row->cmd = "CREATE DATABASE"; 323 | $row->opts = array($row->name); 324 | } 325 | 326 | break; 327 | } 328 | case "SHOW CREATE TABLE": 329 | { 330 | if ($row !== false) 331 | { 332 | $cols = array(); 333 | $result2 = $this->Query("SELECT", array( 334 | "*", 335 | "FROM" => "information_schema.columns", 336 | "WHERE" => "table_schema = ? AND table_name = ?", 337 | "ORDER BY" => "ordinal_position" 338 | ), $row->table_schema, $row->table_name); 339 | while ($row2 = $result2->NextRow()) 340 | { 341 | if (isset($filteropts["hints"]) && isset($filteropts["hints"][$row2->column_name])) $col = $filteropts["hints"][$row2->column_name]; 342 | else 343 | { 344 | $row2->data_type = str_replace(array(" with timezone", "without timezone", "[]"), "", $row2->data_type); 345 | 346 | switch ($row2->data_type) 347 | { 348 | case "integer": $col = array("INTEGER", 4); break; 349 | case "bigint": $col = array("INTEGER", 8); break; 350 | case "smallint": $col = array("INTEGER", 2); break; 351 | case "real": $col = array("FLOAT", 4); break; 352 | case "double precision": $col = array("FLOAT", 8); break; 353 | case "numeric": $col = array("DECIMAL", $row2->numeric_precision, $row2->numeric_scale); break; 354 | case "\"char\"": $col = array("STRING", 1, $row2->character_maximum_length, "FIXED" => true); break; 355 | case "character varying": $col = array("STRING", 1, $row2->character_maximum_length); break; 356 | case "text": $col = array("STRING", 4); break; 357 | case "bytea": $col = array("BINARY", 4); break; 358 | case "date": $col = array("DATE"); break; 359 | case "time": $col = array("TIME"); break; 360 | case "timestamp": $col = array("DATETIME"); break; 361 | case "boolean": $col = array("BOOLEAN"); break; 362 | 363 | default: return; 364 | } 365 | 366 | if ($row2->is_nullable == "NO") $col["NOT NULL"] = true; 367 | if (isset($row2->column_default)) 368 | { 369 | if (strtolower(substr($row2->column_default, 0, 8)) == "nextval(" && strtolower(substr($row2->column_default, -11)) == "::regclass)") $col["AUTO INCREMENT"] = true; 370 | else $col["DEFAULT"] = $row2->column_default; 371 | } 372 | } 373 | 374 | $cols[$row2->column_name] = $col; 375 | } 376 | 377 | // Process indexes. 378 | $lastindex = 0; 379 | $keys = array(); 380 | $result2 = $this->Query("SELECT", array( 381 | "c.oid, c.relname, a.attname, a.attnum, i.indisprimary, i.indisunique", 382 | "FROM" => "pg_index AS i, pg_class AS c, pg_attribute AS a", 383 | "WHERE" => "i.indexrelid = c.oid AND i.indexrelid = a.attrelid AND i.indrelid = " . $this->Quote($this->QuoteIdentifier($row->table_schema) . "." . $this->QuoteIdentifier($row->table_name)) . "::regclass", 384 | "ORDER BY" => "c.oid, a.attnum" 385 | )); 386 | while ($row2 = $result2->NextRow()) 387 | { 388 | if ($lastindex != $row2->oid) 389 | { 390 | if ($lastindex > 0) $keys[] = $key; 391 | 392 | // FULLTEXT index extraction is missing. Feel free to submit a patch. 393 | if ($row2->indisprimary) $type = "PRIMARY"; 394 | else if ($row2->indisunique) $type = "UNIQUE"; 395 | else $type = "KEY"; 396 | 397 | $key = array($type, array(), "NAME" => $row2->relname); 398 | $lastindex = $row2->oid; 399 | } 400 | 401 | $key[1][] = $row2->attname; 402 | } 403 | if ($lastindex > 0) $keys[] = $key; 404 | 405 | // Process foreign keys? It would be nice to see some examples. 406 | 407 | // Generate the final CREATE TABLE information. 408 | $row->cmd = "CREATE TABLE"; 409 | $row->opts = array($row->table_name, $cols); 410 | if (count($keys)) $row->opts[] = $keys; 411 | if ($row->table_type == "LOCAL TEMPORARY") $row->opts["TEMPORARY"] = true; 412 | } 413 | 414 | break; 415 | } 416 | } 417 | 418 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 419 | } 420 | 421 | protected function ProcessColumnDefinition($info) 422 | { 423 | $sql = ""; 424 | $type = strtoupper($info[0]); 425 | switch ($type) 426 | { 427 | case "INTEGER": 428 | { 429 | $varbytes = (isset($info[1]) && (int)$info[1] > 0 ? (int)$info[1] : 4); 430 | if (isset($info["AUTO INCREMENT"]) && $info["AUTO INCREMENT"]) 431 | { 432 | if ($varbytes < 4) $sql .= " SERIAL"; 433 | else $sql .= " BIGSERIAL"; 434 | } 435 | else 436 | { 437 | if ($varbytes < 3) $sql .= " SMALLINT"; 438 | else if ($varbytes < 5) $sql .= " INTEGER"; 439 | else $sql .= " BIGINT"; 440 | 441 | // No UNSIGNED support in PostgreSQL. 442 | } 443 | 444 | break; 445 | } 446 | case "FLOAT": 447 | { 448 | $varbytes = (isset($info[1]) ? (int)$info[1] : 4); 449 | if ($varbytes <= 4) $sql .= " REAL"; 450 | else $sql .= " DOUBLE PRECISION"; 451 | 452 | break; 453 | } 454 | case "DECIMAL": 455 | { 456 | $sql .= " NUMERIC"; 457 | if (isset($info[1])) $sql .= "(" . $info[1] . (isset($info[2]) ? ", " . $info[2] : "") . ")"; 458 | 459 | break; 460 | } 461 | case "STRING": 462 | { 463 | $varbytes = $info[1]; 464 | if ($varbytes == 1) 465 | { 466 | if (isset($info["FIXED"]) && $info["FIXED"]) $sql .= " CHAR(" . $info[2] . ")"; 467 | else $sql .= " VARCHAR(" . $info[2] . ")"; 468 | } 469 | else 470 | { 471 | $sql .= " TEXT"; 472 | } 473 | 474 | break; 475 | } 476 | case "BINARY": $sql .= " BYTEA"; break; 477 | case "DATE": $sql .= " DATE"; break; 478 | case "TIME": $sql .= " TIME"; break; 479 | case "DATETIME": $sql .= " TIMESTAMP"; break; 480 | case "BOOLEAN": $sql .= " BOOLEAN"; break; 481 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown column type '%s'.", $type), "errorcode" => "unknown_column_type"); 482 | } 483 | 484 | if (isset($info["NOT NULL"]) && $info["NOT NULL"]) $sql .= " NOT NULL"; 485 | if (isset($info["DEFAULT"])) $sql .= " DEFAULT " . $this->Quote($info["DEFAULT"]); 486 | if (isset($info["PRIMARY KEY"]) && $info["PRIMARY KEY"]) $sql .= " PRIMARY KEY"; 487 | if (isset($info["UNIQUE KEY"]) && $info["UNIQUE KEY"]) $sql .= " UNIQUE"; 488 | if (isset($info["REFERENCES"])) $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info["REFERENCES"]); 489 | 490 | return array("success" => true, "sql" => $sql); 491 | } 492 | 493 | protected function ProcessKeyDefinition($info) 494 | { 495 | $sql = ""; 496 | $type = strtoupper($info[0]); 497 | foreach ($info[1] as $num => $field) $info[1][$num] = $this->QuoteIdentifier($field); 498 | switch ($type) 499 | { 500 | case "PRIMARY": 501 | { 502 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 503 | $sql .= "PRIMARY KEY"; 504 | $sql .= " (" . implode(", ", $info[1]) . ")"; 505 | if (isset($info["OPTION"])) $sql .= " " . $info["OPTION"]; 506 | 507 | break; 508 | } 509 | case "KEY": 510 | { 511 | // PostgreSQL CREATE TABLE doesn't support regular KEY indexes, but ADD INDEX does. 512 | 513 | break; 514 | } 515 | case "UNIQUE": 516 | { 517 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 518 | $sql .= "UNIQUE"; 519 | $sql .= " (" . implode(", ", $info[1]) . ")"; 520 | if (isset($info["OPTION"])) $sql .= " " . $info["OPTION"]; 521 | 522 | break; 523 | } 524 | case "FULLTEXT": 525 | { 526 | // PostgreSQL CREATE TABLE doesn't support regular FULLTEXT indexes, but ADD INDEX does. 527 | 528 | break; 529 | } 530 | case "FOREIGN": 531 | { 532 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 533 | $sql .= "FOREIGN KEY"; 534 | $sql .= " (" . implode(", ", $info[1]) . ")"; 535 | $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info[2]); 536 | 537 | break; 538 | } 539 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown key type '%s'.", $type), "errorcode" => "unknown_key_type");; 540 | } 541 | 542 | return array("success" => true, "sql" => $sql); 543 | } 544 | } 545 | ?> -------------------------------------------------------------------------------- /support/csdb/db_sqlite.php: -------------------------------------------------------------------------------- 1 | dbprefix = ""; 24 | 25 | parent::Connect($dsn, $username, $password, $options); 26 | } 27 | 28 | public function GetVersion() 29 | { 30 | return $this->GetOne("SELECT", array("SQLITE_VERSION()")); 31 | } 32 | 33 | public function GetInsertID($name = null) 34 | { 35 | return $this->GetOne("SELECT", array("LAST_INSERT_ROWID()")); 36 | } 37 | 38 | public function TableExists($name) 39 | { 40 | return ($this->GetOne("SHOW TABLES", array("LIKE" => (strpos($name, "__") === false ? $this->dbprefix : "") . $name)) === false ? false : true); 41 | } 42 | 43 | public function QuoteIdentifier($str) 44 | { 45 | return "\"" . str_replace(array("\"", "?"), array("\"\"", ""), $str) . "\""; 46 | } 47 | 48 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 49 | { 50 | switch ($cmd) 51 | { 52 | case "SELECT": 53 | { 54 | $supported = array( 55 | "DBPREFIX" => $this->dbprefix, 56 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 57 | "FROM" => array("SUBQUERIES" => true), 58 | "WHERE" => array("SUBQUERIES" => true), 59 | "GROUP BY" => true, 60 | "HAVING" => true, 61 | "ORDER BY" => true, 62 | "LIMIT" => ", " 63 | ); 64 | 65 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 66 | } 67 | case "INSERT": 68 | { 69 | $supported = array( 70 | "DBPREFIX" => $this->dbprefix, 71 | "PREINTO" => array("LOW_PRIORITY" => "bool", "DELAYED" => "bool", "HIGH_PRIORITY" => "bool", "IGNORE" => "bool"), 72 | "SELECT" => true, 73 | "BULKINSERT" => true, 74 | "BULKINSERTLIMIT" => 900, 75 | ); 76 | 77 | return $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 78 | } 79 | case "UPDATE": 80 | { 81 | $supported = array( 82 | "DBPREFIX" => $this->dbprefix, 83 | "PRETABLE" => array("LOW_PRIORITY" => "bool", "IGNORE" => "bool"), 84 | "WHERE" => array("SUBQUERIES" => true), 85 | "ORDER BY" => true, 86 | "LIMIT" => ", " 87 | ); 88 | 89 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 90 | } 91 | case "DELETE": 92 | { 93 | $supported = array( 94 | "DBPREFIX" => $this->dbprefix, 95 | "PREFROM" => array("LOW_PRIORITY" => "bool", "QUICK" => "bool", "IGNORE" => "bool"), 96 | "WHERE" => array("SUBQUERIES" => true), 97 | "ORDER BY" => true, 98 | "LIMIT" => ", " 99 | ); 100 | 101 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 102 | } 103 | case "SET": 104 | case "CREATE DATABASE": 105 | { 106 | return array("success" => false, "errorcode" => "skip_sql_query"); 107 | } 108 | case "USE": 109 | { 110 | $this->dbprefix = $this->GetDBPrefix($queryinfo); 111 | 112 | return array("success" => false, "errorcode" => "skip_sql_query"); 113 | } 114 | case "DROP DATABASE": 115 | { 116 | $master = true; 117 | 118 | $result = $this->Query("SHOW TABLES", array("FROM" => $queryinfo)); 119 | while ($row = $result->NextRow()) 120 | { 121 | $this->Query("DROP TABLE", array($row->name, "FROM" => $queryinfo)); 122 | } 123 | 124 | return array("success" => false, "errorcode" => "skip_sql_query"); 125 | } 126 | case "CREATE TABLE": 127 | { 128 | $supported = array( 129 | "DBPREFIX" => $this->dbprefix, 130 | "TEMPORARY" => "CREATE TEMPORARY TABLE", 131 | "AS_SELECT" => true, 132 | "PROCESSKEYS" => true, 133 | "POSTCREATE" => array() 134 | ); 135 | 136 | $result = $this->ProcessCREATE_TABLE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 137 | if (!$result["success"]) return $result; 138 | 139 | // Handle named keys. 140 | $sql = array($sql); 141 | $opts = array($opts); 142 | if (isset($queryinfo[2]) && is_array($queryinfo[2])) 143 | { 144 | foreach ($queryinfo[2] as $info) 145 | { 146 | if (strtoupper($info[0]) == "KEY" && isset($info["NAME"])) 147 | { 148 | $sql2 = ""; 149 | $opts2 = array(); 150 | $result = $this->GenerateSQL($master, $sql2, $opts2, "ADD INDEX", array($queryinfo[0], $info), array(), false); 151 | if (!$result["success"]) return $result; 152 | 153 | $sql[] = $sql2; 154 | $opts[] = $opts2; 155 | } 156 | } 157 | } 158 | 159 | return $result; 160 | } 161 | case "ADD COLUMN": 162 | { 163 | $master = true; 164 | 165 | $result = $this->ProcessColumnDefinition($queryinfo[2]); 166 | if (!$result["success"]) return $result; 167 | 168 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " ADD COLUMN " . $this->QuoteIdentifier($queryinfo[1]) . " " . $result["sql"]; 169 | 170 | return array("success" => true); 171 | } 172 | case "PRAGMA": 173 | { 174 | // PRAGMA support exists solely for SQLite DROP COLUMN. 175 | $master = true; 176 | 177 | $sql = "PRAGMA " . $queryinfo; 178 | 179 | return array("success" => true); 180 | } 181 | case "DROP COLUMN": 182 | { 183 | // Has to be emulated by creating a temporary table without the column. 184 | // Triggers, views, and foreign keys will probably be lost. If you care, then submit a patch. 185 | $master = true; 186 | 187 | $row = $this->GetRow("SHOW CREATE TABLE", array($queryinfo[0])); 188 | if (!$row) return array("success" => false, "error" => CSDB::DB_Translate("An error occurred while getting existing table information."), "errorcode" => "show_create_table_error"); 189 | 190 | if (!isset($row->opts[1][$queryinfo[1]])) return array("success" => false, "errorcode" => "skip_sql_query"); 191 | 192 | unset($row->opts[1][$queryinfo[1]]); 193 | 194 | if (!count($row->opts[1])) return array("success" => false, "error" => CSDB::DB_Translate("Table contains only one column."), "errorcode" => "drop_column_error"); 195 | 196 | $opts2 = $row->opts; 197 | $opts2[0] = "csdb__dropcol"; 198 | $opts2["TEMPORARY"] = true; 199 | 200 | $cols = array_keys($opts2[1]); 201 | $this->Query("CREATE TABLE", $opts2); 202 | $this->Query("INSERT", array("csdb__dropcol", $cols, "SELECT" => array(array($cols, "?"), $queryinfo[0]))); 203 | $this->Query("DROP TABLE", array($queryinfo[0])); 204 | $this->Query("CREATE TABLE", $row->opts); 205 | $this->Query("INSERT", array($queryinfo[0], $cols, "SELECT" => array(array($cols, "?"), "csdb__dropcol"))); 206 | $this->Query("DROP TABLE", array("csdb__dropcol")); 207 | 208 | return array("success" => false, "errorcode" => "skip_sql_query"); 209 | } 210 | case "ADD INDEX": 211 | { 212 | $master = true; 213 | 214 | $keyinfo = $queryinfo[1]; 215 | $type = strtoupper($keyinfo[0]); 216 | foreach ($keyinfo[1] as $num => $field) $keyinfo[1][$num] = $this->QuoteIdentifier($field); 217 | 218 | if (!isset($keyinfo["NAME"])) return array("success" => false, "errorcode" => "skip_sql_query"); 219 | 220 | if ($type == "PRIMARY" || $type == "UNIQUE") $sql = "CREATE UNIQUE INDEX "; 221 | else if ($type == "FOREIGN") return array("success" => false, "errorcode" => "skip_sql_query"); 222 | else $sql = "CREATE INDEX "; 223 | 224 | $sql .= $this->QuoteIdentifier($this->dbprefix . $queryinfo[0] . "__" . $keyinfo["NAME"]) . " ON " . $this->QuoteIdentifier($this->dbprefix . $queryinfo[0]) . " (" . implode(", ", $keyinfo[1]) . ")"; 225 | 226 | return array("success" => true); 227 | } 228 | case "DROP INDEX": 229 | { 230 | $master = true; 231 | 232 | if (!isset($queryinfo[2])) return array("success" => false, "errorcode" => "skip_sql_query"); 233 | 234 | $sql = "DROP INDEX " . $this->QuoteIdentifier($this->dbprefix . $queryinfo[2]); 235 | 236 | return array("success" => true); 237 | } 238 | case "TRUNCATE TABLE": 239 | { 240 | $supported = array( 241 | "DBPREFIX" => $this->dbprefix, 242 | "PREFROM" => array() 243 | ); 244 | 245 | $queryinfo = array($queryinfo[0]); 246 | 247 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 248 | } 249 | case "DROP TABLE": 250 | { 251 | $master = true; 252 | 253 | $dbprefix = (isset($queryinfo["FROM"]) ? $this->GetDBPrefix($queryinfo["FROM"]) : $this->dbprefix); 254 | $sql = "DROP TABLE " . $this->QuoteIdentifier($dbprefix . $queryinfo[0]); 255 | 256 | return array("success" => true); 257 | } 258 | case "SHOW DATABASES": 259 | { 260 | // Swiped from: http://www.sqlite.org/faq.html#q7 261 | $sql = "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' ORDER BY name"; 262 | if (isset($queryinfo[0])) $queryinfo[0] = $this->GetDBPrefix($queryinfo[0]); 263 | 264 | return array("success" => true, "filter_opts" => array("mode" => "SHOW DATABASES", "queryinfo" => $queryinfo, "names" => array())); 265 | } 266 | case "SHOW TABLES": 267 | { 268 | // Swiped from: http://www.sqlite.org/faq.html#q7 269 | $sql = "SELECT " . (isset($queryinfo["FULL"]) && $queryinfo["FULL"] ? "*" : "name") . " FROM (SELECT *, 'normal' AS tbl_type FROM sqlite_master UNION ALL SELECT *, 'temp' AS tbl_type FROM sqlite_temp_master) WHERE type = 'table'" . (isset($queryinfo["LIKE"]) ? " AND name LIKE ?" : "") . " ORDER BY name"; 270 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 271 | if (isset($queryinfo["LIKE"])) $opts[] = $queryinfo["LIKE"]; 272 | 273 | return array("success" => true, "filter_opts" => array("mode" => "SHOW TABLES", "queryinfo" => $queryinfo)); 274 | } 275 | case "SHOW CREATE DATABASE": 276 | { 277 | $sql = "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' ORDER BY name"; 278 | $queryinfo[0] = (string)@substr((isset($queryinfo[0]) ? $this->GetDBPrefix($queryinfo[0]) : $this->dbprefix), 0, -2); 279 | 280 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE DATABASE", "output" => false, "queryinfo" => $queryinfo)); 281 | } 282 | case "SHOW CREATE TABLE": 283 | { 284 | $sql = "SELECT tbl_type, sql FROM (SELECT *, 'normal' AS tbl_type FROM sqlite_master UNION ALL SELECT *, 'temp' AS tbl_type FROM sqlite_temp_master) WHERE type = 'table' AND name = ?"; 285 | 286 | $opts = array($this->dbprefix . $queryinfo[0]); 287 | 288 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE TABLE", "output" => false, "queryinfo" => $queryinfo, "hints" => (isset($queryinfo["EXPORT HINTS"]) ? $queryinfo["EXPORT HINTS"] : array()))); 289 | } 290 | case "BULK IMPORT MODE": 291 | { 292 | $master = true; 293 | 294 | if ($queryinfo) 295 | { 296 | $sql = array( 297 | "PRAGMA synchronous=OFF", 298 | "PRAGMA journal_mode=MEMORY", 299 | "PRAGMA temp_store=MEMORY", 300 | ); 301 | 302 | $opts = array( 303 | array(), 304 | array(), 305 | array(), 306 | ); 307 | } 308 | else 309 | { 310 | $sql = array( 311 | "PRAGMA synchronous=NORMAL", 312 | "PRAGMA journal_mode=DELETE", 313 | "PRAGMA temp_store=DEFAULT", 314 | ); 315 | 316 | $opts = array( 317 | array(), 318 | array(), 319 | array(), 320 | ); 321 | } 322 | 323 | return array("success" => true); 324 | } 325 | } 326 | 327 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 328 | } 329 | 330 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 331 | { 332 | switch ($filteropts["mode"]) 333 | { 334 | case "SHOW DATABASES": 335 | { 336 | if ($row !== false) 337 | { 338 | $name = $row->name; 339 | $pos = strpos($name, "__"); 340 | if ($pos === false) $name = ""; 341 | else $name = substr($name, 0, $pos); 342 | 343 | if (isset($filteropts["names"][$name])) $fetchnext = true; 344 | else 345 | { 346 | $filteropts["names"][$name] = true; 347 | $row->name = $name; 348 | } 349 | } 350 | 351 | break; 352 | } 353 | case "SHOW TABLES": 354 | { 355 | if ($row !== false) 356 | { 357 | $dbprefix = (isset($filteropts["queryinfo"]["FROM"]) ? $this->GetDBPrefix($filteropts["queryinfo"]["FROM"]) : $this->dbprefix); 358 | $name = $row->name; 359 | 360 | if ($dbprefix == "" && strpos($name, "__") !== false) $fetchnext = true; 361 | else if ($dbprefix != "" && strtolower(substr($name, 0, strlen($dbprefix))) !== strtolower($dbprefix)) $fetchnext = true; 362 | else 363 | { 364 | $row->name = substr($name, strlen($dbprefix)); 365 | $row->tbl_name = $row->name; 366 | } 367 | } 368 | 369 | break; 370 | } 371 | case "SHOW CREATE DATABASE": 372 | { 373 | if ($row !== false) 374 | { 375 | $name = $row->name; 376 | $pos = strpos($name, "__"); 377 | if ($pos === false) $name = ""; 378 | else $name = substr($name, 0, $pos); 379 | 380 | if ($filteropts["output"] || $name == "" || $name !== $filteropts["queryinfo"][0]) $fetchnext = true; 381 | else 382 | { 383 | $row->name = $name; 384 | $row->cmd = "CREATE DATABASE"; 385 | $row->opts = array($name); 386 | 387 | $filteropts["output"] = true; 388 | } 389 | } 390 | 391 | break; 392 | } 393 | case "SHOW CREATE TABLE": 394 | { 395 | if ($row !== false) 396 | { 397 | if ($filteropts["output"]) $fetchnext = true; 398 | else 399 | { 400 | $dbprefix = ($row->tbl_type == "temp" ? "temp." : ""); 401 | 402 | // Process columns according to: http://sqlite.org/datatype3.html 403 | $cols = array(); 404 | $result2 = $this->Query("PRAGMA", $dbprefix . "table_info(" . $this->QuoteIdentifier($this->dbprefix . $filteropts["queryinfo"][0]) . ")"); 405 | while ($row2 = $result2->NextRow()) 406 | { 407 | if (isset($filteropts["hints"]) && isset($filteropts["hints"][$row2->name])) $col = $filteropts["hints"][$row2->name]; 408 | else 409 | { 410 | if (stripos($row2->type, "INT") !== false) $col = array("INTEGER", 8); 411 | else if (stripos($row2->type, "CHAR") !== false || stripos($row2->type, "CLOB") !== false || stripos($row2->type, "TEXT") !== false) 412 | { 413 | $col = array("STRING", 4); 414 | 415 | $pos = strpos($row2->type, "("); 416 | if ($pos !== false) 417 | { 418 | $pos2 = strpos($row2->type, ")", $pos); 419 | if ($pos2 !== false) 420 | { 421 | $num = (int)substr($row2->type, $pos + 1, $pos2 - $pos - 1); 422 | if ($num > 0 && $num < 256) $col = array("STRING", 1, $num); 423 | } 424 | } 425 | } 426 | else if (stripos($row2->type, "BLOB") !== false || $row2->type === "") $col = array("BINARY", 4); 427 | else if (stripos($row2->type, "REAL") !== false || stripos($row2->type, "FLOA") !== false || stripos($row2->type, "DOUB") !== false) $col = array("FLOAT", 8); 428 | else 429 | { 430 | $col = array("NUMERIC"); 431 | $pos = strpos($row2->type, "("); 432 | if ($pos !== false) 433 | { 434 | $pos2 = strpos($row2->type, ")", $pos); 435 | if ($pos2 !== false) 436 | { 437 | $nums = explode(",", substr($row2->type, $pos + 1, $pos2 - $pos - 1)); 438 | $num = (int)trim($nums[0]); 439 | if ($num > 0) $col[] = $num; 440 | if (count($nums) > 1) 441 | { 442 | $num = (int)trim($nums[1]); 443 | if ($num > 0) $col[] = $num; 444 | } 445 | } 446 | } 447 | } 448 | 449 | if ($row2->notnull > 0) $col["NOT NULL"] = true; 450 | if (isset($row2->dflt_value)) $col["DEFAULT"] = $row2->dflt_value; 451 | if ($row2->pk > 0) 452 | { 453 | $col["PRIMARY KEY"] = true; 454 | if ($col[0] == "INTEGER") $col["AUTO INCREMENT"] = true; 455 | } 456 | } 457 | 458 | $cols[$row2->name] = $col; 459 | } 460 | 461 | // Process indexes. 462 | $keys = array(); 463 | $result2 = $this->Query("PRAGMA", $dbprefix . "index_list(" . $this->QuoteIdentifier($this->dbprefix . $filteropts["queryinfo"][0]) . ")"); 464 | while ($row2 = $result2->NextRow()) 465 | { 466 | $cols2 = array(); 467 | $result3 = $this->Query("PRAGMA", $dbprefix . "index_info(" . $this->QuoteIdentifier($row2->name) . ")"); 468 | while ($row3 = $result3->NextRow()) 469 | { 470 | $cols2[] = $row3->name; 471 | } 472 | 473 | if (substr($row2->name, 0, strlen($this->dbprefix . $filteropts["queryinfo"][0]) + 2) == $this->dbprefix . $filteropts["queryinfo"][0] . "__") $row2->name = substr($row2->name, strlen($this->dbprefix . $filteropts["queryinfo"][0]) + 2); 474 | 475 | $keys[] = array(($row2->unique > 0 ? "UNIQUE" : "KEY"), $cols2, "NAME" => $row2->name); 476 | } 477 | 478 | // Process foreign keys? It would be nice to see some output of foreign_key_list(). 479 | 480 | $row->cmd = "CREATE TABLE"; 481 | $row->opts = array($filteropts["queryinfo"][0], $cols); 482 | if (count($keys)) $row->opts[] = $keys; 483 | if ($row->tbl_type == "temp") $row->opts["TEMPORARY"] = true; 484 | $row->opts["CHARACTER SET"] = "utf8"; 485 | 486 | $filteropts["output"] = true; 487 | } 488 | } 489 | 490 | break; 491 | } 492 | } 493 | 494 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 495 | } 496 | 497 | protected function ProcessColumnDefinition($info) 498 | { 499 | // SQLite is seriously limited here with just four data types. 500 | $sql = ""; 501 | $type = strtoupper($info[0]); 502 | switch ($type) 503 | { 504 | case "INTEGER": $sql .= " INTEGER"; break; 505 | case "FLOAT": $sql .= " REAL"; break; 506 | case "DECIMAL": 507 | { 508 | $sql .= " NUMERIC"; 509 | if (isset($info[1])) $sql .= "(" . $info[1] . (isset($info[2]) ? ", " . $info[2] : "") . ")"; 510 | 511 | break; 512 | } 513 | case "STRING": 514 | { 515 | $varbytes = $info[1]; 516 | if ($varbytes == 1) $sql .= " TEXT(" . $info[2] . ")"; 517 | else $sql .= " TEXT"; 518 | 519 | break; 520 | } 521 | case "BINARY": $sql .= " BLOB"; break; 522 | case "DATE": $sql .= " TEXT"; break; 523 | case "TIME": $sql .= " TEXT"; break; 524 | case "DATETIME": $sql .= " TEXT"; break; 525 | case "BOOLEAN": $sql .= " INTEGER"; break; 526 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown column type '%s'.", $type), "errorcode" => "unknown_column_type"); 527 | } 528 | 529 | if (isset($info["NOT NULL"]) && $info["NOT NULL"]) $sql .= " NOT NULL"; 530 | if (isset($info["DEFAULT"])) $sql .= " DEFAULT " . $this->Quote($info["DEFAULT"]); 531 | if (isset($info["PRIMARY KEY"]) && $info["PRIMARY KEY"]) 532 | { 533 | $sql .= " PRIMARY KEY"; 534 | if (isset($info["AUTO INCREMENT"]) && $info["AUTO INCREMENT"]) $sql .= " AUTOINCREMENT"; 535 | } 536 | if (isset($info["UNIQUE KEY"]) && $info["UNIQUE KEY"]) $sql .= " UNIQUE"; 537 | if (isset($info["REFERENCES"])) $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info["REFERENCES"]); 538 | 539 | return array("success" => true, "sql" => $sql); 540 | } 541 | 542 | protected function ProcessKeyDefinition($info) 543 | { 544 | $sql = ""; 545 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 546 | $type = strtoupper($info[0]); 547 | foreach ($info[1] as $num => $field) $info[1][$num] = $this->QuoteIdentifier($field); 548 | switch ($type) 549 | { 550 | case "PRIMARY": 551 | { 552 | $sql .= "PRIMARY KEY"; 553 | $sql .= " (" . implode(", ", $info[1]) . ")"; 554 | 555 | break; 556 | } 557 | case "KEY": 558 | { 559 | // SQLite CREATE TABLE doesn't support regular KEY indexes, but ALTER TABLE does. 560 | 561 | break; 562 | } 563 | case "UNIQUE": 564 | { 565 | $sql .= "UNIQUE"; 566 | $sql .= " (" . implode(", ", $info[1]) . ")"; 567 | 568 | break; 569 | } 570 | case "FULLTEXT": 571 | { 572 | // SQLite doesn't support FULLTEXT indexes. 573 | break; 574 | } 575 | case "FOREIGN": 576 | { 577 | $sql .= "FOREIGN KEY"; 578 | $sql .= " (" . implode(", ", $info[1]) . ")"; 579 | $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info[2]); 580 | 581 | break; 582 | } 583 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown key type '%s'.", $type), "errorcode" => "unknown_key_type");; 584 | } 585 | 586 | return array("success" => true, "sql" => $sql); 587 | } 588 | 589 | private function GetDBPrefix($str) 590 | { 591 | $str = preg_replace('/\s+/', "_", trim(str_replace("_", " ", $str))); 592 | 593 | return ($str != "" ? $str . "__" : ""); 594 | } 595 | } 596 | ?> -------------------------------------------------------------------------------- /support/csdb/db_oci.php: -------------------------------------------------------------------------------- 1 | lastid = 0; 43 | 44 | parent::Connect($dsn, $username, $password, $options); 45 | 46 | // Convert DB NULL values into empty strings for use in code. 47 | $this->dbobj->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING); 48 | 49 | // Converts all uppercase table names into lowercase table names. 50 | $this->dbobj->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 51 | 52 | // To use CLOBs, try: PDO::ATTR_STRINGIFY_FETCHES => true 53 | // Source: https://github.com/yiisoft/yii2/issues/3167 54 | 55 | // Set Oracle session variables to use standard date formats. 56 | $this->Query("SET", "NLS_DATE_FORMAT='YYYY-MM-DD'"); 57 | $this->Query("SET", "NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'"); 58 | 59 | // Set Unicode support. 60 | // TODO: Figure out unicode support for Oracle 61 | //$this->Query("SET", "NLS_LANGUAGE='UTF8'"); 62 | } 63 | 64 | public function GetVersion() 65 | { 66 | $tableExists = $this->TableExists("SSO_USER"); 67 | 68 | return $this->GetOne("SELECT",array( 69 | "banner", 70 | "FROM" => "v\$version", 71 | "WHERE" => "banner LIKE 'Oracle%'" 72 | )); 73 | } 74 | 75 | public function GetInsertID($name = null) 76 | { 77 | return $this->lastid; 78 | } 79 | 80 | public function TableExists($name) 81 | { 82 | return ($this->GetOne("SHOW TABLES", array("LIKE" => $name)) === false ? false : true); 83 | } 84 | 85 | public function QuoteIdentifier($str) 86 | { 87 | return preg_replace('/[^A-Za-z0-9_]/', "_", $str); 88 | } 89 | 90 | // This function is used to get the last inserted sequence value by table name. 91 | // 92 | // Uses automatically geneerated sequences as part of the Oracle 12c IDENTITY 93 | // column. This is not available in 11g and older Oracle databases. 94 | // See the ProcessColumnDefinition() function for more detail. 95 | private function GetOracleInsertID($tableName) 96 | { 97 | // Query the "all_tab_columns" for the oracle IDENTITY column and identify the sequence. 98 | $seqName = $this->GetOne("SELECT", array( 99 | "data_default", 100 | "FROM" => "all_tab_columns", 101 | "WHERE" => "identity_column = 'YES' AND table_name = ?" 102 | ), strtoupper($tableName)); 103 | 104 | // The previous query returned "nextval" with the sequence name. 105 | // However, we need the current sequence value. 106 | $seqName = str_replace(".nextval", ".CURRVAL", $seqName); 107 | 108 | // This grabs the current value from the sequence identified above. 109 | $retVal = $this->GetOne("SELECT", array( 110 | $seqName, 111 | "FROM" => "DUAL" 112 | )); 113 | 114 | // Return the current sequence value. 115 | return $retVal; 116 | } 117 | 118 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 119 | { 120 | switch ($cmd) 121 | { 122 | case "SELECT": 123 | { 124 | $supported = array( 125 | "PRECOLUMN" => array("DISTINCT" => "bool", "SUBQUERIES" => true), 126 | "FROM" => array("SUBQUERIES" => true), 127 | "WHERE" => array("SUBQUERIES" => true), 128 | "GROUP BY" => true, 129 | "HAVING" => true, 130 | "ORDER BY" => true, 131 | // Haven't figured out the LIMIT problem yet 132 | // TODO: Figure out how to use Oracle's ROWNUM where clause functionalitty 133 | // instead of the LIMIT function 134 | // Probably best to fake this on versions before Oracle 12c. 135 | // Oracle 12c and later supports this: OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; 136 | //"LIMIT" => " OFFSET " 137 | ); 138 | 139 | // Oracle does not support aliasing table names in the FROM clause. 140 | // However, alias' are supported in COLUMN names. 141 | // AS is used in the Oracle FROM clause to process nested queries, 142 | // but does not support alias'. 143 | $queryinfo["FROM"] = str_replace("? AS ", "? ", $queryinfo["FROM"]); 144 | 145 | return $this->ProcessSELECT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 146 | } 147 | case "INSERT": 148 | { 149 | $supported = array( 150 | "PREINTO" => array(), 151 | "POSTVALUES" => array("RETURNING" => "key_identifier"), 152 | "SELECT" => true, 153 | "BULKINSERT" => false 154 | ); 155 | 156 | $result = $this->ProcessINSERT($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 157 | if ($result["success"] && isset($queryinfo["AUTO INCREMENT"])) $result["filter_opts"] = array("mode" => "INSERT", "queryinfo" => $queryinfo); 158 | 159 | // Handle bulk insert by rewriting the queries because, well, Oracle. 160 | // http://stackoverflow.com/questions/39576/best-way-to-do-multi-row-insert-in-oracle 161 | if ($result["success"] && is_array($sql)) 162 | { 163 | $sql2 = "INSERT ALL"; 164 | foreach ($sql as $entry) $sql2 .= substr($entry, 6); 165 | $sql2 .= " SELECT 1 FROM DUAL"; 166 | $sql = $sql2; 167 | } 168 | 169 | return $result; 170 | } 171 | case "UPDATE": 172 | { 173 | // No ORDER BY or LIMIT support. 174 | $supported = array( 175 | "PRETABLE" => array("ONLY" => "bool"), 176 | "WHERE" => array("SUBQUERIES" => true) 177 | ); 178 | 179 | return $this->ProcessUPDATE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 180 | } 181 | case "DELETE": 182 | { 183 | // No ORDER BY or LIMIT support. 184 | $supported = array( 185 | "PREFROM" => array("ONLY" => "bool"), 186 | "WHERE" => array("SUBQUERIES" => true) 187 | ); 188 | 189 | return $this->ProcessDELETE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 190 | } 191 | case "SET": 192 | { 193 | $sql = "ALTER SESSION SET " . $queryinfo; 194 | 195 | return array("success" => true); 196 | } 197 | 198 | case "USE": 199 | { 200 | // Fake multiple databases with Oracle schemas. 201 | // SCHEMA is already selected with user. 202 | // $sql = "SELECT 1 FROM DUAL"; 203 | 204 | return array("success" => false, "errorcode" => "skip_sql_query"); 205 | } 206 | case "CREATE DATABASE": 207 | { 208 | // Fake creating a databse which isn't needed with oracle. 209 | 210 | return array("success" => false, "errorcode" => "skip_sql_query"); 211 | } 212 | case "DROP DATABASE": 213 | { 214 | // Fake dropping a database which isn't needed with oracle. 215 | 216 | return array("success" => false, "errorcode" => "skip_sql_query"); 217 | } 218 | 219 | case "CREATE TABLE": 220 | { 221 | // A random PostgreSQL reference...is this comment relevant to Oracle/OCI? 222 | // UTF-8 support has to be declared at the database (not schema or table) level. 223 | // CREATE DATABASE whatever WITH ENCODING 'UTF8'; 224 | // See also: http://stackoverflow.com/questions/9961795/ 225 | $supported = array( 226 | "TEMPORARY" => "CREATE TEMPORARY TABLE", 227 | "AS_SELECT" => true, 228 | "PRE_AS" => array(), 229 | "PROCESSKEYS" => true, 230 | "POSTCREATE" => array() 231 | ); 232 | 233 | $result = $this->ProcessCREATE_TABLE($master, $sql, $opts, $queryinfo, $args, $subquery, $supported); 234 | if (!$result["success"]) return $result; 235 | 236 | // Handle named keys and fulltext searches. 237 | $sql = array($sql); 238 | $opts = array($opts); 239 | if (isset($queryinfo[2]) && is_array($queryinfo[2])) 240 | { 241 | foreach ($queryinfo[2] as $info) 242 | { 243 | if (isset($info["NAME"])) 244 | { 245 | if (strtoupper($info[0]) == "KEY" || strtoupper($info[0]) == "FULLTEXT") 246 | { 247 | $sql2 = ""; 248 | $opts2 = array(); 249 | $result = $this->GenerateSQL($master, $sql2, $opts2, "ADD INDEX", array($queryinfo[0], $info), array(), false); 250 | if (!$result["success"]) return $result; 251 | 252 | $sql[] = $sql2; 253 | $opts[] = $opts2; 254 | } 255 | } 256 | } 257 | } 258 | 259 | return $result; 260 | } 261 | case "ADD COLUMN": 262 | { 263 | $master = true; 264 | 265 | $result = $this->ProcessColumnDefinition($queryinfo[2]); 266 | if (!$result["success"]) return $result; 267 | 268 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " ADD COLUMN " . $this->QuoteIdentifier($queryinfo[1]) . " " . $result["sql"]; 269 | 270 | return array("success" => true); 271 | } 272 | case "DROP COLUMN": 273 | { 274 | $master = true; 275 | 276 | $sql = "ALTER TABLE " . $this->QuoteIdentifier($queryinfo[0]) . " DROP COLUMN " . $this->QuoteIdentifier($queryinfo[1]); 277 | 278 | return array("success" => true); 279 | } 280 | case "ADD INDEX": 281 | { 282 | $master = true; 283 | 284 | $keyinfo = $queryinfo[1]; 285 | $type = strtoupper($keyinfo[0]); 286 | foreach ($keyinfo[1] as $num => $field) $keyinfo[1][$num] = $this->QuoteIdentifier($field); 287 | 288 | if (!isset($keyinfo["NAME"])) return array("success" => false, "errorcode" => "skip_sql_query"); 289 | 290 | if ($type == "PRIMARY" || $type == "UNIQUE") $sql = "CREATE UNIQUE INDEX "; 291 | else if ($type == "FOREIGN") return array("success" => false, "errorcode" => "skip_sql_query"); 292 | else $sql = "CREATE INDEX "; 293 | 294 | $sql .= $this->QuoteIdentifier($keyinfo["NAME"]) . " ON " . $this->QuoteIdentifier($queryinfo[0]) . ($type == "FULLTEXT" ? " USING GIN" : "") . " (" . implode(", ", $keyinfo[1]) . ")"; 295 | 296 | return array("success" => true); 297 | } 298 | case "DROP INDEX": 299 | { 300 | $master = true; 301 | 302 | if (!isset($queryinfo[2])) return array("success" => false, "errorcode" => "skip_sql_query"); 303 | 304 | $sql = "DROP INDEX " . $this->QuoteIdentifier($queryinfo[2]); 305 | 306 | return array("success" => true); 307 | } 308 | case "TRUNCATE TABLE": 309 | { 310 | $master = true; 311 | 312 | $sql = "TRUNCATE TABLE " . $this->QuoteIdentifier($queryinfo[0]); 313 | 314 | return array("success" => true); 315 | } 316 | case "DROP TABLE": 317 | { 318 | $master = true; 319 | 320 | $sql = "DROP TABLE " . $this->QuoteIdentifier($queryinfo[0]); 321 | if (isset($queryinfo["RESTRICT"]) && $queryinfo["RESTRICT"]) $sql .= " RESTRICT"; 322 | else if (isset($queryinfo["CASCADE"]) && $queryinfo["CASCADE"]) $sql .= " CASCADE"; 323 | 324 | return array("success" => true); 325 | } 326 | case "SHOW DATABASES": 327 | { 328 | // Not a perfect query. Will show a list of owners (users), which in oracle are considered schemas 329 | $sql = "SELECT DISTINCT owner AS name FROM all_tables WHERE owner NOT IN ('MDSYS', 'SYSTEM', 'XDB', 'SYS')"; 330 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 331 | 332 | return array("success" => true, "filter_opts" => array("mode" => "SHOW DATABASES", "queryinfo" => $queryinfo)); 333 | } 334 | case "SHOW TABLES": 335 | { 336 | $sql = "SELECT " . (isset($queryinfo["FULL"]) && $queryinfo["FULL"] ? "*" : "table_name AS name") . " FROM all_tables" . (isset($queryinfo["LIKE"]) ? " WHERE table_name LIKE ?" : "") . " ORDER BY table_name"; 337 | if (isset($queryinfo[0]) && $queryinfo[0] == "") unset($queryinfo[0]); 338 | if (isset($queryinfo["LIKE"])) $opts[] = $queryinfo["LIKE"]; 339 | 340 | return array("success" => true, "filter_opts" => array("mode" => "SHOW TABLES", "queryinfo" => $queryinfo)); 341 | } 342 | case "SHOW CREATE DATABASE": 343 | { 344 | // Not a perfect query. Will show a list of owners (users), which in oracle are considered schemas 345 | $sql = "SELECT DISTINCT owner AS name FROM all_tables WHERE owner NOT IN ('MDSYS', 'SYSTEM', 'XDB', 'SYS') AND owner = ?"; 346 | $opts[] = $queryinfo[0]; 347 | 348 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE DATABASE")); 349 | } 350 | case "SHOW CREATE TABLE": 351 | { 352 | $sql = "SELECT DISTINCT owner AS table_schema, table_name, temporary AS table_type FROM all_tables WHERE owner = ? AND table_name = ?"; 353 | $opts[] = ($this->currdb !== false ? $this->currdb : "public"); 354 | $opts[] = $queryinfo[0]; 355 | 356 | return array("success" => true, "filter_opts" => array("mode" => "SHOW CREATE TABLE", "hints" => (isset($queryinfo["EXPORT HINTS"]) ? $queryinfo["EXPORT HINTS"] : array()))); 357 | } 358 | case "BULK IMPORT MODE": 359 | { 360 | $master = true; 361 | 362 | return array("success" => false, "errorcode" => "skip_sql_query"); 363 | } 364 | } 365 | 366 | return array("success" => false, "error" => CSDB::DB_Translate("Unknown query command '%s'.", $cmd), "errorcode" => "unknown_query_command"); 367 | } 368 | 369 | protected function RunStatementFilter(&$stmt, &$filteropts) 370 | { 371 | if ($filteropts["mode"] == "INSERT") 372 | { 373 | // Force the last ID value to be extracted for INSERT queries. 374 | // Unable to find a way to get Oracle to return a row without 375 | // Using PL/SQL functions. 376 | $result = new CSDB_PDO_Statement($this, $stmt, $filteropts); 377 | $row = $result->NextRow(); 378 | 379 | $stmt = false; 380 | } 381 | 382 | parent::RunStatementFilter($stmt, $filteropts); 383 | } 384 | 385 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 386 | { 387 | switch ($filteropts["mode"]) 388 | { 389 | case "INSERT": 390 | { 391 | // Use the private function provided above to get the Last Inserted ID 392 | $this->lastid = $this->GetOracleInsertID($filteropts["queryinfo"][0]); 393 | 394 | break; 395 | } 396 | case "SHOW CREATE DATABASE": 397 | { 398 | if ($row !== false) 399 | { 400 | $row->cmd = "CREATE DATABASE"; 401 | $row->opts = array($row->name); 402 | } 403 | 404 | break; 405 | } 406 | case "SHOW CREATE TABLE": 407 | { 408 | /* 409 | TODO: Update the column types for Oracle 410 | if ($row !== false) 411 | { 412 | $cols = array(); 413 | $result2 = $this->Query("SELECT", array( 414 | "*", 415 | "FROM" => "all_tab_columns", 416 | "WHERE" => " table_name = ?", 417 | "ORDER BY" => "column_id" 418 | ), $row->table_name); 419 | while ($row2 = $result2->NextRow()) 420 | { 421 | if (isset($filteropts["hints"]) && isset($filteropts["hints"][$row2->column_name])) $col = $filteropts["hints"][$row2->column_name]; 422 | else 423 | { 424 | $row2->data_type = str_replace(array(" with timezone", "without timezone", "[]"), "", $row2->data_type); 425 | 426 | switch (strtoupper($type)) 427 | { 428 | case "BIT": 429 | { 430 | $opts2 = array("INTEGER"); 431 | if (!count($typeopts) || $typeopts[0] <= 8) $opts2[] = 1; 432 | else if ($typeopts[0] <= 16) $opts2[] = 2; 433 | else if ($typeopts[0] <= 24) $opts2[] = 3; 434 | else if ($typeopts[0] <= 32) $opts2[] = 4; 435 | else $opts2[] = 8; 436 | 437 | break; 438 | } 439 | case "TINYINT": $opts2 = array("INTEGER", 1); $extras = array("UNSIGNED" => "keep", "ZEROFILL" => "ignore"); break; 440 | case "SMALLINT": $opts2 = array("INTEGER", 2); $extras = array("UNSIGNED" => "keep", "ZEROFILL" => "ignore"); break; 441 | case "MEDIUMINT": $opts2 = array("INTEGER", 3); $extras = array("UNSIGNED" => "keep", "ZEROFILL" => "ignore"); break; 442 | case "INT": case "INTEGER": $opts2 = array("INTEGER", 4); $extras = array("UNSIGNED" => "keep", "ZEROFILL" => "ignore"); break; 443 | case "BIGINT": $opts2 = array("INTEGER", 8); $extras = array("UNSIGNED" => "keep", "ZEROFILL" => "ignore"); break; 444 | 445 | case "DOUBLE": case "REAL": $opts2 = array("FLOAT", 8); $extras = array("UNSIGNED" => "ignore", "ZEROFILL" => "ignore"); break; 446 | case "FLOAT": $opts2 = array("FLOAT", 4); $extras = array("UNSIGNED" => "ignore", "ZEROFILL" => "ignore"); break; 447 | case "DECIMAL": case "NUMERIC": $opts2[] = array("DECIMAL", (count($typeopts) ? $typeopts[0] : 10), (count($typeopts) > 1 ? $typeopts[1] : 0)); $extras = array("UNSIGNED" => "ignore", "ZEROFILL" => "ignore"); break; 448 | 449 | case "DATE": $opts2 = array("DATE"); break; 450 | case "TIME": $opts2 = array("DATE"); break; 451 | case "DATETIME": case "TIMESTAMP": $opts2 = array("DATETIME"); break; 452 | case "YEAR": $opts2 = array("INTEGER", 4); break; 453 | 454 | case "CHAR": $opts2 = array("STRING", 1, (count($typeopts) ? $typeopts[0] : 255), "FIXED" => true); $extras = array("CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 455 | case "VARCHAR": $opts2 = array("STRING", ($typeopts[0] > 255 ? 2 : 1), $typeopts[0]); $extras = array("CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 456 | case "TINYTEXT": $opts2 = array("STRING", 1, 255); $extras = array("BINARY" => "ignore", "CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 457 | case "TEXT": $opts2 = array("STRING", 2); $extras = array("BINARY" => "ignore", "CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 458 | case "MEDIUMTEXT": $opts2 = array("STRING", 3); $extras = array("BINARY" => "ignore", "CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 459 | case "LONGTEXT": $opts2 = array("STRING", 4); $extras = array("BINARY" => "ignore", "CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 460 | case "ENUM": $opts2 = array("STRING", 1, 255); $extras = array("CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 461 | case "SET": $opts2 = array("STRING", 2); $extras = array("CHARACTER SET" => "ignore_with_opt", "COLLATE" => "ignore_with_opt"); break; 462 | 463 | case "BINARY": $opts2 = array("BINARY", 1, (count($typeopts) ? $typeopts[0] : 255), "FIXED" => true); break; 464 | case "VARBINARY": $opts2 = array("BINARY", ($typeopts[0] > 255 ? 2 : 1), $typeopts[0]); break; 465 | case "TINYBLOB": $opts2 = array("BINARY", 1, 255); break; 466 | case "BLOB": $opts2 = array("BINARY", 2); break; 467 | case "MEDIUMBLOB": $opts2 = array("BINARY", 3); break; 468 | case "LONGBLOB": $opts2 = array("BINARY", 4); break; 469 | 470 | default: return; 471 | } 472 | 473 | if ($row2->is_nullable == "NO") $col["NOT NULL"] = true; 474 | if (isset($row2->column_default)) 475 | { 476 | if (strtolower(substr($row2->column_default, 0, 8)) == "nextval(" && strtolower(substr($row2->column_default, -11)) == "::regclass)") $col["AUTO INCREMENT"] = true; 477 | else $col["DEFAULT"] = $row2->column_default; 478 | } 479 | } 480 | 481 | $cols[$row2->column_name] = $col; 482 | } 483 | 484 | // Process indexes. 485 | $lastindex = 0; 486 | $keys = array(); 487 | $result2 = $this->Query("SELECT", array( 488 | "c.oid, c.relname, a.attname, a.attnum, i.indisprimary, i.indisunique", 489 | "FROM" => "pg_index AS i, pg_class AS c, pg_attribute AS a", 490 | "WHERE" => "i.indexrelid = c.oid AND i.indexrelid = a.attrelid AND i.indrelid = " . $this->Quote($this->QuoteIdentifier($row->table_schema) . "." . $this->QuoteIdentifier($row->table_name)) . "::regclass", 491 | "ORDER BY" => "c.oid, a.attnum" 492 | )); 493 | while ($row2 = $result2->NextRow()) 494 | { 495 | if ($lastindex != $row2->oid) 496 | { 497 | if ($lastindex > 0) $keys[] = $key; 498 | 499 | // FULLTEXT index extraction is missing. Feel free to submit a patch. 500 | if ($row2->indisprimary) $type = "PRIMARY"; 501 | else if ($row2->indisunique) $type = "UNIQUE"; 502 | else $type = "KEY"; 503 | 504 | $key = array($type, array(), "NAME" => $row2->relname); 505 | $lastindex = $row2->oid; 506 | } 507 | 508 | $key[1][] = $row2->attname; 509 | } 510 | if ($lastindex > 0) $keys[] = $key; 511 | 512 | // Process foreign keys? It would be nice to see some examples. 513 | 514 | // Generate the final CREATE TABLE information. 515 | $row->cmd = "CREATE TABLE"; 516 | $row->opts = array($row->table_name, $cols); 517 | if (count($keys)) $row->opts[] = $keys; 518 | if ($row->table_type == "LOCAL TEMPORARY") $row->opts["TEMPORARY"] = true; 519 | } 520 | 521 | break; 522 | */ 523 | } 524 | } 525 | 526 | if (!$fetchnext) parent::RunRowFilter($row, $filteropts, $fetchnext); 527 | } 528 | 529 | protected function ProcessColumnDefinition($info) 530 | { 531 | $sql = ""; 532 | $type = strtoupper($info[0]); 533 | switch ($type) 534 | { 535 | case "INTEGER": 536 | { 537 | $varbytes = (isset($info[1]) && (int)$info[1] > 0 ? (int)$info[1] : 4); 538 | 539 | // THIS WILL ONLY WORK WITH ORACLE 12c and later! 540 | // 541 | // The IDENTITY column type is not available in 11g and 542 | // prior databases. It may be possible to make sequences 543 | // and default values work, but all accepted methods for 544 | // auto increment columns in Oracle use TRIGGERS. I couldn't 545 | // figure out a way to create a trigger with a native SQL 546 | // statmenet. It appears that Trigger creation requires PL/SQL 547 | if (isset($info["AUTO INCREMENT"]) && $info["AUTO INCREMENT"]) 548 | { 549 | if ($varbytes < 4) $sql .= " NUMBER(10,0) GENERATED BY DEFAULT ON NULL AS IDENTITY"; 550 | else $sql .= " NUMBER(19,0) GENERATED BY DEFAULT ON NULL AS IDENTITY"; 551 | } 552 | else 553 | { 554 | if ($varbytes == 1) $sql .= " NUMBER(3,0)"; 555 | else if ($varbytes == 2) $sql .= " NUMBER(5,0)"; 556 | else if ($varbytes == 3) $sql .= " NUMBER(7,0)"; 557 | else if ($varbytes == 4) $sql .= " NUMBER(10,0)"; 558 | else $sql .= " NUMBER(19,0)"; 559 | } 560 | 561 | break; 562 | } 563 | case "FLOAT": 564 | { 565 | $varbytes = (isset($info[1]) ? (int)$info[1] : 4); 566 | if ($varbytes <= 4) $sql .= " FLOAT"; 567 | else $sql .= " FLOAT(24)"; 568 | 569 | break; 570 | } 571 | case "DECIMAL": 572 | { 573 | $sql .= " FLOAT(24)"; 574 | 575 | break; 576 | } 577 | case "STRING": 578 | { 579 | $varbytes = $info[1]; 580 | if ($varbytes == 1) 581 | { 582 | if (isset($info["FIXED"]) && $info["FIXED"]) $sql .= " NCHAR2(" . $info[2] . ")"; 583 | else $sql .= " NVARCHAR2(" . $info[2] . ")"; 584 | 585 | // Used NVARCHAR2 so that double-byte characters could be used for internationalization 586 | } 587 | else if ($varbytes == 2) $sql .= " VARCHAR2(4000)"; 588 | else if ($varbytes == 3) $sql .= " VARCHAR2(4000)"; 589 | else $sql .= " VARCHAR2(4000)"; 590 | 591 | // Used VARCHAR2(4000) instead of CLOB because PHP was not reading CLOB data correctly. 592 | // data was being returned as a stream and not a text value. It may be possible to 593 | // use CLOB functionality, however it will need more investigation. 594 | // See comment in Connect() for a possible fix. 595 | // If that fails, this might be able to be handled by using a RunRowFilter() for SELECT queries. Convert the column to a string. 596 | 597 | // Could not use NVARCHAR because the maximum number of characters is 2000, not 4000 598 | // This was not long enough to support the "info" and "info2" columns in SSO 599 | 600 | break; 601 | } 602 | case "BINARY": $sql .= " BLOB"; 603 | case "DATE": $sql .= " DATE"; break; 604 | case "TIME": $sql .= " TIMESTAMP"; break; 605 | case "DATETIME": $sql .= " TIMESTAMP"; break; 606 | case "BOOLEAN": $sql .= " BOOLEAN"; break; 607 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown column type '%s'.", $type), "errorcode" => "unknown_column_type"); 608 | } 609 | 610 | if (isset($info["NOT NULL"]) && $info["NOT NULL"] && $type != "STRING") $sql .= " NOT NULL"; 611 | if (isset($info["DEFAULT"])) $sql .= " DEFAULT " . $this->Quote($info["DEFAULT"]); 612 | if (isset($info["PRIMARY KEY"]) && $info["PRIMARY KEY"]) $sql .= " PRIMARY KEY"; 613 | if (isset($info["UNIQUE KEY"]) && $info["UNIQUE KEY"]) $sql .= " UNIQUE"; 614 | 615 | return array("success" => true, "sql" => $sql); 616 | } 617 | 618 | protected function ProcessKeyDefinition($info) 619 | { 620 | $sql = ""; 621 | $type = strtoupper($info[0]); 622 | foreach ($info[1] as $num => $field) $info[1][$num] = $this->QuoteIdentifier($field); 623 | switch ($type) 624 | { 625 | case "PRIMARY": 626 | { 627 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 628 | $sql .= "PRIMARY KEY"; 629 | $sql .= " (" . implode(", ", $info[1]) . ")"; 630 | if (isset($info["OPTION"])) $sql .= " " . $info["OPTION"]; 631 | 632 | break; 633 | } 634 | case "KEY": 635 | { 636 | // Oracle CREATE TABLE doesn't support regular KEY indexes, but ADD INDEX does. 637 | 638 | break; 639 | } 640 | case "UNIQUE": 641 | { 642 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 643 | $sql .= "UNIQUE"; 644 | $sql .= " (" . implode(", ", $info[1]) . ")"; 645 | if (isset($info["OPTION"])) $sql .= " " . $info["OPTION"]; 646 | 647 | break; 648 | } 649 | case "FULLTEXT": 650 | { 651 | // Oracle CREATE TABLE doesn't support regular FULLTEXT indexes, but ADD INDEX does. 652 | 653 | break; 654 | } 655 | case "FOREIGN": 656 | { 657 | if (isset($info["CONSTRAINT"])) $sql .= "CONSTRAINT " . $info["CONSTRAINT"] . " "; 658 | $sql .= "FOREIGN KEY"; 659 | $sql .= " (" . implode(", ", $info[1]) . ")"; 660 | $sql .= " REFERENCES " . $this->ProcessReferenceDefinition($info[2]); 661 | 662 | break; 663 | } 664 | default: return array("success" => false, "error" => CSDB::DB_Translate("Unknown key type '%s'.", $type), "errorcode" => "unknown_key_type");; 665 | } 666 | 667 | return array("success" => true, "sql" => $sql); 668 | } 669 | } 670 | ?> -------------------------------------------------------------------------------- /support/csdb/db.php: -------------------------------------------------------------------------------- 1 | numqueries = 0; 47 | $this->totaltime = 0; 48 | $this->dbobj = false; 49 | $this->origdbobj = false; 50 | $this->debug = false; 51 | $this->transaction = 0; 52 | $this->mdsn = false; 53 | $this->currdb = false; 54 | $this->available_status = $this->IsAvailable(); 55 | 56 | if ($dsn !== false) $this->Connect($dsn, $username, $password, $options); 57 | } 58 | 59 | public function __destruct() 60 | { 61 | $this->Disconnect(); 62 | } 63 | 64 | public function SetDebug($debug) 65 | { 66 | $this->debug = $debug; 67 | } 68 | 69 | protected function AssertPDOAvailable($checkdb) 70 | { 71 | if (!is_string($this->available_status)) 72 | { 73 | throw new Exception(CSDB::DB_Translate("The driver is not available.")); 74 | exit(); 75 | } 76 | 77 | if ($checkdb && $this->dbobj === false) 78 | { 79 | throw new Exception(CSDB::DB_Translate("Not connected to a database.")); 80 | exit(); 81 | } 82 | } 83 | 84 | public function BeginTransaction() 85 | { 86 | $this->AssertPDOAvailable(true); 87 | 88 | if (!$this->transaction) 89 | { 90 | $this->dbobj->beginTransaction(); 91 | 92 | if (is_resource($this->debug)) fwrite($this->debug, "BEGIN transaction.\n----------\n"); 93 | } 94 | 95 | $this->transaction++; 96 | } 97 | 98 | public function NumTransactions() 99 | { 100 | return $this->transaction; 101 | } 102 | 103 | public function Commit() 104 | { 105 | $this->AssertPDOAvailable(true); 106 | 107 | if ($this->transaction) 108 | { 109 | $this->transaction--; 110 | if (!$this->transaction) 111 | { 112 | $this->dbobj->commit(); 113 | 114 | if (is_resource($this->debug)) fwrite($this->debug, "COMMIT transaction.\n----------\n"); 115 | } 116 | } 117 | } 118 | 119 | public function Rollback() 120 | { 121 | $this->AssertPDOAvailable(true); 122 | 123 | if ($this->transaction) 124 | { 125 | $this->transaction = 0; 126 | $this->dbobj->rollBack(); 127 | 128 | if (is_resource($this->debug)) fwrite($this->debug, "ROLLBACK transaction.\n----------\n"); 129 | } 130 | } 131 | 132 | public function Connect($dsn, $username = false, $password = false, $options = array()) 133 | { 134 | $this->AssertPDOAvailable(false); 135 | 136 | $this->origdbobj = $this->dbobj; 137 | $this->dbobj = false; 138 | $this->mdsn = false; 139 | 140 | $startts = microtime(true); 141 | 142 | if (is_array($dsn)) 143 | { 144 | foreach ($dsn as $key => $val) $dsn[$key] = $key . "=" . $val; 145 | $dsn = $this->available_status . ":" . implode(";", $dsn); 146 | } 147 | 148 | $pos = strpos($dsn, ":"); 149 | $driver = substr($dsn, 0, $pos); 150 | if ($driver !== $this->available_status) 151 | { 152 | throw new Exception(CSDB::DB_Translate("The driver '%s' is invalid. Must be '%s'.", htmlspecialchars($driver), htmlspecialchars($this->available_status))); 153 | exit(); 154 | } 155 | 156 | try 157 | { 158 | if ($password !== false) $this->dbobj = new PDO($dsn, $username, $password); 159 | else if ($username !== false) $this->dbobj = new PDO($dsn, $username); 160 | else $this->dbobj = new PDO($dsn); 161 | } 162 | catch (Exception $e) 163 | { 164 | if (is_resource($this->debug)) fwrite($this->debug, "The connection to the database server failed. " . $e->getMessage() . "\n----------\n"); 165 | 166 | if (is_resource($this->debug) || $this->debug) throw new Exception(CSDB::DB_Translate("The connection to the database server failed. %s", $e->getMessage())); 167 | else throw new Exception(CSDB::DB_Translate("The connection to the database server failed.")); 168 | exit(); 169 | } 170 | 171 | if (is_resource($this->debug)) fwrite($this->debug, "Connected to '" . $dsn . "'.\n----------\n"); 172 | 173 | $this->totaltime += (microtime(true) - $startts); 174 | 175 | return true; 176 | } 177 | 178 | public function SetMaster($dsn, $username = false, $password = false, $options = array()) 179 | { 180 | $this->mdsn = $dsn; 181 | $this->musername = $username; 182 | $this->mpassword = $password; 183 | $this->moptions = $options; 184 | } 185 | 186 | public function Disconnect() 187 | { 188 | $startts = microtime(true); 189 | 190 | while ($this->transaction) $this->Commit(); 191 | 192 | if ($this->dbobj !== false) 193 | { 194 | unset($this->dbobj); 195 | $this->dbobj = false; 196 | } 197 | if ($this->origdbobj !== false) 198 | { 199 | unset($this->origdbobj); 200 | $this->origdbobj = false; 201 | } 202 | 203 | $this->totaltime += (microtime(true) - $startts); 204 | 205 | if (is_resource($this->debug)) fwrite($this->debug, "Disconnected from database.\n\nTotal query time: " . sprintf("%.03f", $this->totaltime) . " seconds\n----------\n"); 206 | 207 | return true; 208 | } 209 | 210 | public function Query() 211 | { 212 | $params = func_get_args(); 213 | return $this->InternalQuery($params); 214 | } 215 | 216 | public function GetRow() 217 | { 218 | $params = func_get_args(); 219 | $dbresult = $this->InternalQuery($params); 220 | if ($dbresult === false) return false; 221 | $row = $dbresult->NextRow(); 222 | unset($dbresult); 223 | 224 | return $row; 225 | } 226 | 227 | public function GetRowArray() 228 | { 229 | $params = func_get_args(); 230 | $dbresult = $this->InternalQuery($params); 231 | if ($dbresult === false) return false; 232 | $row = $dbresult->NextRow(PDO::FETCH_BOTH); 233 | unset($dbresult); 234 | 235 | return $row; 236 | } 237 | 238 | public function GetCol() 239 | { 240 | $result = array(); 241 | 242 | $params = func_get_args(); 243 | $dbresult = $this->InternalQuery($params); 244 | if ($dbresult === false) return false; 245 | while ($row = $dbresult->NextRow(PDO::FETCH_NUM)) 246 | { 247 | $result[] = $row[0]; 248 | } 249 | 250 | return $result; 251 | } 252 | 253 | public function GetOne() 254 | { 255 | $params = func_get_args(); 256 | $dbresult = $this->InternalQuery($params); 257 | if ($dbresult === false) return false; 258 | $row = $dbresult->NextRow(PDO::FETCH_NUM); 259 | unset($dbresult); 260 | if ($row === false) return false; 261 | 262 | return $row[0]; 263 | } 264 | 265 | public function GetVersion() 266 | { 267 | return ""; 268 | } 269 | 270 | public function GetInsertID($name = null) 271 | { 272 | return $this->dbobj->lastInsertId($name); 273 | } 274 | 275 | public function TableExists($name) 276 | { 277 | return false; 278 | } 279 | 280 | public function LargeResults($enable) 281 | { 282 | } 283 | 284 | public function NumQueries() 285 | { 286 | return $this->numqueries; 287 | } 288 | 289 | // Execution time with microsecond precision. 290 | public function ExecutionTime() 291 | { 292 | return $this->totaltime; 293 | } 294 | 295 | private function InternalQuery($params) 296 | { 297 | $startts = microtime(true); 298 | 299 | $cmd = array_shift($params); 300 | if ($cmd !== false) $cmd = strtoupper($cmd); 301 | $queryinfo = array_shift($params); 302 | if (count($params) == 1 && is_array($params[0])) $params = $params[0]; 303 | 304 | if ($cmd === false) 305 | { 306 | $master = true; 307 | $sqls = array((string)$queryinfo); 308 | $opts = array($params); 309 | $filteropts = false; 310 | } 311 | else 312 | { 313 | $master = false; 314 | $sqls = ""; 315 | $opts = array(); 316 | $result = $this->GenerateSQL($master, $sqls, $opts, $cmd, $queryinfo, $params, false); 317 | $filteropts = (isset($result["filter_opts"]) ? $result["filter_opts"] : false); 318 | if (!$result["success"]) 319 | { 320 | if ($result["errorcode"] == "skip_sql_query") return new CSDB_PDO_Statement($this, false, $filteropts); 321 | 322 | if (is_resource($this->debug)) fwrite($this->debug, "Error generating '" . $cmd . "' SQL query. " . $result["error"] . " (" . $result["errorcode"] . ")\n\n" . (is_string($sqls) ? $sqls : var_export($sqls, true)) . "\n----------\n"); 323 | 324 | throw new Exception(CSDB::DB_Translate("Error generating '%s' SQL query. %s (%s)", $cmd, $result["error"], $result["errorcode"])); 325 | exit(); 326 | } 327 | 328 | if ($cmd == "USE") $this->currdb = $queryinfo; 329 | } 330 | 331 | // Switch to master database. 332 | if ($master && $this->mdsn !== false) 333 | { 334 | $numcommit = $this->transaction; 335 | while ($this->transaction) $this->Commit(); 336 | 337 | if (!$this->Connect($this->mdsn, $this->musername, $this->mpassword, $this->moptions)) 338 | { 339 | throw new Exception(CSDB::DB_Translate("The connection to the master database failed.")); 340 | exit(); 341 | } 342 | 343 | if (is_resource($this->debug)) fwrite($this->debug, "Connection to master database succeeded.\n----------\n"); 344 | 345 | if ($this->currdb !== false) $this->Query("USE", $this->currdb); 346 | 347 | while ($this->transaction < $numcommit) $this->BeginTransaction(); 348 | } 349 | 350 | $prepareopts = (isset($result["prepare_opts"]) ? $result["prepare_opts"] : array()); 351 | 352 | if (is_string($sqls)) 353 | { 354 | $sqls = array($sqls); 355 | $opts = array($opts); 356 | } 357 | foreach ($sqls as $num => $sql) 358 | { 359 | $opts2 = $opts[$num]; 360 | 361 | $result = $this->dbobj->prepare($sql, $prepareopts); 362 | if ($result === false) 363 | { 364 | $info = $this->dbobj->errorInfo(); 365 | 366 | if (is_resource($this->debug)) fwrite($this->debug, $info[0] . " " . $info[2] . " (" . $info[1] . ").\nError preparing SQL query:\n\n" . $sql . "\n\n" . var_export($opts2, true) . "\n----------\n"); 367 | 368 | if (is_resource($this->debug) || $this->debug) throw new Exception(CSDB::DB_Translate("%s %s (%s). Error preparing SQL query: %s %s", $info[0], $info[2], $info[1], $sql, var_export($opts2, true))); 369 | else throw new Exception(CSDB::DB_Translate("Error preparing SQL query. %s %s (%s)", $info[0], $info[2], $info[1])); 370 | exit(); 371 | } 372 | if (!$result->execute($opts2)) 373 | { 374 | $info = $result->errorInfo(); 375 | 376 | if (is_resource($this->debug)) fwrite($this->debug, $info[0] . " " . $info[2] . " (" . $info[1] . ").\nError executing SQL query:\n\n" . $sql . "\n\n" . var_export($opts2, true) . "\n----------\n"); 377 | 378 | if (is_resource($this->debug) || $this->debug) throw new Exception(CSDB::DB_Translate("%s %s (%s). Error executing SQL query: %s %s", $info[0], $info[2], $info[1], $sql, var_export($opts2, true))); 379 | else throw new Exception(CSDB::DB_Translate("Error executing SQL query. %s %s (%s)", $info[0], $info[2], $info[1])); 380 | exit(); 381 | } 382 | 383 | $this->numqueries++; 384 | 385 | $diff = (microtime(true) - $startts); 386 | $this->totaltime += $diff; 387 | 388 | if (is_resource($this->debug)) 389 | { 390 | ob_start(); 391 | debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 392 | $info = ob_get_contents(); 393 | ob_end_clean(); 394 | fwrite($this->debug, "Query #" . $this->numqueries . "\n\n" . $sql . "\n\n" . var_export($opts2, true) . "\n\n" . $info . "\nCommand: " . $cmd . "\n\n" . sprintf("%.03f", $diff) . " seconds (" . sprintf("%.03f", $this->totaltime) . " total)\n----------\n"); 395 | } 396 | 397 | if ($filteropts !== false) $this->RunStatementFilter($result, $filteropts); 398 | } 399 | 400 | return new CSDB_PDO_Statement($this, $result, $filteropts); 401 | } 402 | 403 | public function Quote($str, $type = PDO::PARAM_STR) 404 | { 405 | if (is_bool($str) && !$str) return "NULL"; 406 | 407 | return $this->dbobj->quote((string)$str, $type); 408 | } 409 | 410 | public function QuoteIdentifier($str) 411 | { 412 | return ""; 413 | } 414 | 415 | protected function ProcessSubqueries(&$result, &$master, $subqueries) 416 | { 417 | foreach ($subqueries as $num => $subquery) 418 | { 419 | $sql2 = ""; 420 | $opts2 = array(); 421 | $queryinfo2 = array_shift($subquery); 422 | if (count($subquery) == 1 && is_array($subquery[0])) $subquery = $subquery[0]; 423 | $result = $this->GenerateSQL($master, $sql2, $opts2, "SELECT", $queryinfo2, $subquery, true); 424 | if (!$result["success"]) return $result; 425 | 426 | $result = str_replace("{" . $num . "}", "(" . $sql2 . ")", $result); 427 | } 428 | 429 | return array("success" => true); 430 | } 431 | 432 | // Derived classes implement this function. 433 | protected function GenerateSQL(&$master, &$sql, &$opts, $cmd, $queryinfo, $args, $subquery) 434 | { 435 | return array("success" => false, "error" => CSDB::DB_Translate("The base class GenerateSQL() was called."), "errorcode" => "wrong_class_used"); 436 | } 437 | 438 | protected function RunStatementFilter(&$stmt, &$filteropts) 439 | { 440 | } 441 | 442 | // Can't be a 'protected' function since this is called from CSDB_PDO_Statement. 443 | public function RunRowFilter(&$row, &$filteropts, &$fetchnext) 444 | { 445 | if ($row === false) return; 446 | 447 | switch ($filteropts["mode"]) 448 | { 449 | case "SHOW DATABASES": 450 | case "SHOW TABLES": 451 | { 452 | if (isset($row->name) && isset($filteropts["queryinfo"]) && isset($filteropts["queryinfo"][0])) 453 | { 454 | if (stripos($row->name, $filteropts["queryinfo"][0]) === false) $fetchnext = true; 455 | } 456 | 457 | break; 458 | } 459 | case "SELECT EXPORT": 460 | { 461 | if ($row !== false) 462 | { 463 | $opts = array($filteropts["table"], array()); 464 | foreach ($row as $key => $val) 465 | { 466 | $opts[1][$key] = $val; 467 | unset($row->$key); 468 | } 469 | 470 | $row->cmd = "INSERT"; 471 | $row->opts = $opts; 472 | } 473 | 474 | break; 475 | } 476 | } 477 | } 478 | 479 | protected function ProcessColumnDefinition($info) 480 | { 481 | return array("success" => false, "error" => CSDB::DB_Translate("The base class ProcessColumnDefinition() was called."), "errorcode" => "wrong_class_used"); 482 | } 483 | 484 | protected function ProcessKeyDefinition($info) 485 | { 486 | return array("success" => false, "error" => CSDB::DB_Translate("The base class ProcessKeyDefinition() was called."), "errorcode" => "wrong_class_used"); 487 | } 488 | 489 | // Not intended to be overridden, just accessible to the derived class. 490 | protected function ProcessSELECT(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 491 | { 492 | $sql = "SELECT"; 493 | foreach ($supported["PRECOLUMN"] as $key => $mode) 494 | { 495 | if ($key != "SUBQUERIES" && isset($queryinfo[$key])) 496 | { 497 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 498 | } 499 | } 500 | 501 | if (is_array($queryinfo[0])) 502 | { 503 | foreach ($queryinfo[0] as $num => $col) $queryinfo[0][$num] = $this->QuoteIdentifier($col); 504 | $queryinfo[0] = implode(", ", $queryinfo[0]); 505 | } 506 | 507 | if ($supported["PRECOLUMN"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 508 | { 509 | $result = $this->ProcessSubqueries($queryinfo[0], $master, $queryinfo["SUBQUERIES"]); 510 | if (!$result["success"]) return $result; 511 | } 512 | 513 | $sql .= " " . $queryinfo[0]; 514 | 515 | if (isset($queryinfo["FROM"])) $queryinfo[1] = $queryinfo["FROM"]; 516 | 517 | if (isset($queryinfo[1])) 518 | { 519 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 520 | 521 | $pos = strpos($queryinfo[1], "?"); 522 | while ($pos !== false) 523 | { 524 | $queryinfo[1] = substr($queryinfo[1], 0, $pos) . $this->QuoteIdentifier($prefix . array_shift($args)) . substr($queryinfo[1], $pos + 1); 525 | 526 | $pos = strpos($queryinfo[1], "?"); 527 | } 528 | 529 | if ($supported["FROM"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 530 | { 531 | $result = $this->ProcessSubqueries($queryinfo[1], $master, $queryinfo["SUBQUERIES"]); 532 | if (!$result["success"]) return $result; 533 | } 534 | 535 | $sql .= " FROM " . $queryinfo[1]; 536 | } 537 | 538 | if (isset($queryinfo["WHERE"])) 539 | { 540 | if ($supported["WHERE"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 541 | { 542 | $result = $this->ProcessSubqueries($queryinfo["WHERE"], $master, $queryinfo["SUBQUERIES"]); 543 | if (!$result["success"]) return $result; 544 | } 545 | 546 | $sql .= " WHERE " . $queryinfo["WHERE"]; 547 | } 548 | 549 | if (isset($supported["GROUP BY"]) && $supported["GROUP BY"] && isset($queryinfo["GROUP BY"])) $sql .= " GROUP BY " . $queryinfo["GROUP BY"]; 550 | if (isset($supported["HAVING"]) && $supported["HAVING"] && isset($queryinfo["HAVING"])) $sql .= " HAVING " . $queryinfo["HAVING"]; 551 | if (isset($supported["ORDER BY"]) && $supported["ORDER BY"] && isset($queryinfo["ORDER BY"])) $sql .= " ORDER BY " . $queryinfo["ORDER BY"]; 552 | if (isset($supported["LIMIT"]) && $supported["LIMIT"] !== false && !$subquery && isset($queryinfo["LIMIT"])) 553 | { 554 | if (is_array($queryinfo["LIMIT"])) $queryinfo["LIMIT"] = implode($supported["LIMIT"], $queryinfo["LIMIT"]); 555 | $sql .= " LIMIT " . $queryinfo["LIMIT"]; 556 | } 557 | 558 | $opts = $args; 559 | 560 | if (isset($queryinfo["EXPORT ROWS"])) return array("success" => true, "filter_opts" => array("mode" => "SELECT EXPORT", "table" => $queryinfo["EXPORT ROWS"])); 561 | 562 | return array("success" => true); 563 | } 564 | 565 | protected function ProcessINSERT(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 566 | { 567 | $master = true; 568 | 569 | $sql = "INSERT"; 570 | foreach ($supported["PREINTO"] as $key => $mode) 571 | { 572 | if (isset($queryinfo[$key])) 573 | { 574 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 575 | } 576 | } 577 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 578 | $sql .= " INTO " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 579 | 580 | if (isset($queryinfo["FROM"])) $queryinfo[1] = $queryinfo["FROM"]; 581 | 582 | if (isset($queryinfo["SELECT"])) 583 | { 584 | if (!$supported["SELECT"]) return array("success" => false, CSDB::DB_Translate("INSERT INTO SELECT not supported."), "insert_select_unsupported"); 585 | 586 | if (isset($queryinfo[1]) && is_array($queryinfo[1]) && count($queryinfo[1])) 587 | { 588 | $keys = array(); 589 | foreach ($queryinfo[1] as $key) $keys[] = $this->QuoteIdentifier($key); 590 | $sql .= " (" . implode(", ", $keys) . ")"; 591 | } 592 | 593 | $sql2 = ""; 594 | $opts2 = array(); 595 | $queryinfo2 = array_shift($queryinfo["SELECT"]); 596 | if (count($queryinfo["SELECT"]) == 1 && is_array($queryinfo["SELECT"][0])) $queryinfo["SELECT"] = $queryinfo["SELECT"][0]; 597 | $result = $this->GenerateSQL($master, $sql2, $opts2, "SELECT", $queryinfo2, $queryinfo["SELECT"], false); 598 | if (!$result["success"]) return $result; 599 | $sql .= " " . $sql2; 600 | } 601 | else if (isset($queryinfo[1]) && is_array($queryinfo[1]) && count($queryinfo[1])) 602 | { 603 | $keys = array(); 604 | $vals = array(); 605 | foreach ($queryinfo[1] as $key => $val) 606 | { 607 | $keys[] = $this->QuoteIdentifier($key); 608 | $vals[] = "?"; 609 | $args[] = $val; 610 | } 611 | 612 | // Avoid this if possible. 613 | if (isset($queryinfo[2])) 614 | { 615 | foreach ($queryinfo[2] as $key => $val) 616 | { 617 | $keys[] = $this->QuoteIdentifier($key); 618 | $vals[] = $val; 619 | } 620 | } 621 | $sql .= " (" . implode(", ", $keys) . ") VALUES "; 622 | $origsql = $sql; 623 | $sql .= "(" . implode(", ", $vals) . ")"; 624 | 625 | // Handle bulk inserts. 626 | if (isset($queryinfo[3]) && isset($queryinfo[4])) 627 | { 628 | $bulkinsert = (isset($supported["BULKINSERT"]) && $supported["BULKINSERT"]); 629 | $bulkinsertlimit = (isset($supported["BULKINSERTLIMIT"]) ? $supported["BULKINSERTLIMIT"] : false); 630 | $sql = array($sql); 631 | $args = array($args); 632 | $lastpos = 0; 633 | for ($x = 3; isset($queryinfo[$x]) && isset($queryinfo[$x + 1]); $x += 2) 634 | { 635 | if (!$bulkinsert || ($bulkinsertlimit !== false && count($args[$lastpos]) + count($queryinfo[$x]) + count($queryinfo[$x + 1]) >= $bulkinsertlimit)) 636 | { 637 | $sql[] = $origsql; 638 | $args[] = array(); 639 | $lastpos++; 640 | } 641 | else 642 | { 643 | $sql[$lastpos] .= ", "; 644 | } 645 | 646 | $vals = array(); 647 | foreach ($queryinfo[$x] as $key => $val) 648 | { 649 | $vals[] = "?"; 650 | $args[$lastpos][] = $val; 651 | } 652 | 653 | // Avoid this if possible. 654 | foreach ($queryinfo[$x + 1] as $key => $val) $vals[] = $val; 655 | 656 | $sql[$lastpos] .= "(" . implode(", ", $vals) . ")"; 657 | } 658 | } 659 | 660 | if (isset($supported["POSTVALUES"]) && !isset($queryinfo[3])) 661 | { 662 | foreach ($supported["POSTVALUES"] as $key => $mode) 663 | { 664 | if (isset($queryinfo[$key])) 665 | { 666 | if ($mode == "key_identifier" && isset($queryinfo[$key])) $sql .= " " . $key . " " . $this->QuoteIdentifier($queryinfo[$key]); 667 | } 668 | } 669 | } 670 | } 671 | else return array("success" => false, "error" => CSDB::DB_Translate("INSERT command is missing required option or parameter."), "errorcode" => "missing_option_or_parameter"); 672 | 673 | $opts = $args; 674 | 675 | return array("success" => true); 676 | } 677 | 678 | protected function ProcessUPDATE(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 679 | { 680 | $master = true; 681 | 682 | $sql = "UPDATE"; 683 | foreach ($supported["PRETABLE"] as $key => $mode) 684 | { 685 | if (isset($queryinfo[$key])) 686 | { 687 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 688 | } 689 | } 690 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 691 | $sql .= " " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 692 | 693 | $set = array(); 694 | $vals = array(); 695 | foreach ($queryinfo[1] as $key => $val) 696 | { 697 | $set[] = $this->QuoteIdentifier($key) . " = " . (is_bool($val) ? ($val ? "DEFAULT" : "NULL") : "?"); 698 | if (!is_bool($val)) $vals[] = $val; 699 | } 700 | $args = array_merge($vals, $args); 701 | 702 | // Avoid this if possible. 703 | if (isset($queryinfo[2])) 704 | { 705 | foreach ($queryinfo[2] as $key => $val) 706 | { 707 | $set[] = $this->QuoteIdentifier($key) . " = " . $val; 708 | } 709 | } 710 | 711 | $sql .= " SET " . implode(", ", $set); 712 | 713 | if (isset($queryinfo["WHERE"])) 714 | { 715 | if ($supported["WHERE"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 716 | { 717 | $result = $this->ProcessSubqueries($queryinfo["WHERE"], $master, $queryinfo["SUBQUERIES"]); 718 | if (!$result["success"]) return $result; 719 | } 720 | 721 | $sql .= " WHERE " . $queryinfo["WHERE"]; 722 | } 723 | else 724 | { 725 | // Attempt to detect accidental 'WHERE = ...' clauses. 726 | foreach ($queryinfo as $key => $val) 727 | { 728 | if (is_int($key) && is_string($val) && strtoupper(substr($val, 0, 5)) === "WHERE") return array("success" => false, "error" => CSDB::DB_Translate("UPDATE command appears to have a WHERE in a value instead of a key. Query blocked to avoid an unintentional change to the entire table. Did you write 'WHERE something = ...' instead of 'WHERE' => 'something = ...'?"), "errorcode" => "query_blocked_where_clause"); 729 | } 730 | } 731 | 732 | if (isset($supported["ORDER BY"]) && $supported["ORDER BY"] && isset($queryinfo["ORDER BY"])) $sql .= " ORDER BY " . $queryinfo["ORDER BY"]; 733 | if (isset($supported["LIMIT"]) && $supported["LIMIT"] !== false && !$subquery && isset($queryinfo["LIMIT"])) 734 | { 735 | if (is_array($queryinfo["LIMIT"])) $queryinfo["LIMIT"] = implode($supported["LIMIT"], $queryinfo["LIMIT"]); 736 | $sql .= " LIMIT " . $queryinfo["LIMIT"]; 737 | } 738 | 739 | $opts = $args; 740 | 741 | return array("success" => true); 742 | } 743 | 744 | protected function ProcessDELETE(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 745 | { 746 | $master = true; 747 | 748 | $sql = "DELETE"; 749 | foreach ($supported["PREFROM"] as $key => $mode) 750 | { 751 | if (isset($queryinfo[$key])) 752 | { 753 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 754 | } 755 | } 756 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 757 | $sql .= " FROM " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 758 | 759 | if (isset($queryinfo["WHERE"])) 760 | { 761 | if ($supported["WHERE"]["SUBQUERIES"] && isset($queryinfo["SUBQUERIES"])) 762 | { 763 | $result = $this->ProcessSubqueries($queryinfo["WHERE"], $master, $queryinfo["SUBQUERIES"]); 764 | if (!$result["success"]) return $result; 765 | } 766 | 767 | $sql .= " WHERE " . $queryinfo["WHERE"]; 768 | } 769 | else 770 | { 771 | // Attempt to detect accidental 'WHERE = ...' clauses. 772 | foreach ($queryinfo as $key => $val) 773 | { 774 | if (is_int($key) && is_string($val) && strtoupper(substr($val, 0, 5)) === "WHERE") return array("success" => false, "error" => CSDB::DB_Translate("DELETE command appears to have a WHERE in a value instead of a key. Query blocked to avoid an unintentional deletion of all records in the entire table. Did you write 'WHERE something = ...' instead of 'WHERE' => 'something = ...'?"), "errorcode" => "query_blocked_where_clause"); 775 | } 776 | } 777 | 778 | if (isset($supported["ORDER BY"]) && $supported["ORDER BY"] && isset($queryinfo["ORDER BY"])) $sql .= " ORDER BY " . $queryinfo["ORDER BY"]; 779 | if (isset($supported["LIMIT"]) && $supported["LIMIT"] !== false && !$subquery && isset($queryinfo["LIMIT"])) 780 | { 781 | if (is_array($queryinfo["LIMIT"])) $queryinfo["LIMIT"] = implode($supported["LIMIT"], $queryinfo["LIMIT"]); 782 | $sql .= " LIMIT " . $queryinfo["LIMIT"]; 783 | } 784 | 785 | $opts = $args; 786 | 787 | return array("success" => true); 788 | } 789 | 790 | protected function ProcessCREATE_TABLE(&$master, &$sql, &$opts, $queryinfo, $args, $subquery, $supported) 791 | { 792 | $master = true; 793 | 794 | if (isset($supported["TEMPORARY"]) && isset($queryinfo["TEMPORARY"]) && $queryinfo["TEMPORARY"]) $cmd = $supported["TEMPORARY"]; 795 | else $cmd = "CREATE TABLE"; 796 | $prefix = (isset($supported["DBPREFIX"]) ? $supported["DBPREFIX"] : ""); 797 | $sql = $cmd . " " . $this->QuoteIdentifier($prefix . $queryinfo[0]); 798 | 799 | if (isset($queryinfo["SELECT"])) 800 | { 801 | if (!isset($supported["AS_SELECT"]) || !$supported["AS_SELECT"]) return array("success" => false, CSDB::DB_Translate("CREATE TABLE AS SELECT not supported."), "create_table_select_unsupported"); 802 | 803 | $sql2 = ""; 804 | $opts2 = array(); 805 | $queryinfo2 = array_shift($queryinfo["SELECT"]); 806 | if (count($queryinfo["SELECT"]) == 1 && is_array($queryinfo["SELECT"][0])) $queryinfo["SELECT"] = $queryinfo["SELECT"][0]; 807 | $result = $this->GenerateSQL($master, $sql2, $opts2, "SELECT", $queryinfo2, $queryinfo["SELECT"], false); 808 | if (!$result["success"]) return $result; 809 | 810 | if (isset($supported["PRE_AS"])) 811 | { 812 | foreach ($supported["PRE_AS"] as $key => $mode) 813 | { 814 | if (isset($queryinfo[$key])) 815 | { 816 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 817 | } 818 | } 819 | } 820 | 821 | $sql .= " AS " . $sql2; 822 | } 823 | else 824 | { 825 | $sql2 = array(); 826 | foreach ($queryinfo[1] as $key => $info) 827 | { 828 | $sql3 = $this->QuoteIdentifier($key); 829 | $result = $this->ProcessColumnDefinition($info); 830 | if (!$result["success"]) return $result; 831 | 832 | $sql2[] = $sql3 . $result["sql"]; 833 | } 834 | 835 | if (isset($supported["PROCESSKEYS"]) && $supported["PROCESSKEYS"] && isset($queryinfo[2]) && is_array($queryinfo[2])) 836 | { 837 | foreach ($queryinfo[2] as $info) 838 | { 839 | $result = $this->ProcessKeyDefinition($info); 840 | if (!$result["success"]) return $result; 841 | 842 | if ($result["sql"] != "") $sql2[] = $result["sql"]; 843 | } 844 | } 845 | 846 | $sql .= " (\n"; 847 | if (count($sql2)) $sql .= "\t" . implode(",\n\t", $sql2) . "\n"; 848 | $sql .= ")"; 849 | foreach ($supported["POSTCREATE"] as $key => $mode) 850 | { 851 | if (isset($queryinfo[$key])) 852 | { 853 | if ($mode == "bool" && $queryinfo[$key]) $sql .= " " . $key; 854 | else if ($mode == "string") $sql .= " " . $key . " " . $queryinfo[$key]; 855 | } 856 | } 857 | } 858 | 859 | $opts = $args; 860 | 861 | return array("success" => true); 862 | } 863 | 864 | protected function ProcessReferenceDefinition($info) 865 | { 866 | foreach ($info[1] as $num => $colname) $info[1][$num] = $this->QuoteIdentifier($colname); 867 | $sql = $this->QuoteIdentifier($info[0]) . " (" . implode(", ", $info[1]) . ")"; 868 | if (isset($info["MATCH FULL"]) && $info["MATCH FULL"]) $sql .= " MATCH FULL"; 869 | else if (isset($info["MATCH PARTIAL"]) && $info["MATCH PARTIAL"]) $sql .= " MATCH PARTIAL"; 870 | else if (isset($info["MATCH SIMPLE"]) && $info["MATCH SIMPLE"]) $sql .= " MATCH SIMPLE"; 871 | if (isset($info["ON DELETE"])) $sql .= " ON DELETE " . $info["ON DELETE"]; 872 | if (isset($info["ON UPDATE"])) $sql .= " ON UPDATE " . $info["ON UPDATE"]; 873 | 874 | return $sql; 875 | } 876 | 877 | protected static function DB_Translate() 878 | { 879 | $args = func_get_args(); 880 | if (!count($args)) return ""; 881 | 882 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 883 | } 884 | } 885 | 886 | class CSDB_PDO_Statement 887 | { 888 | private $db, $stmt, $filteropts; 889 | 890 | function __construct($db, $stmt, $filteropts) 891 | { 892 | $this->db = $db; 893 | $this->stmt = $stmt; 894 | $this->filteropts = $filteropts; 895 | } 896 | 897 | function __destruct() 898 | { 899 | $this->Free(); 900 | } 901 | 902 | function Free() 903 | { 904 | if ($this->stmt === false) return false; 905 | 906 | $this->stmt = false; 907 | 908 | return true; 909 | } 910 | 911 | function NextRow($fetchtype = PDO::FETCH_OBJ) 912 | { 913 | if ($this->stmt === false && $this->filteropts === false) return false; 914 | 915 | do 916 | { 917 | $fetchnext = false; 918 | $result = ($this->stmt !== false ? $this->stmt->fetch($this->filteropts === false ? $fetchtype : PDO::FETCH_OBJ) : false); 919 | if ($this->filteropts !== false) $this->db->RunRowFilter($result, $this->filteropts, $fetchnext); 920 | } while ($result !== false && $fetchnext); 921 | 922 | if ($result === false) $this->Free(); 923 | else if ($this->filteropts !== false && $fetchtype != PDO::FETCH_OBJ) 924 | { 925 | $result2 = array(); 926 | foreach ($result as $key => $val) 927 | { 928 | if ($fetchtype == PDO::FETCH_NUM || $fetchtype == PDO::FETCH_BOTH) $result2[] = $val; 929 | if ($fetchtype == PDO::FETCH_ASSOC || $fetchtype == PDO::FETCH_BOTH) $result2[$key] = $val; 930 | } 931 | 932 | $result = $result2; 933 | } 934 | 935 | return $result; 936 | } 937 | } 938 | ?> --------------------------------------------------------------------------------