From b0e8486ef1e2be20b65492a8017b143f53b406a2 Mon Sep 17 00:00:00 2001 From: Alex Marin Date: Mon, 28 May 2012 00:59:49 +0300 Subject: [PATCH] plugins and OOP: import XML, export and import SQL; auth structure --- .../auth/AuthenticationConfig.class.php | 71 + .../auth/AuthenticationCookie.class.php | 89 + .../plugins/auth/AuthenticationHTTP.class.php | 71 + .../auth/AuthenticationSIgnOn.class.php | 71 + .../auth/swekey/authentication.inc.php | 172 ++ libraries/plugins/auth/swekey/musbe-ca.crt | 25 + .../plugins/auth/swekey/swekey.auth.lib.php | 291 +++ libraries/plugins/auth/swekey/swekey.php | 517 +++++ libraries/plugins/export/ExportSQL.class.php | 1886 +++++++++++++++++ libraries/plugins/import/ImportSQL.class.php | 392 ++++ libraries/plugins/import/ImportXML.class.php | 458 ++++ 11 files changed, 4043 insertions(+) create mode 100644 libraries/plugins/auth/AuthenticationConfig.class.php create mode 100644 libraries/plugins/auth/AuthenticationCookie.class.php create mode 100644 libraries/plugins/auth/AuthenticationHTTP.class.php create mode 100644 libraries/plugins/auth/AuthenticationSIgnOn.class.php create mode 100644 libraries/plugins/auth/swekey/authentication.inc.php create mode 100644 libraries/plugins/auth/swekey/musbe-ca.crt create mode 100644 libraries/plugins/auth/swekey/swekey.auth.lib.php create mode 100644 libraries/plugins/auth/swekey/swekey.php create mode 100644 libraries/plugins/export/ExportSQL.class.php create mode 100644 libraries/plugins/import/ImportSQL.class.php create mode 100644 libraries/plugins/import/ImportXML.class.php diff --git a/libraries/plugins/auth/AuthenticationConfig.class.php b/libraries/plugins/auth/AuthenticationConfig.class.php new file mode 100644 index 0000000000..d9837db1eb --- /dev/null +++ b/libraries/plugins/auth/AuthenticationConfig.class.php @@ -0,0 +1,71 @@ + + + diff --git a/libraries/plugins/auth/swekey/musbe-ca.crt b/libraries/plugins/auth/swekey/musbe-ca.crt new file mode 100644 index 0000000000..2a31ad18f9 --- /dev/null +++ b/libraries/plugins/auth/swekey/musbe-ca.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIJAMjw7QcLWCd6MA0GCSqGSIb3DQEBBQUAMGsxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5j +LjESMBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2Jl +LmNvbTAeFw0wODA5MDQxNDE2MTNaFw0zNzEyMjExNDE2MTNaMGsxCzAJBgNVBAYT +AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5jLjES +MBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2JlLmNv +bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBhOljxVzQfK4gted2I +d3BemcjW4abAUOzn3KYWXpPO5xIfVeXNDGkDbyH+X+7fo94sX25/ewuKNFDSOcvo +tXHq7uQenTHB35r+a+LY81KceUHgW90a3XsqPAkwAjyYcgo3zmM2DtLvw+5Yod8T +wAHk9m3qavnQ1uk99jBTwL7RZ9jIZHh9pFCL93uJc2obtd8O96Iycbn2q0w/AWbb ++eUVWIHzvLtfPvROeL3lJzr/Uz5LjKapxJ3qyqASflfHpnj9pU8l6g2TQ6Hg5KT5 +tLFkRe7uGhOfRtOQ/+NjaWrEuNCFnpyN4Q5Fv+5qA1Ip1IpH0200sWbAf/k2u0Qp +Sx0CAwEAAaOB0DCBzTAdBgNVHQ4EFgQUczJrQ7hCvtsnzcqiDIZ/GSn/CiwwgZ0G +A1UdIwSBlTCBkoAUczJrQ7hCvtsnzcqiDIZ/GSn/Ciyhb6RtMGsxCzAJBgNVBAYT +AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRQwEgYDVQQKEwtNdXNiZSwgSW5jLjES +MBAGA1UEAxMJbXVzYmUuY29tMR0wGwYJKoZIhvcNAQkBFg5pbmZvQG11c2JlLmNv +bYIJAMjw7QcLWCd6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGxk +8xzIljeBDQWWVRr0NEALVSv3i09V4jAKkyEOfmZ8lKMKJi0atwbtjrXTzLnNYj+Q +pyUbyY/8ItWvV7pnVxMiF9qcer7e9X4vw358GZuMVE/da1nWxz+CwzTm5oO30RzA +antM9bISFFr9lJq69bDWOnCUi1IG8DSL3TxtlABso7S4vqiZ+sB33l6k1K4a/Njb +QkU9UejKhKkVVZTsOrumfnOJ4MCmPfX8Y/AY2o670y5HnzpxerIYziCVzApPVrW7 +sKH0tuVGturMfQOKgstYe4/m9glBTeTLMkjD+6MJC2ONBD7GAiOO95gNl5M1fzJQ +FEe5CJ7DCYl0GdmLXXw= +-----END CERTIFICATE----- diff --git a/libraries/plugins/auth/swekey/swekey.auth.lib.php b/libraries/plugins/auth/swekey/swekey.auth.lib.php new file mode 100644 index 0000000000..29d4a7f72b --- /dev/null +++ b/libraries/plugins/auth/swekey/swekey.auth.lib.php @@ -0,0 +1,291 @@ + + + \n"; +// if (file_exists($caFile)) +// echo "\n"; + } + + if (file_exists($caFile)) { + Swekey_SetCAFile($caFile); + } elseif (! empty($caFile) && (substr($_SESSION['SWEKEY']['CONF_SERVER_CHECK'], 0, 8) == "https://")) { + return "Internal Error: CA File $caFile not found"; + } + + $result = null; + $swekey_id = $_GET['swekey_id']; + $swekey_otp = $_GET['swekey_otp']; + + if (isset($swekey_id)) { + unset($_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY']); + if (! isset($_SESSION['SWEKEY']['RND_TOKEN'])) { + unset($swekey_id); + } else { + if (strlen($swekey_id) == 32) { + $res = Swekey_CheckOtp($swekey_id, $_SESSION['SWEKEY']['RND_TOKEN'], $swekey_otp); + unset($_SESSION['SWEKEY']['RND_TOKEN']); + if (! $res) { + $result = __('Hardware authentication failed') . ' (' . Swekey_GetLastError() . ')'; + } else { + $_SESSION['SWEKEY']['AUTHENTICATED_SWEKEY'] = $swekey_id; + $_SESSION['SWEKEY']['FORCE_USER'] = $_SESSION['SWEKEY']['VALID_SWEKEYS'][$swekey_id]; + return null; + } + } else { + $result = __('No valid authentication key plugged'); + if ($_SESSION['SWEKEY']['CONF_DEBUG']) { + $result .= "
" . htmlspecialchars($swekey_id); + } + unset($_SESSION['SWEKEY']['CONF_LOADED']); // reload the conf file + } + } + } else { + unset($_SESSION['SWEKEY']); + } + + $_SESSION['SWEKEY']['RND_TOKEN'] = Swekey_GetFastRndToken(); + if (strlen($_SESSION['SWEKEY']['RND_TOKEN']) != 64) { + $result = __('Hardware authentication failed') . ' (' . Swekey_GetLastError() . ')'; + unset($_SESSION['SWEKEY']['CONF_LOADED']); // reload the conf file + } + + if (! isset($swekey_id)) { + ?> + + display(); + if ($GLOBALS['error_handler']->hasDisplayErrors()) { + echo '
'; + $GLOBALS['error_handler']->dispErrors(); + echo '
'; + } + } + + if (isset($_SESSION['SWEKEY']) && $_SESSION['SWEKEY']['ENABLED']) { + echo ''; + } +} + +if (!empty($_GET['session_to_unset'])) { + session_write_close(); + session_id($_GET['session_to_unset']); + session_start(); + $_SESSION = array(); + session_write_close(); + session_destroy(); + exit; +} + +if (isset($_GET['swekey_reset'])) { + unset($_SESSION['SWEKEY']); +} + +?> diff --git a/libraries/plugins/auth/swekey/swekey.php b/libraries/plugins/auth/swekey/swekey.php new file mode 100644 index 0000000000..0f49732101 --- /dev/null +++ b/libraries/plugins/auth/swekey/swekey.php @@ -0,0 +1,517 @@ +"; + +/** + * Servers addresses + * Use the Swekey_SetXxxServer($server) functions to set them + */ + +global $gSwekeyCheckServer; +if (! isset($gSwekeyCheckServer)) { + $gSwekeyCheckServer = SWEKEY_DEFAULT_CHECK_SERVER; +} + +global $gSwekeyRndTokenServer; +if (! isset($gSwekeyRndTokenServer)) { + $gSwekeyRndTokenServer = SWEKEY_DEFAULT_RND_SERVER; +} + +global $gSwekeyStatusServer; +if (! isset($gSwekeyStatusServer)) { + $gSwekeyStatusServer = SWEKEY_DEFAULT_STATUS_SERVER; +} + +global $gSwekeyCA; + +global $gSwekeyTokenCacheEnabled; +if (! isset($gSwekeyTokenCacheEnabled)) { + $gSwekeyTokenCacheEnabled = true; +} + +/** + * Change the address of the Check server. + * If $server is empty the default value 'http://auth-check.musbe.net' will be used + * + * @param server The protocol and hostname to use + * @access public + */ +function Swekey_SetCheckServer($server) +{ + global $gSwekeyCheckServer; + if (empty($server)) { + $gSwekeyCheckServer = SWEKEY_DEFAULT_CHECK_SERVER; + } else { + $gSwekeyCheckServer = $server; + } +} + +/** + * Change the address of the Random Token Generator server. + * If $server is empty the default value 'http://auth-rnd-gen.musbe.net' will be used + * + * @param server The protocol and hostname to use + * @access public + */ +function Swekey_SetRndTokenServer($server) +{ + global $gSwekeyRndTokenServer; + if (empty($server)) { + $gSwekeyRndTokenServer = SWEKEY_DEFAULT_RND_SERVER; + } else { + $gSwekeyRndTokenServer = $server; + } +} + +/** + * Change the address of the Satus server. + * If $server is empty the default value 'http://auth-status.musbe.net' will be used + * + * @param server The protocol and hostname to use + * @access public + */ +function Swekey_SetStatusServer($server) +{ + global $gSwekeyStatusServer; + if (empty($server)) { + $gSwekeyStatusServer = SWEKEY_DEFAULT_STATUS_SERVER; + } else { + $gSwekeyStatusServer = $server; + } +} + +/** + * Change the certificat file in case of the the severs use https instead of http + * + * @param cafile The path of the crt file to use + * @access public + */ +function Swekey_SetCAFile($cafile) +{ + global $gSwekeyCA; + $gSwekeyCA = $cafile; +} + +/** + * Enable or disable the random token caching + * Because everybody has full access to the cache file, it can be a DOS vulnerability + * So disable it if you are running in a non secure enviromnement + * + * @param $enable + * @access public + */ +function Swekey_EnableTokenCache($enable) +{ + global $gSwekeyTokenCacheEnabled; + $gSwekeyTokenCacheEnabled = ! empty($enable); +} + + +/** + * Return the last error. + * + * @return The Last Error + * @access public + */ +function Swekey_GetLastError() +{ + global $gSwekeyLastError; + return $gSwekeyLastError; +} + +/** + * Return the last result. + * + * @return The Last Error + * @access public + */ +function Swekey_GetLastResult() +{ + global $gSwekeyLastResult; + return $gSwekeyLastResult; +} + +/** + * Send a synchronous request to the server. + * This function manages timeout then will not block if one of the server is down + * + * @param url The url to get + * @param response_code The response code + * + * @return The body of the response or "" in case of error + * @access private + */ +function Swekey_HttpGet($url, &$response_code) +{ + global $gSwekeyLastError; + $gSwekeyLastError = 0; + global $gSwekeyLastResult; + $gSwekeyLastResult = ""; + + // use curl if available + if (function_exists('curl_init')) { + $sess = curl_init($url); + if (substr($url, 0, 8) == "https://") { + global $gSwekeyCA; + + if (! empty($gSwekeyCA)) { + if (file_exists($gSwekeyCA)) { + if (! curl_setopt($sess, CURLOPT_CAINFO, $gSwekeyCA)) { + error_log("SWEKEY_ERROR:Could not set CA file : ".curl_error($sess)); + } else { + $caFileOk = true; + } + } else { + error_log("SWEKEY_ERROR:Could not find CA file $gSwekeyCA getting $url"); + } + } + + curl_setopt($sess, CURLOPT_SSL_VERIFYHOST, '2'); + curl_setopt($sess, CURLOPT_SSL_VERIFYPEER, '2'); + curl_setopt($sess, CURLOPT_CONNECTTIMEOUT, '20'); + curl_setopt($sess, CURLOPT_TIMEOUT, '20'); + } else { + curl_setopt($sess, CURLOPT_CONNECTTIMEOUT, '3'); + curl_setopt($sess, CURLOPT_TIMEOUT, '5'); + } + + curl_setopt($sess, CURLOPT_RETURNTRANSFER, '1'); + $res=curl_exec($sess); + $response_code = curl_getinfo($sess, CURLINFO_HTTP_CODE); + $curlerr = curl_error($sess); + curl_close($sess); + + if ($response_code == 200) { + $gSwekeyLastResult = $res; + return $res; + } + + if (! empty($response_code)) { + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error $gSwekeyLastError ($curlerr) getting $url"); + return ""; + } + + $response_code = 408; // Request Timeout + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error $curlerr getting $url"); + return ""; + } + + // use pecl_http if available + if (class_exists('HttpRequest')) { + // retry if one of the server is down + for ($num=1; $num <= 3; $num++ ) { + $r = new HttpRequest($url); + $options = array('timeout' => '3'); + + if (substr($url, 0, 6) == "https:") { + $sslOptions = array(); + $sslOptions['verifypeer'] = true; + $sslOptions['verifyhost'] = true; + + $capath = __FILE__; + $name = strrchr($capath, '/'); + // windows + if (empty($name)) { + $name = strrchr($capath, '\\'); + } + $capath = substr($capath, 0, strlen($capath) - strlen($name) + 1).'musbe-ca.crt'; + + if (! empty($gSwekeyCA)) { + $sslOptions['cainfo'] = $gSwekeyCA; + } + + $options['ssl'] = $sslOptions; + } + + $r->setOptions($options); + + // try + { + $reply = $r->send(); + $res = $reply->getBody(); + $info = $r->getResponseInfo(); + $response_code = $info['response_code']; + if ($response_code != 200) { + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error ".$gSwekeyLastError." getting ".$url); + return ""; + } + + + $gSwekeyLastResult = $res; + return $res; + } + // catch (HttpException $e) + // { + // error_log("SWEKEY_WARNING:HttpException ".$e." getting ".$url); + // } + } + + $response_code = 408; // Request Timeout + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error ".$gSwekeyLastError." getting ".$url); + return ""; + } + + global $http_response_header; + $res = @file_get_contents($url); + $response_code = substr($http_response_header[0], 9, 3); //HTTP/1.0 + if ($response_code == 200) { + $gSwekeyLastResult = $res; + return $res; + } + + $gSwekeyLastError = $response_code; + error_log("SWEKEY_ERROR:Error ".$response_code." getting ".$url); + return ""; +} + +/** + * Get a Random Token from a Token Server + * The RT is a 64 vhars hexadecimal value + * You should better use Swekey_GetFastRndToken() for performance + * @access public + */ +function Swekey_GetRndToken() +{ + global $gSwekeyRndTokenServer; + return Swekey_HttpGet($gSwekeyRndTokenServer.'/FULL-RND-TOKEN', $response_code); +} + +/** + * Get a Half Random Token from a Token Server + * The RT is a 64 vhars hexadecimal value + * Use this value if you want to make your own Swekey_GetFastRndToken() + * @access public + */ +function Swekey_GetHalfRndToken() +{ + global $gSwekeyRndTokenServer; + return Swekey_HttpGet($gSwekeyRndTokenServer.'/HALF-RND-TOKEN', $response_code); +} + +/** + * Get a Half Random Token + * The RT is a 64 vhars hexadecimal value + * This function get a new random token and reuse it. + * Token are refetched from the server only once every 30 seconds. + * You should always use this function to get half random token. + * @access public + */ +function Swekey_GetFastHalfRndToken() +{ + global $gSwekeyTokenCacheEnabled; + + $res = ""; + $cachefile = ""; + + // We check if we have a valid RT is the session + if (isset($_SESSION['rnd-token-date'])) { + if (time() - $_SESSION['rnd-token-date'] < 30) { + $res = $_SESSION['rnd-token']; + } + } + + // If not we try to get it from a temp file (PHP >= 5.2.1 only) + if (strlen($res) != 32 && $gSwekeyTokenCacheEnabled) { + if (function_exists('sys_get_temp_dir')) { + $tempdir = sys_get_temp_dir(); + $cachefile = $tempdir."/swekey-rnd-token-".get_current_user(); + $modif = filemtime($cachefile); + if ($modif != false) { + if (time() - $modif < 30) { + $res = @file_get_contents($cachefile); + if (strlen($res) != 32) { + $res = ""; + } else { + $_SESSION['rnd-token'] = $res; + $_SESSION['rnd-token-date'] = $modif; + } + } + } + } + } + + // If we don't have a valid RT here we have to get it from the server + if (strlen($res) != 32) { + $res = substr(Swekey_GetHalfRndToken(), 0, 32); + $_SESSION['rnd-token'] = $res; + $_SESSION['rnd-token-date'] = time(); + if (! empty($cachefile)) { + // we unlink the file so no possible tempfile race attack + unlink($cachefile); + $file = fopen($cachefile, "x"); + if ($file != false) { + @fwrite($file, $res); + @fclose($file); + } + } + } + + return $res."00000000000000000000000000000000"; +} + +/** + * Get a Random Token + * The RT is a 64 vhars hexadecimal value + * This function generates a unique random token for each call but call the + * server only once every 30 seconds. + * You should always use this function to get random token. + * @access public + */ +function Swekey_GetFastRndToken() +{ + $res = Swekey_GetFastHalfRndToken(); + if (strlen($res) == 64) { + return substr($res, 0, 32).strtoupper(md5("Musbe Authentication Key" + mt_rand() + date(DATE_ATOM))); + } + return ""; +} + + +/** + * Checks that an OTP generated by a Swekey is valid + * + * @param id The id of the swekey + * @param rt The random token used to generate the otp + * @param otp The otp generated by the swekey + * + * @return true or false + * @access public + */ +function Swekey_CheckOtp($id, $rt, $otp) +{ + global $gSwekeyCheckServer; + $res = Swekey_HttpGet($gSwekeyCheckServer.'/CHECK-OTP/'.$id.'/'.$rt.'/'.$otp, $response_code); + return $response_code == 200 && $res == "OK"; +} + +/** + * Values that are associated with a key. + * The following values can be returned by the Swekey_GetStatus() function + */ +define("SWEKEY_STATUS_OK", 0); +define("SWEKEY_STATUS_NOT_FOUND", 1); // The key does not exist in the db +define("SWEKEY_STATUS_INACTIVE", 2); // The key has never been activated +define("SWEKEY_STATUS_LOST", 3); // The user has lost his key +define("SWEKEY_STATUS_STOLEN", 4); // The key was stolen +define("SWEKEY_STATUS_FEE_DUE", 5); // The annual fee was not paid +define("SWEKEY_STATUS_OBSOLETE", 6); // The hardware is no longer supported +define("SWEKEY_STATUS_UNKOWN", 201); // We could not connect to the authentication server + +/** + * Values that are associated with a key. + * The Javascript Api can also return the following values + */ +define("SWEKEY_STATUS_REPLACED", 100); // This key has been replaced by a backup key +define("SWEKEY_STATUS_BACKUP_KEY", 101); // This key is a backup key that is not activated yet +define("SWEKEY_STATUS_NOTPLUGGED", 200); // This key is not plugged in the computer + + +/** + * Return the text corresponding to the integer status of a key + * + * @param status The status + * + * @return The text corresponding to the status + * @access public + */ +function Swekey_GetStatusStr($status) +{ + switch($status) + { + case SWEKEY_STATUS_OK : + return 'OK'; + case SWEKEY_STATUS_NOT_FOUND : + return 'Key does not exist in the db'; + case SWEKEY_STATUS_INACTIVE : + return 'Key not activated'; + case SWEKEY_STATUS_LOST : + return 'Key was lost'; + case SWEKEY_STATUS_STOLEN : + return 'Key was stolen'; + case SWEKEY_STATUS_FEE_DUE : + return 'The annual fee was not paid'; + case SWEKEY_STATUS_OBSOLETE : + return 'Key no longer supported'; + case SWEKEY_STATUS_REPLACED : + return 'This key has been replaced by a backup key'; + case SWEKEY_STATUS_BACKUP_KEY : + return 'This key is a backup key that is not activated yet'; + case SWEKEY_STATUS_NOTPLUGGED : + return 'This key is not plugged in the computer'; + case SWEKEY_STATUS_UNKOWN : + return 'Unknow Status, could not connect to the authentication server'; + } + return 'unknown status '.$status; +} + +/** + * If your web site requires a key to login you should check that the key + * is still valid (has not been lost or stolen) before requiring it. + * A key can be authenticated only if its status is SWEKEY_STATUS_OK + * + * @param id The id of the swekey + * + * @return The status of the swekey + * @access public + */ +function Swekey_GetStatus($id) +{ + global $gSwekeyStatusServer; + $res = Swekey_HttpGet($gSwekeyStatusServer.'/GET-STATUS/'.$id, $response_code); + if ($response_code == 200) { + return intval($res); + } + return SWEKEY_STATUS_UNKOWN; +} + +?> diff --git a/libraries/plugins/export/ExportSQL.class.php b/libraries/plugins/export/ExportSQL.class.php new file mode 100644 index 0000000000..fe04056362 --- /dev/null +++ b/libraries/plugins/export/ExportSQL.class.php @@ -0,0 +1,1886 @@ +setProperties(); + + // Avoids undefined variables, use NULL so isset() returns false + if (! isset($GLOBALS['sql_backquotes'])) { + $GLOBALS['sql_backquotes'] = null; + } + } + + /** + * Initialize the local variables that are used specific for export SQL + * + * @global type $plugin_param + * @global type $mysql_charset_map + * @global type $sql_drop_table + * @global type $sql_backquotes + * @global type $sql_constraints + * @global type $sql_constraints_query + * @global type $sql_drop_foreign_keys + * @global type $cfgRelation + * @global type $current_row + * + * @return void + */ + private function initLocalVariables() + { + global $plugin_param; + global $mysql_charset_map; + global $sql_drop_table; + global $sql_backquotes; + global $sql_constraints; + global $sql_constraints_query; + global $sql_drop_foreign_keys; + global $cfgRelation; + global $current_row; + $this->setPlugin_param($plugin_param); + $this->setMysql_charset_map($mysql_charset_map); + $this->setSql_drop_table($sql_drop_table); + $this->setSql_backquotes($sql_backquotes); + $this->setSql_constraints($sql_constraints); + $this->setSql_constraints_query($sql_constraints_query); + $this->setSql_drop_foreign_keys($sql_drop_foreign_keys); + $this->setCfgRelation($cfgRelation); + $this->setCurrent_row($current_row); + } + + /** + * Sets the export XML properties + * + * @return void + */ + protected function setProperties() + { + $plugin_param = $this->getPlugin_param(); + + $hide_sql = false; + $hide_structure = false; + if ($plugin_param['export_type'] == 'table' + && ! $plugin_param['single_table'] + ) { + $hide_structure = true; + $hide_sql = true; + } + if (! $hide_sql) { + $this->properties = array( + 'text' => __('SQL'), + 'extension' => 'sql', + 'mime_type' => 'text/x-sql', + 'options' => array(), + 'options_text' => __('Options') + ); + + $this->properties['options'][] = array( + 'type' => 'begin_group', + 'name' => 'general_opts' + ); + + /* comments */ + $this->properties['options'][] = array( + 'type' => 'begin_subgroup', + 'subgroup_header' => array( + 'type' => 'bool', + 'name' => 'include_comments', + 'text' => __( + 'Display comments (includes info such as export timestamp,' + . ' PHP version, and server version)' + ) + ) + ); + $this->properties['options'][] = array( + 'type' => 'text', + 'name' => 'header_comment', + 'text' => __('Additional custom header comment (\n splits lines):') + ); + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'dates', + 'text' => __( + 'Include a timestamp of when databases were created, last' + . ' updated, and last checked' + ) + ); + if (! empty($GLOBALS['cfgRelation']['relation'])) { + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'relation', + 'text' => __('Display foreign key relationships') + ); + } + if (! empty($GLOBALS['cfgRelation']['mimework'])) { + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'mime', + 'text' => __('Display MIME types') + ); + } + $this->properties['options'][] = array( + 'type' => 'end_subgroup' + ); + /* end comments */ + + /* enclose in a transaction */ + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'use_transaction', + 'text' => __('Enclose export in a transaction'), + 'doc' => array( + 'programs', + 'mysqldump', + 'option_mysqldump_single-transaction' + ) + ); + + /* disable foreign key checks */ + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'disable_fk', + 'text' => __('Disable foreign key checks'), + 'doc' => array( + 'manual_MySQL_Database_Administration', + 'server-system-variables', + 'sysvar_foreign_key_checks' + ) + ); + + /* compatibility maximization */ + $compats = PMA_DBI_getCompatibilities(); + if (count($compats) > 0) { + $values = array(); + foreach ($compats as $val) { + $values[$val] = $val; + } + $this->properties['options'][] = array( + 'type' => 'select', + 'name' => 'compatibility', + 'text' => __( + 'Database system or older MySQL server to maximize output' + . ' compatibility with:' + ), + 'values' => $values, + 'doc' => array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode' + ) + ); + unset($values); + } + + /* server export options */ + if ($plugin_param['export_type'] == 'server') { + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'drop_database', + 'text' => sprintf( + __('Add %s statement'), 'DROP DATABASE' + ) + ); + } + + /* what to dump (structure/data/both) */ + $this->properties['options'][] = array( + 'type' => 'begin_subgroup', + 'subgroup_header' => array( + 'type' => 'message_only', + 'text' => __('Dump table') + ) + ); + $this->properties['options'][] = array( + 'type' => 'radio', + 'name' => 'structure_or_data', + 'values' => array( + 'structure' => __('structure'), + 'data' => __('data'), + 'structure_and_data' => __('structure and data') + ) + ); + $this->properties['options'][] = array( + 'type' => 'end_subgroup' + ); + + $this->properties['options'][] = array( + 'type' => 'end_group' + ); + + /* begin Structure options */ + if (! $hide_structure) { + $this->properties['options'][] = array( + 'type' => 'begin_group', + 'name' => 'structure', + 'text' => __('Object creation options'), + 'force' => 'data' + ); + + /* begin SQL Statements */ + $this->properties['options'][] = array( + 'type' => 'begin_subgroup', + 'subgroup_header' => array( + 'type' => 'message_only', + 'name' => 'add_statements', + 'text' => __('Add statements:') + ) + ); + if ($plugin_param['export_type'] == 'table') { + if (PMA_Table::isView($GLOBALS['db'], $GLOBALS['table'])) { + $drop_clause = 'DROP VIEW'; + } else { + $drop_clause = 'DROP TABLE'; + } + } else { + if (PMA_DRIZZLE) { + $drop_clause = 'DROP TABLE'; + } else { + $drop_clause = 'DROP TABLE / VIEW / PROCEDURE' + . ' / FUNCTION'; + if (PMA_MYSQL_INT_VERSION > 50100) { + $drop_clause .= ' / EVENT'; + } + } + } + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'drop_table', + 'text' => sprintf(__('Add %s statement'), $drop_clause) + ); + // Drizzle doesn't support procedures and functions + if (! PMA_DRIZZLE) { + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'procedure_function', + 'text' => sprintf( + __('Add %s statement'), + 'CREATE PROCEDURE / FUNCTION' + . (PMA_MYSQL_INT_VERSION > 50100 + ? ' / EVENT' : '') + ) + ); + } + + /* begin CREATE TABLE statements*/ + $this->properties['options'][] = array( + 'type' => 'begin_subgroup', + 'subgroup_header' => array( + 'type' => 'bool', + 'name' => 'create_table_statements', + 'text' => __('CREATE TABLE options:') + ) + ); + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'if_not_exists', + 'text' => 'IF NOT EXISTS' + ); + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'auto_increment', + 'text' => 'AUTO_INCREMENT' + ); + $this->properties['options'][] = array( + 'type' => 'end_subgroup' + ); + /* end CREATE TABLE statements */ + + $this->properties['options'][] = array( + 'type' => 'end_subgroup' + ); + /* end SQL statements */ + + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'backquotes', + 'text' => __( + 'Enclose table and column names with backquotes ' + . '(Protects column and table names formed with' + . ' special characters or keywords)' + ) + ); + + $this->properties['options'][] = array( + 'type' => 'end_group' + ); + } + /* end Structure options */ + + /* begin Data options */ + $this->properties['options'][] = array( + 'type' => 'begin_group', + 'name' => 'data', + 'text' => __('Data dump options'), + 'force' => 'structure' + ); + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'truncate', + 'text' => __('Truncate table before insert') + ); + /* begin SQL statements */ + $this->properties['options'][] = array( + 'type' => 'begin_subgroup', + 'subgroup_header' => array( + 'type' => 'message_only', + 'text' => __('Instead of INSERT statements, use:') + ) + ); + // Not supported in Drizzle + if (! PMA_DRIZZLE) { + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'delayed', + 'text' => __('INSERT DELAYED statements'), + 'doc' => array( + 'manual_MySQL_Database_Administration', + 'insert_delayed' + ) + ); + } + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'ignore', + 'text' => __('INSERT IGNORE statements'), + 'doc' => array( + 'manual_MySQL_Database_Administration', + 'insert' + ) + ); + $this->properties['options'][] = array( + 'type' => 'end_subgroup' + ); + /* end SQL statements */ + + /* Function to use when dumping data */ + $this->properties['options'][] = array( + 'type' => 'select', + 'name' => 'type', + 'text' => __('Function to use when dumping data:'), + 'values' => array( + 'INSERT' => 'INSERT', + 'UPDATE' => 'UPDATE', + 'REPLACE' => 'REPLACE' + ) + ); + + /* Syntax to use when inserting data */ + $this->properties['options'][] = array( + 'type' => 'begin_subgroup', + 'subgroup_header' => array( + 'type' => 'message_only', + 'text' => __('Syntax to use when inserting data:') + ) + ); + $this->properties['options'][] = array( + 'type' => 'radio', + 'name' => 'insert_syntax', + 'values' => array( + 'complete' => __( + 'include column names in every INSERT statement' + . '
      Example: INSERT INTO' + . ' tbl_name (col_A,col_B,col_C) VALUES (1,2,3)' + ), + 'extended' => __( + 'insert multiple rows in every INSERT statement' + . '
      Example: INSERT INTO' + . ' tbl_name VALUES (1,2,3), (4,5,6), (7,8,9)' + ), + 'both' => __( + 'both of the above
      Example:' + . ' INSERT INTO tbl_name (col_A,col_B) VALUES (1,2,3),' + . ' (4,5,6), (7,8,9)' + ), + 'none' => __( + 'neither of the above
      Example:' + . ' INSERT INTO tbl_name VALUES (1,2,3)' + ) + ) + ); + $this->properties['options'][] = array( + 'type' => 'end_subgroup' + ); + + /* Max length of query */ + $this->properties['options'][] = array( + 'type' => 'text', + 'name' => 'max_query_size', + 'text' => __('Maximal length of created query') + ); + + /* Dump binary columns in hexadecimal */ + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'hex_for_blob', + 'text' => __( + 'Dump binary columns in hexadecimal notation' + . ' (for example, "abc" becomes 0x616263)' + ) + ); + + // Drizzle works only with UTC timezone + if (! PMA_DRIZZLE) { + /* Dump time in UTC */ + $this->properties['options'][] = array( + 'type' => 'bool', + 'name' => 'utc_time', + 'text' => __( + 'Dump TIMESTAMP columns in UTC (enables TIMESTAMP columns' + . ' to be dumped and reloaded between servers in different' + . ' time zones)' + ) + ); + } + + $this->properties['options'][] = array( + 'type' => 'end_group' + ); + /* end Data options */ + } + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Exports routines (procedures and functions) + * + * @param string $db Database + * + * @return bool Whether it succeeded + */ + public function exportRoutines($db) + { + $text = ''; + $delimiter = '$$'; + + $procedure_names = PMA_DBI_get_procedures_or_functions($db, 'PROCEDURE'); + $function_names = PMA_DBI_get_procedures_or_functions($db, 'FUNCTION'); + + if ($procedure_names || $function_names) { + $text .= $crlf + . 'DELIMITER ' . $delimiter . $crlf; + } + + if ($procedure_names) { + $text .= + $this->exportComment() + . $this->exportComment(__('Procedures')) + . $this->exportComment(); + + foreach ($procedure_names as $procedure_name) { + if (! empty($GLOBALS['sql_drop_table'])) { + $text .= 'DROP PROCEDURE IF EXISTS ' + . PMA_backquote($procedure_name) + . $delimiter . $crlf; + } + $text .= PMA_DBI_get_definition($db, 'PROCEDURE', $procedure_name) + . $delimiter . $crlf . $crlf; + } + } + + if ($function_names) { + $text .= + $this->exportComment() + . $this->exportComment(__('Functions')) + . $this->exportComment(); + + foreach ($function_names as $function_name) { + if (! empty($GLOBALS['sql_drop_table'])) { + $text .= 'DROP FUNCTION IF EXISTS ' + . PMA_backquote($function_name) + . $delimiter . $crlf; + } + $text .= PMA_DBI_get_definition($db, 'FUNCTION', $function_name) + . $delimiter . $crlf . $crlf; + } + } + + if ($procedure_names || $function_names) { + $text .= 'DELIMITER ;' . $crlf; + } + + if (! empty($text)) { + return PMA_exportOutputHandler($text); + } else { + return false; + } + } + + /** + * Possibly outputs comment + * + * @param string $text Text of comment + * + * @return string The formatted comment + */ + private function exportComment($text = '') + { + if (isset($GLOBALS['sql_include_comments']) + && $GLOBALS['sql_include_comments'] + ) { + // see http://dev.mysql.com/doc/refman/5.0/en/ansi-diff-comments.html + return '--' . (empty($text) ? '' : ' ') . $text . $GLOBALS['crlf']; + } else { + return ''; + } + } + + /** + * Possibly outputs CRLF + * + * @return string $crlf or nothing + */ + private function possibleCRLF() + { + if (isset($GLOBALS['sql_include_comments']) + && $GLOBALS['sql_include_comments'] + ) { + return $GLOBALS['crlf']; + } else { + return ''; + } + } + + /** + * Outputs export footer + * + * @return bool Whether it succeeded + * + * @access public + */ + public function exportFooter() + { + $crlf = $this->getCrlf(); + $mysql_charset_map = $this->getMysql_charset_map(); + + $foot = ''; + + if (isset($GLOBALS['sql_disable_fk'])) { + $foot .= 'SET FOREIGN_KEY_CHECKS=1;' . $crlf; + } + + if (isset($GLOBALS['sql_use_transaction'])) { + $foot .= 'COMMIT;' . $crlf; + } + + // restore connection settings + $charset_of_file = isset($GLOBALS['charset_of_file']) + ? $GLOBALS['charset_of_file'] : ''; + if (! empty($GLOBALS['asfile']) + && isset($mysql_charset_map[$charset_of_file]) + && ! PMA_DRIZZLE + ) { + $foot .= $crlf + . '/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;' + . $crlf + . '/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;' + . $crlf + . '/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;' + . $crlf; + } + + /* Restore timezone */ + if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) { + PMA_DBI_query('SET time_zone = "' . $GLOBALS['old_tz'] . '"'); + } + + return PMA_exportOutputHandler($foot); + } + + /** + * Outputs export header. It is the first method to be called, so all + * the required variables are initialized here. + * + * @return bool Whether it succeeded + * + * @access public + */ + public function exportHeader() + { + // initialize the general export variables + $this->initExportCommonVariables(); + + // initialize the specific export sql variables + $this->initLocalVariables(); + + $crlf = $this->getCrlf(); + $cfg = $this->getCfg(); + $mysql_charset_map = $this->getMysql_charset_map(); + + if (isset($GLOBALS['sql_compatibility'])) { + $tmp_compat = $GLOBALS['sql_compatibility']; + if ($tmp_compat == 'NONE') { + $tmp_compat = ''; + } + PMA_DBI_try_query('SET SQL_MODE="' . $tmp_compat . '"'); + unset($tmp_compat); + } + $head = $this->exportComment('phpMyAdmin SQL Dump') + . $this->exportComment('version ' . PMA_VERSION) + . $this->exportComment('http://www.phpmyadmin.net') + . $this->exportComment(); + $host_string = __('Host') . ': ' . $cfg['Server']['host']; + if (! empty($cfg['Server']['port'])) { + $host_string .= ':' . $cfg['Server']['port']; + } + $head .= $this->exportComment($host_string); + $head .= + $this->exportComment( + __('Generation Time') . ': ' . PMA_localisedDate() + ) + . $this->exportComment(__('Server version') . ': ' . PMA_MYSQL_STR_VERSION) + . $this->exportComment(__('PHP Version') . ': ' . phpversion()) + . $this->possibleCRLF(); + + if (isset($GLOBALS['sql_header_comment']) + && ! empty($GLOBALS['sql_header_comment']) + ) { + // '\n' is not a newline (like "\n" would be), it's the characters + // backslash and n, as explained on the export interface + $lines = explode('\n', $GLOBALS['sql_header_comment']); + $head .= $this->exportComment(); + foreach ($lines as $one_line) { + $head .= $this->exportComment($one_line); + } + $head .= $this->exportComment(); + } + + if (isset($GLOBALS['sql_disable_fk'])) { + $head .= 'SET FOREIGN_KEY_CHECKS=0;' . $crlf; + } + + // We want exported AUTO_INCREMENT columns to have still same value, + // do this only for recent MySQL exports + if ((! isset($GLOBALS['sql_compatibility']) + || $GLOBALS['sql_compatibility'] == 'NONE') + && ! PMA_DRIZZLE + ) { + $head .= 'SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";' . $crlf; + } + + if (isset($GLOBALS['sql_use_transaction'])) { + $head .= 'SET AUTOCOMMIT = 0;' . $crlf + . 'START TRANSACTION;' . $crlf; + } + + /* Change timezone if we should export timestamps in UTC */ + if (isset($GLOBALS['sql_utc_time']) && $GLOBALS['sql_utc_time']) { + $head .= 'SET time_zone = "+00:00";' . $crlf; + $GLOBALS['old_tz'] = PMA_DBI_fetch_value('SELECT @@session.time_zone'); + PMA_DBI_query('SET time_zone = "+00:00"'); + } + + $head .= $this->possibleCRLF(); + + if (! empty($GLOBALS['asfile']) && ! PMA_DRIZZLE) { + // we are saving as file, therefore we provide charset information + // so that a utility like the mysql client can interpret + // the file correctly + if (isset($GLOBALS['charset_of_file']) + && isset($mysql_charset_map[$GLOBALS['charset_of_file']]) + ) { + // we got a charset from the export dialog + $set_names = $mysql_charset_map[$GLOBALS['charset_of_file']]; + } else { + // by default we use the connection charset + $set_names = $mysql_charset_map['utf-8']; + } + $head .= $crlf + . '/*!40101 SET @OLD_CHARACTER_SET_CLIENT=' + . '@@CHARACTER_SET_CLIENT */;' . $crlf + . '/*!40101 SET @OLD_CHARACTER_SET_RESULTS=' + . '@@CHARACTER_SET_RESULTS */;' . $crlf + . '/*!40101 SET @OLD_COLLATION_CONNECTION=' + . '@@COLLATION_CONNECTION */;'. $crlf + . '/*!40101 SET NAMES ' . $set_names . ' */;' . $crlf . $crlf; + } + + return PMA_exportOutputHandler($head); + } + + /** + * Outputs CREATE DATABASE statement + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBCreate($db) + { + $crlf = $this->getCrlf(); + if (isset($GLOBALS['sql_drop_database'])) { + if (! PMA_exportOutputHandler( + 'DROP DATABASE ' + . (isset($GLOBALS['sql_backquotes']) + ? PMA_backquote($db) : $db) + . ';' . $crlf + )) { + return false; + } + } + $create_query = 'CREATE DATABASE ' + . (isset($GLOBALS['sql_backquotes']) ? PMA_backquote($db) : $db); + $collation = PMA_getDbCollation($db); + if (PMA_DRIZZLE) { + $create_query .= ' COLLATE ' . $collation; + } else { + if (strpos($collation, '_')) { + $create_query .= ' DEFAULT CHARACTER SET ' + . substr($collation, 0, strpos($collation, '_')) + . ' COLLATE ' . $collation; + } else { + $create_query .= ' DEFAULT CHARACTER SET ' . $collation; + } + } + $create_query .= ';' . $crlf; + if (! PMA_exportOutputHandler($create_query)) { + return false; + } + if (isset($GLOBALS['sql_backquotes']) + && ((isset($GLOBALS['sql_compatibility']) + && $GLOBALS['sql_compatibility'] == 'NONE') + || PMA_DRIZZLE) + ) { + $result = PMA_exportOutputHandler( + 'USE ' . PMA_backquote($db) . ';' . $crlf + ); + } else { + $result = PMA_exportOutputHandler('USE ' . $db . ';' . $crlf); + } + + return $result; + } + + /** + * Outputs database header + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBHeader($db) + { + $head = $this->exportComment() + . $this->exportComment( + __('Database') . ': ' + . (isset($GLOBALS['sql_backquotes']) + ? PMA_backquote($db) : '\'' . $db . '\'') + ) + . $this->exportComment(); + return PMA_exportOutputHandler($head); + } + + /** + * Outputs database footer + * + * @param string $db Database name + * + * @return bool Whether it succeeded + */ + public function exportDBFooter($db) + { + $crlf = $this->getCrlf(); + + $result = true; + if (isset($GLOBALS['sql_constraints'])) { + $result = PMA_exportOutputHandler($GLOBALS['sql_constraints']); + unset($GLOBALS['sql_constraints']); + } + + if (($GLOBALS['sql_structure_or_data'] == 'structure' + || $GLOBALS['sql_structure_or_data'] == 'structure_and_data') + && isset($GLOBALS['sql_procedure_function']) + ) { + $text = ''; + $delimiter = '$$'; + + if (PMA_MYSQL_INT_VERSION > 50100) { + $event_names = PMA_DBI_fetch_result( + 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE' + . ' EVENT_SCHEMA= \'' . PMA_sqlAddSlashes($db, true) . '\';' + ); + } else { + $event_names = array(); + } + + if ($event_names) { + $text .= $crlf + . 'DELIMITER ' . $delimiter . $crlf; + + $text .= + $this->exportComment() + . $this->exportComment(__('Events')) + . $this->exportComment(); + + foreach ($event_names as $event_name) { + if (! empty($GLOBALS['sql_drop_table'])) { + $text .= 'DROP EVENT ' . PMA_backquote($event_name) + . $delimiter . $crlf; + } + $text .= PMA_DBI_get_definition($db, 'EVENT', $event_name) + . $delimiter . $crlf . $crlf; + } + + $text .= 'DELIMITER ;' . $crlf; + } + + if (! empty($text)) { + $result = PMA_exportOutputHandler($text); + } + } + return $result; + } + + /** + * Returns a stand-in CREATE definition to resolve view dependencies + * + * @param string $db the database name + * @param string $view the view name + * @param string $crlf the end of line sequence + * + * @return string resulting definition + */ + public function getTableDefStandIn($db, $view, $crlf) + { + $create_query = ''; + if (! empty($GLOBALS['sql_drop_table'])) { + $create_query .= 'DROP VIEW IF EXISTS ' . PMA_backquote($view) + . ';' . $crlf; + } + + $create_query .= 'CREATE TABLE '; + + if (isset($GLOBALS['sql_if_not_exists']) + && $GLOBALS['sql_if_not_exists'] + ) { + $create_query .= 'IF NOT EXISTS '; + } + $create_query .= PMA_backquote($view) . ' (' . $crlf; + $tmp = array(); + $columns = PMA_DBI_get_columns_full($db, $view); + foreach ($columns as $column_name => $definition) { + $tmp[] = PMA_backquote($column_name) . ' ' . $definition['Type'] . $crlf; + } + $create_query .= implode(',', $tmp) . ');'; + return($create_query); + } + + /** + * Returns $table's CREATE definition + * + * @param string $db the database name + * @param string $table the table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param bool $show_dates whether to include creation/update/check dates + * @param bool $add_semicolon whether to add semicolon and end-of-line at + * the end + * @param bool $view whether we're handling a view + * + * @return string resulting schema + */ + public function getTableDef( + $db, + $table, + $crlf, + $error_url, + $show_dates = false, + $add_semicolon = true, + $view = false + ) { + $sql_drop_table = $this->getSql_drop_table(); + $sql_backquotes = $this->getSql_backquotes(); + $sql_constraints = $this->getSql_constraints(); + $sql_constraints_query = $this->getSql_constraints_query(); + $sql_drop_foreign_keys = $this->getSql_drop_foreign_keys(); + + $schema_create = ''; + $auto_increment = ''; + $new_crlf = $crlf; + + // need to use PMA_DBI_QUERY_STORE with PMA_DBI_num_rows() in mysqli + $result = PMA_DBI_query( + 'SHOW TABLE STATUS FROM ' . PMA_backquote($db) . ' LIKE \'' + . PMA_sqlAddSlashes($table, true) . '\'', + null, + PMA_DBI_QUERY_STORE + ); + if ($result != false) { + if (PMA_DBI_num_rows($result) > 0) { + $tmpres = PMA_DBI_fetch_assoc($result); + if (PMA_DRIZZLE && $show_dates) { + // Drizzle doesn't give Create_time and Update_time in + // SHOW TABLE STATUS, add it + $sql ="SELECT + TABLE_CREATION_TIME AS Create_time, + TABLE_UPDATE_TIME AS Update_time + FROM data_dictionary.TABLES + WHERE TABLE_SCHEMA = '" . PMA_sqlAddSlashes($db) . "' + AND TABLE_NAME = '" . PMA_sqlAddSlashes($table) . "'"; + $tmpres = array_merge(PMA_DBI_fetch_single_row($sql), $tmpres); + } + // Here we optionally add the AUTO_INCREMENT next value, + // but starting with MySQL 5.0.24, the clause is already included + // in SHOW CREATE TABLE so we'll remove it below + // It's required for Drizzle because SHOW CREATE TABLE uses + // the value from table's creation time + if (isset($GLOBALS['sql_auto_increment']) + && ! empty($tmpres['Auto_increment']) + ) { + $auto_increment .= ' AUTO_INCREMENT=' + . $tmpres['Auto_increment'] . ' '; + } + + if ($show_dates + && isset($tmpres['Create_time']) + && ! empty($tmpres['Create_time']) + ) { + $schema_create .= $this->exportComment( + __('Creation') . ': ' + . PMA_localisedDate(strtotime($tmpres['Create_time'])) + ); + $new_crlf = $this->exportComment() . $crlf; + } + + if ($show_dates + && isset($tmpres['Update_time']) + && ! empty($tmpres['Update_time']) + ) { + $schema_create .= $this->exportComment( + __('Last update') . ': ' + . PMA_localisedDate(strtotime($tmpres['Update_time'])) + ); + $new_crlf = $this->exportComment() . $crlf; + } + + if ($show_dates + && isset($tmpres['Check_time']) + && ! empty($tmpres['Check_time']) + ) { + $schema_create .= $this->exportComment( + __('Last check') . ': ' + . PMA_localisedDate(strtotime($tmpres['Check_time'])) + ); + $new_crlf = $this->exportComment() . $crlf; + } + } + PMA_DBI_free_result($result); + } + + $schema_create .= $new_crlf; + + // no need to generate a DROP VIEW here, it was done earlier + if (! empty($sql_drop_table) && ! PMA_Table::isView($db, $table)) { + $schema_create .= 'DROP TABLE IF EXISTS ' + . PMA_backquote($table, $sql_backquotes) . ';' . $crlf; + } + + // Complete table dump, + // Whether to quote table and column names or not + // Drizzle always quotes names + if (! PMA_DRIZZLE) { + if ($sql_backquotes) { + PMA_DBI_query('SET SQL_QUOTE_SHOW_CREATE = 1'); + } else { + PMA_DBI_query('SET SQL_QUOTE_SHOW_CREATE = 0'); + } + } + + // I don't see the reason why this unbuffered query could cause problems, + // because SHOW CREATE TABLE returns only one row, and we free the + // results below. Nonetheless, we got 2 user reports about this + // (see bug 1562533) so I removed the unbuffered mode. + // $result = PMA_DBI_query('SHOW CREATE TABLE ' . PMA_backquote($db) + // . '.' . PMA_backquote($table), null, PMA_DBI_QUERY_UNBUFFERED); + // + // Note: SHOW CREATE TABLE, at least in MySQL 5.1.23, does not + // produce a displayable result for the default value of a BIT + // column, nor does the mysqldump command. See MySQL bug 35796 + $result = PMA_DBI_try_query( + 'SHOW CREATE TABLE ' . PMA_backquote($db) . '.' . PMA_backquote($table) + ); + // an error can happen, for example the table is crashed + $tmp_error = PMA_DBI_getError(); + if ($tmp_error) { + return $this->exportComment(__('in use') . '(' . $tmp_error . ')'); + } + + if ($result != false && ($row = PMA_DBI_fetch_row($result))) { + $create_query = $row[1]; + unset($row); + + // Convert end of line chars to one that we want (note that MySQL + // doesn't return query it will accept in all cases) + if (strpos($create_query, "(\r\n ")) { + $create_query = str_replace("\r\n", $crlf, $create_query); + } elseif (strpos($create_query, "(\n ")) { + $create_query = str_replace("\n", $crlf, $create_query); + } elseif (strpos($create_query, "(\r ")) { + $create_query = str_replace("\r", $crlf, $create_query); + } + + /* + * Drop database name from VIEW creation. + * + * This is a bit tricky, but we need to issue SHOW CREATE TABLE with + * database name, but we don't want name to show up in CREATE VIEW + * statement. + */ + if ($view) { + $create_query = preg_replace( + '/' . PMA_backquote($db) . '\./', + '', + $create_query + ); + } + + // Should we use IF NOT EXISTS? + if (isset($GLOBALS['sql_if_not_exists'])) { + $create_query = preg_replace( + '/^CREATE TABLE/', + 'CREATE TABLE IF NOT EXISTS', + $create_query + ); + } + + // Drizzle (checked on 2011.03.13) returns ROW_FORMAT surrounded + // with quotes, which is not accepted by parser + if (PMA_DRIZZLE) { + $create_query = preg_replace( + '/ROW_FORMAT=\'(\S+)\'/', + 'ROW_FORMAT=$1', + $create_query + ); + } + + // are there any constraints to cut out? + if (preg_match('@CONSTRAINT|FOREIGN[\s]+KEY@', $create_query)) { + + // Split the query into lines, so we can easily handle it. + // We know lines are separated by $crlf (done few lines above). + $sql_lines = explode($crlf, $create_query); + $sql_count = count($sql_lines); + + // lets find first line with constraints + for ($i = 0; $i < $sql_count; $i++) { + if (preg_match( + '@^[\s]*(CONSTRAINT|FOREIGN[\s]+KEY)@', + $sql_lines[$i] + )) { + break; + } + } + + // If we really found a constraint + if ($i != $sql_count) { + + // remove, from the end of create statement + $sql_lines[$i - 1] = preg_replace( + '@,$@', + '', + $sql_lines[$i - 1] + ); + + // prepare variable for constraints + if (! isset($sql_constraints)) { + if (isset($GLOBALS['no_constraints_comments'])) { + $sql_constraints = ''; + } else { + $sql_constraints = $crlf + . $this->exportComment() + . $this->exportComment( + __('Constraints for dumped tables') + ) + . $this->exportComment(); + } + } + + // comments for current table + if (! isset($GLOBALS['no_constraints_comments'])) { + $sql_constraints .= $crlf + . $this->exportComment() + . $this->exportComment( + __('Constraints for table') + . ' ' + . PMA_backquote($table) + ) + . $this->exportComment(); + } + + // let's do the work + $sql_constraints_query .= 'ALTER TABLE ' + . PMA_backquote($table) . $crlf; + $sql_constraints .= 'ALTER TABLE ' + . PMA_backquote($table) . $crlf; + $sql_drop_foreign_keys .= 'ALTER TABLE ' + . PMA_backquote($db) . '.' + . PMA_backquote($table) . $crlf; + + $first = true; + for ($j = $i; $j < $sql_count; $j++) { + if (preg_match( + '@CONSTRAINT|FOREIGN[\s]+KEY@', + $sql_lines[$j] + )) { + if (! $first) { + $sql_constraints .= $crlf; + } + if (strpos($sql_lines[$j], 'CONSTRAINT') === false) { + $tmp_str = preg_replace( + '/(FOREIGN[\s]+KEY)/', + 'ADD \1', + $sql_lines[$j] + ); + $sql_constraints_query .= $tmp_str; + $sql_constraints .= $tmp_str; + } else { + $tmp_str = preg_replace( + '/(CONSTRAINT)/', + 'ADD \1', + $sql_lines[$j] + ); + $sql_constraints_query .= $tmp_str; + $sql_constraints .= $tmp_str; + preg_match( + '/(CONSTRAINT)([\s])([\S]*)([\s])/', + $sql_lines[$j], + $matches + ); + if (! $first) { + $sql_drop_foreign_keys .= ', '; + } + $sql_drop_foreign_keys .= 'DROP FOREIGN KEY ' + . $matches[3]; + } + $first = false; + } else { + break; + } + } + $sql_constraints .= ';' . $crlf; + $sql_constraints_query .= ';'; + + $create_query = implode( + $crlf, + array_slice($sql_lines, 0, $i) + ) + . $crlf + . implode( + $crlf, + array_slice($sql_lines, $j, $sql_count - 1) + ); + unset($sql_lines); + } + } + $schema_create .= $create_query; + } + + // remove a possible "AUTO_INCREMENT = value" clause + // that could be there starting with MySQL 5.0.24 + // in Drizzle it's useless as it contains the value given at table + // creation time + $schema_create = preg_replace( + '/AUTO_INCREMENT\s*=\s*([0-9])+/', + '', + $schema_create + ); + + $schema_create .= $auto_increment; + + PMA_DBI_free_result($result); + return $schema_create . ($add_semicolon ? ';' . $crlf : ''); + } // end of the 'getTableDef()' function + + /** + * Returns $table's comments, relations etc. + * + * @param string $db database name + * @param string $table table name + * @param string $crlf end of line sequence + * @param bool $do_relation whether to include relation comments + * @param bool $do_mime whether to include mime comments + * + * @return string resulting comments + */ + private function getTableComments( + $db, + $table, + $crlf, + $do_relation = false, + $do_mime = false + ) { + $cfgRelation = $this->getCfgRelation(); + $sql_backquotes = $this->getSql_backquotes(); + + $schema_create = ''; + + // Check if we can use Relations + if ($do_relation && ! empty($cfgRelation['relation'])) { + // Find which tables are related with the current one and write it in + // an array + $res_rel = PMA_getForeigners($db, $table); + + if ($res_rel && count($res_rel) > 0) { + $have_rel = true; + } else { + $have_rel = false; + } + } else { + $have_rel = false; + } // end if + + if ($do_mime && $cfgRelation['mimework']) { + if (! ($mime_map = PMA_getMIME($db, $table, true))) { + unset($mime_map); + } + } + + if (isset($mime_map) && count($mime_map) > 0) { + $schema_create .= $this->possibleCRLF() + . $this->exportComment() + . $this->exportComment( + __('MIME TYPES FOR TABLE'). ' ' + . PMA_backquote($table, $sql_backquotes) . ':' + ); + @reset($mime_map); + foreach ($mime_map AS $mime_field => $mime) { + $schema_create .= + $this->exportComment( + ' ' + . PMA_backquote($mime_field, $sql_backquotes) + ) + . $this->exportComment( + ' ' + . PMA_backquote($mime['mimetype'], $sql_backquotes) + ); + } + $schema_create .= $this->exportComment(); + } + + if ($have_rel) { + $schema_create .= $this->possibleCRLF() + . $this->exportComment() + . $this->exportComment( + __('RELATIONS FOR TABLE') . ' ' + . PMA_backquote($table, $sql_backquotes) + . ':' + ); + foreach ($res_rel AS $rel_field => $rel) { + $schema_create .= + $this->exportComment( + ' ' + . PMA_backquote($rel_field, $sql_backquotes) + ) + . $this->exportComment( + ' ' + . PMA_backquote($rel['foreign_table'], $sql_backquotes) + . ' -> ' + . PMA_backquote($rel['foreign_field'], $sql_backquotes) + ); + } + $schema_create .= $this->exportComment(); + } + + return $schema_create; + + } // end of the 'getTableComments()' function + + /** + * Outputs table's structure + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $export_mode 'create_table','triggers','create_view', + * 'stand_in' + * @param string $export_type 'server', 'database', 'table' + * @param bool $relation whether to include relation comments + * @param bool $comments whether to include the pmadb-style column comments + * as comments in the structure; this is deprecated + * but the parameter is left here because export.php + * calls exportStructure() also for other export + * types which use this parameter + * @param bool $mime whether to include mime comments + * @param bool $dates whether to include creation/update/check dates + * + * @return bool Whether it succeeded + */ + public function exportStructure( + $db, + $table, + $crlf, + $error_url, + $export_mode, + $export_type, + $relation = false, + $comments = false, + $mime = false, + $dates = false + ) { + $formatted_table_name = (isset($GLOBALS['sql_backquotes'])) + ? PMA_backquote($table) + : '\'' . $table . '\''; + $dump = $this->possibleCRLF() + . $this->exportComment(str_repeat('-', 56)) + . $this->possibleCRLF() + . $this->exportComment(); + + switch($export_mode) { + case 'create_table': + $dump .= $this->exportComment( + __('Table structure for table') . ' '. $formatted_table_name + ); + $dump .= $this->exportComment(); + $dump .= getTableDef($db, $table, $crlf, $error_url, $dates); + $dump .= $this->getTableComments($db, $table, $crlf, $relation, $mime); + break; + case 'triggers': + $dump = ''; + $triggers = PMA_DBI_get_triggers($db, $table); + if ($triggers) { + $dump .= $this->possibleCRLF() + . $this->exportComment() + . $this->exportComment( + __('Triggers') . ' ' . $formatted_table_name + ) + . $this->exportComment(); + $delimiter = '//'; + foreach ($triggers as $trigger) { + $dump .= $trigger['drop'] . ';' . $crlf; + $dump .= 'DELIMITER ' . $delimiter . $crlf; + $dump .= $trigger['create']; + $dump .= 'DELIMITER ;' . $crlf; + } + } + break; + case 'create_view': + $dump .= + $this->exportComment( + __('Structure for view') + . ' ' + . $formatted_table_name + ) + . $this->exportComment(); + // delete the stand-in table previously created (if any) + if ($export_type != 'table') { + $dump .= 'DROP TABLE IF EXISTS ' + . PMA_backquote($table) . ';' . $crlf; + } + $dump .= getTableDef( + $db, $table, $crlf, $error_url, $dates, true, true + ); + break; + case 'stand_in': + $dump .= + $this->exportComment( + __('Stand-in structure for view') . ' ' . $formatted_table_name + ) + . $this->exportComment(); + // export a stand-in definition to resolve view dependencies + $dump .= getTableDefStandIn($db, $table, $crlf); + } // end switch + + // this one is built by getTableDef() to use in table copy/move + // but not in the case of export + unset($GLOBALS['sql_constraints_query']); + + return PMA_exportOutputHandler($dump); + } + + /** + * Outputs the content of a table in SQL format + * + * @param string $db database name + * @param string $table table name + * @param string $crlf the end of line sequence + * @param string $error_url the url to go back in case of error + * @param string $sql_query SQL query for obtaining data + * + * @return bool Whether it succeeded + */ + public function exportData($db, $table, $crlf, $error_url, $sql_query) + { + $sql_backquotes = $this->getSql_backquotes(); + $current_row = $this->getCurrent_row(); + + $formatted_table_name = (isset($GLOBALS['sql_backquotes'])) + ? PMA_backquote($table) + : '\'' . $table . '\''; + + // Do not export data for a VIEW + // (For a VIEW, this is called only when exporting a single VIEW) + if (PMA_Table::isView($db, $table)) { + $head = $this->possibleCRLF() + . $this->exportComment() + . $this->exportComment('VIEW ' . ' ' . $formatted_table_name) + . $this->exportComment(__('Data') . ': ' . __('None')) + . $this->exportComment() + . $this->possibleCRLF(); + + if (! PMA_exportOutputHandler($head)) { + return false; + } + return true; + } + + // analyze the query to get the true column names, not the aliases + // (this fixes an undefined index, also if Complete inserts + // are used, we did not get the true column name in case of aliases) + $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($sql_query)); + + $result = PMA_DBI_try_query($sql_query, null, PMA_DBI_QUERY_UNBUFFERED); + // a possible error: the table has crashed + $tmp_error = PMA_DBI_getError(); + if ($tmp_error) { + return PMA_exportOutputHandler( + $this->exportComment( + __('Error reading data:') . ' (' . $tmp_error . ')' + ) + ); + } + + if ($result != false) { + $fields_cnt = PMA_DBI_num_fields($result); + + // Get field information + $fields_meta = PMA_DBI_get_fields_meta($result); + $field_flags = array(); + for ($j = 0; $j < $fields_cnt; $j++) { + $field_flags[$j] = PMA_DBI_field_flags($result, $j); + } + + for ($j = 0; $j < $fields_cnt; $j++) { + if (isset($analyzed_sql[0]['select_expr'][$j]['column'])) { + $field_set[$j] = PMA_backquote( + $analyzed_sql[0]['select_expr'][$j]['column'], + $sql_backquotes + ); + } else { + $field_set[$j] = PMA_backquote( + $fields_meta[$j]->name, + $sql_backquotes + ); + } + } + + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'UPDATE' + ) { + // update + $schema_insert = 'UPDATE '; + if (isset($GLOBALS['sql_ignore'])) { + $schema_insert .= 'IGNORE '; + } + // avoid EOL blank + $schema_insert .= PMA_backquote($table, $sql_backquotes) . ' SET'; + } else { + // insert or replace + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'REPLACE' + ) { + $sql_command = 'REPLACE'; + } else { + $sql_command = 'INSERT'; + } + + // delayed inserts? + if (isset($GLOBALS['sql_delayed'])) { + $insert_delayed = ' DELAYED'; + } else { + $insert_delayed = ''; + } + + // insert ignore? + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'INSERT' + && isset($GLOBALS['sql_ignore']) + ) { + $insert_delayed .= ' IGNORE'; + } + //truncate table before insert + if (isset($GLOBALS['sql_truncate']) + && $GLOBALS['sql_truncate'] + && $sql_command == 'INSERT' + ) { + $truncate = 'TRUNCATE TABLE ' + . PMA_backquote($table, $sql_backquotes) . ";"; + $truncatehead = $this->possibleCRLF() + . $this->exportComment() + . $this->exportComment( + __('Truncate table before insert') . ' ' + . $formatted_table_name + ) + . $this->exportComment() + . $crlf; + PMA_exportOutputHandler($truncatehead); + PMA_exportOutputHandler($truncate); + } else { + $truncate = ''; + } + // scheme for inserting fields + if ($GLOBALS['sql_insert_syntax'] == 'complete' + || $GLOBALS['sql_insert_syntax'] == 'both' + ) { + $fields = implode(', ', $field_set); + $schema_insert = $sql_command . $insert_delayed .' INTO ' + . PMA_backquote($table, $sql_backquotes) + // avoid EOL blank + . ' (' . $fields . ') VALUES'; + } else { + $schema_insert = $sql_command . $insert_delayed .' INTO ' + . PMA_backquote($table, $sql_backquotes) + . ' VALUES'; + } + } + + //\x08\\x09, not required + $search = array("\x00", "\x0a", "\x0d", "\x1a"); + $replace = array('\0', '\n', '\r', '\Z'); + $current_row = 0; + $query_size = 0; + if (($GLOBALS['sql_insert_syntax'] == 'extended' + || $GLOBALS['sql_insert_syntax'] == 'both') + && (! isset($GLOBALS['sql_type']) + || $GLOBALS['sql_type'] != 'UPDATE') + ) { + $separator = ','; + $schema_insert .= $crlf; + } else { + $separator = ';'; + } + + while ($row = PMA_DBI_fetch_row($result)) { + if ($current_row == 0) { + $head = $this->possibleCRLF() + . $this->exportComment() + . $this->exportComment( + __('Dumping data for table') . ' ' + . $formatted_table_name + ) + . $this->exportComment() + . $crlf; + if (! PMA_exportOutputHandler($head)) { + return false; + } + } + $current_row++; + for ($j = 0; $j < $fields_cnt; $j++) { + // NULL + if (! isset($row[$j]) || is_null($row[$j])) { + $values[] = 'NULL'; + } elseif ($fields_meta[$j]->numeric + && $fields_meta[$j]->type != 'timestamp' + && ! $fields_meta[$j]->blob + ) { + // a number + // timestamp is numeric on some MySQL 4.1, BLOBs are + // sometimes numeric + $values[] = $row[$j]; + } elseif (stristr($field_flags[$j], 'BINARY') + && $fields_meta[$j]->blob + && isset($GLOBALS['sql_hex_for_blob']) + ) { + // a true BLOB + // - mysqldump only generates hex data when the --hex-blob + // option is used, for fields having the binary attribute + // no hex is generated + // - a TEXT field returns type blob but a real blob + // returns also the 'binary' flag + + // empty blobs need to be different, but '0' is also empty + // :-( + if (empty($row[$j]) && $row[$j] != '0') { + $values[] = '\'\''; + } else { + $values[] = '0x' . bin2hex($row[$j]); + } + } elseif ($fields_meta[$j]->type == 'bit') { + // detection of 'bit' works only on mysqli extension + $values[] = "b'" . PMA_sqlAddSlashes( + PMA_printable_bit_value( + $row[$j], $fields_meta[$j]->length + ) + ) + . "'"; + } else { + // something else -> treat as a string + $values[] = '\'' + . str_replace( + $search, $replace, PMA_sqlAddSlashes($row[$j]) + ) + . '\''; + } // end if + } // end for + + // should we make update? + if (isset($GLOBALS['sql_type']) + && $GLOBALS['sql_type'] == 'UPDATE' + ) { + + $insert_line = $schema_insert; + for ($i = 0; $i < $fields_cnt; $i++) { + if (0 == $i) { + $insert_line .= ' '; + } + if ($i > 0) { + // avoid EOL blank + $insert_line .= ','; + } + $insert_line .= $field_set[$i] . ' = ' . $values[$i]; + } + + list($tmp_unique_condition, $tmp_clause_is_unique) + = PMA_getUniqueCondition( + $result, + $fields_cnt, + $fields_meta, + $row + ); + $insert_line .= ' WHERE ' . $tmp_unique_condition; + unset($tmp_unique_condition, $tmp_clause_is_unique); + + } else { + + // Extended inserts case + if ($GLOBALS['sql_insert_syntax'] == 'extended' + || $GLOBALS['sql_insert_syntax'] == 'both' + ) { + if ($current_row == 1) { + $insert_line = $schema_insert . '(' + . implode(', ', $values) . ')'; + } else { + $insert_line = '(' . implode(', ', $values) . ')'; + $sql_max_size = $GLOBALS['sql_max_query_size']; + if (isset($sql_max_size) + && $sql_max_size > 0 + && $query_size + strlen($insert_line) > $sql_max_size + ) { + if (! PMA_exportOutputHandler(';' . $crlf)) { + return false; + } + $query_size = 0; + $current_row = 1; + $insert_line = $schema_insert . $insert_line; + } + } + $query_size += strlen($insert_line); + // Other inserts case + } else { + $insert_line = $schema_insert + . '(' + . implode(', ', $values) + . ')'; + } + } + unset($values); + + if (! PMA_exportOutputHandler( + ($current_row == 1 ? '' : $separator . $crlf) + . $insert_line + )) { + return false; + } + + } // end while + if ($current_row > 0) { + if (! PMA_exportOutputHandler(';' . $crlf)) { + return false; + } + } + } // end if ($result != false) + PMA_DBI_free_result($result); + + return true; + } // end of the 'exportData()' function + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + private function getPlugin_param() + { + return $this->plugin_param; + } + + private function setPlugin_param($plugin_param) + { + $this->plugin_param = $plugin_param; + } + + private function getMysql_charset_map() + { + return $this->mysql_charset_map; + } + + private function setMysql_charset_map($mysql_charset_map) + { + $this->mysql_charset_map = $mysql_charset_map; + } + + private function getSql_drop_table() + { + return $this->sql_drop_table; + } + + private function setSql_drop_table($sql_drop_table) + { + $this->sql_drop_table = $sql_drop_table; + } + + private function getSql_backquotes() + { + return $this->sql_backquotes; + } + + private function setSql_backquotes($sql_backquotes) + { + $this->sql_backquotes = $sql_backquotes; + } + + private function getSql_constraints() + { + return $this->sql_constraints; + } + + private function setSql_constraints($sql_constraints) + { + $this->sql_constraints = $sql_constraints; + } + + private function getSql_constraints_query() + { + return $this->sql_constraints_query; + } + + private function setSql_constraints_query($sql_constraints_query) + { + $this->sql_constraints_query = $sql_constraints_query; + } + + private function getSql_drop_foreign_keys() + { + return $this->sql_drop_foreign_keys; + } + + private function setSql_drop_foreign_keys($sql_drop_foreign_keys) + { + $this->sql_drop_foreign_keys = $sql_drop_foreign_keys; + } + + private function getCfgRelation() + { + return $this->cfgRelation; + } + + private function setCfgRelation($cfgRelation) + { + $this->cfgRelation = $cfgRelation; + } + + private function getCurrent_row() + { + return $this->current_row; + } + + private function setCurrent_row($current_row) + { + $this->current_row = $current_row; + } +} \ No newline at end of file diff --git a/libraries/plugins/import/ImportSQL.class.php b/libraries/plugins/import/ImportSQL.class.php new file mode 100644 index 0000000000..eb1b510af7 --- /dev/null +++ b/libraries/plugins/import/ImportSQL.class.php @@ -0,0 +1,392 @@ +setProperties(); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $this->properties = array( + 'text' => __('SQL'), + 'extension' => 'sql', + 'options' => array(), + 'options_text' => __('Options'), + ); + + $compats = PMA_DBI_getCompatibilities(); + if (count($compats) > 0) { + $values = array(); + foreach ($compats as $val) { + $values[$val] = $val; + } + $this->properties['options'] = array( + array('type' => 'begin_group', 'name' => 'general_opts'), + array( + 'type' => 'select', + 'name' => 'compatibility', + 'text' => __('SQL compatibility mode:'), + 'values' => $values, + 'doc' => array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + ), + ), + array( + 'type' => 'bool', + 'name' => 'no_auto_value_on_zero', + 'text' => __('Do not use AUTO_INCREMENT for zero values'), + 'doc' => array( + 'manual_MySQL_Database_Administration', + 'Server_SQL_mode', + 'sqlmode_no_auto_value_on_zero' + ), + + ), + array('type' => 'end_group'), + ); + } + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + */ + public function doImport() + { + // initialize the general import variables + $this->initImportCommonVariables(); + + $error = $this->getError(); + $timeout_passed = $this->getTimeout_passed(); + + $buffer = ''; + // Defaults for parser + $sql = ''; + $start_pos = 0; + $i = 0; + $len= 0; + $big_value = 2147483647; + $delimiter_keyword = 'DELIMITER '; // include the space because it's mandatory + $length_of_delimiter_keyword = strlen($delimiter_keyword); + + if (isset($_POST['sql_delimiter'])) { + $sql_delimiter = $_POST['sql_delimiter']; + } else { + $sql_delimiter = ';'; + } + + // Handle compatibility options + $sql_modes = array(); + if (isset($_REQUEST['sql_compatibility']) + && 'NONE' != $_REQUEST['sql_compatibility'] + ) { + $sql_modes[] = $_REQUEST['sql_compatibility']; + } + if (isset($_REQUEST['sql_no_auto_value_on_zero'])) { + $sql_modes[] = 'NO_AUTO_VALUE_ON_ZERO'; + } + if (count($sql_modes) > 0) { + PMA_DBI_try_query('SET SQL_MODE="' . implode(',', $sql_modes) . '"'); + } + unset($sql_modes); + + /** + * will be set in PMA_importGetNextChunk() + * + * @global boolean $GLOBALS['finished'] + */ + $GLOBALS['finished'] = false; + + while (! ($GLOBALS['finished'] && $i >= $len) && ! $error && ! $timeout_passed) { + $data = PMA_importGetNextChunk(); + if ($data === false) { + // subtract data we didn't handle yet and stop processing + $offset -= strlen($buffer); + break; + } elseif ($data === true) { + // Handle rest of buffer + } else { + // Append new data to buffer + $buffer .= $data; + // free memory + unset($data); + // Do not parse string when we're not at the end and don't have ; inside + if ((strpos($buffer, $sql_delimiter, $i) === false) + && ! $GLOBALS['finished'] + ) { + continue; + } + } + // Current length of our buffer + $len = strlen($buffer); + + // Grab some SQL queries out of it + while ($i < $len) { + $found_delimiter = false; + // Find first interesting character + $old_i = $i; + // this is about 7 times faster that looking for each sequence i + // one by one with strpos() + $match = preg_match( + '/(\'|"|#|-- |\/\*|`|(?i)(? unclosed quote, + // but we handle it as end of query + if ($GLOBALS['finished']) { + $endq = true; + $i = $len - 1; + } + $found_delimiter = false; + break; + } + // Was not the quote escaped? + $j = $pos - 1; + while ($buffer[$j] == '\\') { + $j--; + } + // Even count means it was not escaped + $endq = (((($pos - 1) - $j) % 2) == 0); + // Skip the string + $i = $pos; + + if ($first_sql_delimiter < $pos) { + $found_delimiter = false; + } + } + if (!$endq) { + break; + } + $i++; + // Aren't we at the end? + if ($GLOBALS['finished'] && $i == $len) { + $i--; + } else { + continue; + } + } + + // Not enough data to decide + if ((($i == ($len - 1) && ($ch == '-' || $ch == '/')) + || ($i == ($len - 2) && (($ch == '-' && $buffer[$i + 1] == '-') + || ($ch == '/' && $buffer[$i + 1] == '*')))) && !$GLOBALS['finished'] + ) { + break; + } + + // Comments + if ($ch == '#' + || ($i < ($len - 1) && $ch == '-' && $buffer[$i + 1] == '-' + && (($i < ($len - 2) && $buffer[$i + 2] <= ' ') + || ($i == ($len - 1) && $GLOBALS['finished']))) + || ($i < ($len - 1) && $ch == '/' && $buffer[$i + 1] == '*') + ) { + // Copy current string to SQL + if ($start_pos != $i) { + $sql .= substr($buffer, $start_pos, $i - $start_pos); + } + // Skip the rest + $start_of_comment = $i; + // do not use PHP_EOL here instead of "\n", because the export + // file might have been produced on a different system + $i = strpos($buffer, $ch == '/' ? '*/' : "\n", $i); + // didn't we hit end of string? + if ($i === false) { + if ($GLOBALS['finished']) { + $i = $len - 1; + } else { + break; + } + } + // Skip * + if ($ch == '/') { + $i++; + } + // Skip last char + $i++; + // We need to send the comment part in case we are defining + // a procedure or function and comments in it are valuable + $sql .= substr($buffer, $start_of_comment, $i - $start_of_comment); + // Next query part will start here + $start_pos = $i; + // Aren't we at the end? + if ($i == $len) { + $i--; + } else { + continue; + } + } + // Change delimiter, if redefined, and skip it (don't send to server!) + if (strtoupper(substr($buffer, $i, $length_of_delimiter_keyword)) == $delimiter_keyword + && ($i + $length_of_delimiter_keyword < $len) + ) { + // look for EOL on the character immediately after 'DELIMITER ' + // (see previous comment about PHP_EOL) + $new_line_pos = strpos($buffer, "\n", $i + $length_of_delimiter_keyword); + // it might happen that there is no EOL + if (false === $new_line_pos) { + $new_line_pos = $len; + } + $sql_delimiter = substr( + $buffer, + $i + $length_of_delimiter_keyword, + $new_line_pos - $i - $length_of_delimiter_keyword + ); + $i = $new_line_pos + 1; + // Next query part will start here + $start_pos = $i; + continue; + } + + // End of SQL + if ($found_delimiter || ($GLOBALS['finished'] && ($i == $len - 1))) { + $tmp_sql = $sql; + if ($start_pos < $len) { + $length_to_grab = $i - $start_pos; + + if (! $found_delimiter) { + $length_to_grab++; + } + $tmp_sql .= substr($buffer, $start_pos, $length_to_grab); + unset($length_to_grab); + } + // Do not try to execute empty SQL + if (! preg_match('/^([\s]*;)*$/', trim($tmp_sql))) { + $sql = $tmp_sql; + PMA_importRunQuery( + $sql, + substr($buffer, 0, $i + strlen($sql_delimiter)) + ); + $buffer = substr($buffer, $i + strlen($sql_delimiter)); + // Reset parser: + $len = strlen($buffer); + $sql = ''; + $i = 0; + $start_pos = 0; + // Any chance we will get a complete query? + //if ((strpos($buffer, ';') === false) && !$GLOBALS['finished']) { + if (strpos($buffer, $sql_delimiter) === false + && ! $GLOBALS['finished'] + ) { + break; + } + } else { + $i++; + $start_pos = $i; + } + } + } // End of parser loop + } // End of import loop + // Commit any possible data in buffers + PMA_importRunQuery('', substr($buffer, 0, $len)); + PMA_importRunQuery(); + } +} \ No newline at end of file diff --git a/libraries/plugins/import/ImportXML.class.php b/libraries/plugins/import/ImportXML.class.php new file mode 100644 index 0000000000..677ef8ef1d --- /dev/null +++ b/libraries/plugins/import/ImportXML.class.php @@ -0,0 +1,458 @@ +setProperties(); + } + + /** + * Initialize the local variables that are used specific for import XML + * + * @global type $table + * @global type $tables + * + * @return void + */ + private function initLocalVariables() + { + global $table; + global $tables; + $this->setTable($table); + $this->setTables($tables); + } + + /** + * Sets the import plugin properties. + * Called in the constructor. + * + * @return void + */ + protected function setProperties() + { + $this->properties = array( + 'text' => __('XML'), + 'extension' => 'xml', + 'mime_type' => 'text/xml', + 'options' => array(), + 'options_text' => __('Options') + ); + } + + /** + * This method is called when any PluginManager to which the observer + * is attached calls PluginManager::notify() + * + * @param SplSubject $subject The PluginManager notifying the observer + * of an update. + * + * @return void + */ + public function update (SplSubject $subject) + { + } + + /** + * Handles the whole import logic + */ + public function doImport() + { + // initialize the general import variables + $this->initImportCommonVariables(); + + // initialize the specific import xml variables + $this->initLocalVariables(); + + $error = $this->getError(); + $timeout_passed = $this->getTimeout_passed(); + $db = $this->getDb(); + // this is used in other functions while doImport() is being run + global $finished; + + $i = 0; + $len = 0; + $buffer = ""; + + /** + * Read in the file via PMA_importGetNextChunk so that + * it can process compressed files + */ + while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) { + $data = PMA_importGetNextChunk(); + if ($data === false) { + /* subtract data we didn't handle yet and stop processing */ + $offset -= strlen($buffer); + break; + } elseif ($data === true) { + /* Handle rest of buffer */ + } else { + /* Append new data to buffer */ + $buffer .= $data; + unset($data); + } + } + + unset($data); + + /** + * Disable loading of external XML entities. + */ + libxml_disable_entity_loader(); + + /** + * Load the XML string + * + * The option LIBXML_COMPACT is specified because it can + * result in increased performance without the need to + * alter the code in any way. It's basically a freebee. + */ + $xml = simplexml_load_string($buffer, "SimpleXMLElement", LIBXML_COMPACT); + + unset($buffer); + + /** + * The XML was malformed + */ + if ($xml === false) { + PMA_Message::error(__( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ))->display(); + unset($xml); + $GLOBALS['finished'] = false; + return; + } + + /** + * Table accumulator + */ + $tables = array(); + /** + * Row accumulator + */ + $rows = array(); + + /** + * Temp arrays + */ + $tempRow = array(); + $tempCells = array(); + + /** + * CREATE code included (by default: no) + */ + $struct_present = false; + + /** + * Analyze the data in each table + */ + $namespaces = $xml->getNameSpaces(true); + + /** + * Get the database name, collation and charset + */ + $db_attr = $xml->children($namespaces['pma'])->{'structure_schemas'}->{'database'}; + + if ($db_attr instanceof SimpleXMLElement) { + $db_attr = $db_attr->attributes(); + $db_name = (string)$db_attr['name']; + $collation = (string)$db_attr['collation']; + $charset = (string)$db_attr['charset']; + } else { + /** + * If the structure section is not present + * get the database name from the data section + */ + $db_attr = $xml->children()->attributes(); + $db_name = (string)$db_attr['name']; + $collation = null; + $charset = null; + } + + /** + * The XML was malformed + */ + if ($db_name === null) { + PMA_Message::error(__( + 'The XML file specified was either malformed or incomplete.' + . ' Please correct the issue and try again.' + ))->display(); + unset($xml); + $GLOBALS['finished'] = false; + return; + } + + /** + * Retrieve the structure information + */ + if (isset($namespaces['pma'])) { + /** + * Get structures for all tables + */ + $struct = $xml->children($namespaces['pma']); + + $create = array(); + + foreach ($struct as $tier1 => $val1) { + foreach ($val1 as $tier2 => $val2) { + // Need to select the correct database for the creation of tables, + // views, triggers, etc. + /** + * @todo Generating a USE here blocks importing of a table + * into another database. + */ + $attrs = $val2->attributes(); + $create[] = "USE " . PMA_backquote($attrs["name"]); + + foreach ($val2 as $val3) { + /** + * Remove the extra cosmetic spacing + */ + $val3 = str_replace(" ", "", (string)$val3); + $create[] = $val3; + } + } + } + + $struct_present = true; + } + + /** + * Move down the XML tree to the actual data + */ + $xml = $xml->children()->children(); + + $data_present = false; + + /** + * Only attempt to analyze/collect data if there is data present + */ + if ($xml && @$xml->count()) { + $data_present = true; + + /** + * Process all database content + */ + foreach ($xml as $k1 => $v1) { + $tbl_attr = $v1->attributes(); + + $isInTables = false; + for ($i = 0; $i < count($tables); ++$i) { + if (! strcmp($tables[$i][TBL_NAME], (string)$tbl_attr['name'])) { + $isInTables = true; + break; + } + } + + if ($isInTables == false) { + $tables[] = array((string)$tbl_attr['name']); + } + + foreach ($v1 as $k2 => $v2) { + $row_attr = $v2->attributes(); + if (! array_search((string)$row_attr['name'], $tempRow)) { + $tempRow[] = (string)$row_attr['name']; + } + $tempCells[] = (string)$v2; + } + + $rows[] = array((string)$tbl_attr['name'], $tempRow, $tempCells); + + $tempRow = array(); + $tempCells = array(); + } + + unset($tempRow); + unset($tempCells); + unset($xml); + + /** + * Bring accumulated rows into the corresponding table + */ + $num_tbls = count($tables); + for ($i = 0; $i < $num_tbls; ++$i) { + for ($j = 0; $j < count($rows); ++$j) { + if (! strcmp($tables[$i][TBL_NAME], $rows[$j][TBL_NAME])) { + if (! isset($tables[$i][COL_NAMES])) { + $tables[$i][] = $rows[$j][COL_NAMES]; + } + + $tables[$i][ROWS][] = $rows[$j][ROWS]; + } + } + } + + unset($rows); + + if (! $struct_present) { + $analyses = array(); + + $len = count($tables); + for ($i = 0; $i < $len; ++$i) { + $analyses[] = PMA_analyzeTable($tables[$i]); + } + } + } + + unset($xml); + unset($tempRows); + unset($tempCells); + unset($rows); + + /** + * Only build SQL from data if there is data present + */ + if ($data_present) { + /** + * Set values to NULL if they were not present + * to maintain PMA_buildSQL() call integrity + */ + if (! isset($analyses)) { + $analyses = null; + if (! $struct_present) { + $create = null; + } + } + } + + /** + * string $db_name (no backquotes) + * + * array $table = array(table_name, array() column_names, array()() rows) + * array $tables = array of "$table"s + * + * array $analysis = array(array() column_types, array() column_sizes) + * array $analyses = array of "$analysis"s + * + * array $create = array of SQL strings + * + * array $options = an associative array of options + */ + + /* Set database name to the currently selected one, if applicable */ + if (strlen($db)) { + /* Override the database name in the XML file, if one is selected */ + $db_name = $db; + $options = array('create_db' => false); + } else { + if ($db_name === null) { + $db_name = 'XML_DB'; + } + + /* Set database collation/charset */ + $options = array( + 'db_collation' => $collation, + 'db_charset' => $charset, + ); + } + + /* Created and execute necessary SQL statements from data */ + PMA_buildSQL($db_name, $tables, $analyses, $create, $options); + + unset($analyses); + unset($tables); + unset($create); + + /* Commit any possible data in buffers */ + PMA_importRunQuery(); + } + + + /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */ + + + /** + * Gets the database name + * + * @return string + */ + public function getDb() + { + return $this->db; + } + + /** + * Sets the database name + * + * @param String $db database name + * + * @return void + */ + public function setDb($db) + { + $this->db = $db; + } + + private function getTable() + { + return $this->table; + } + + private function setTable($table) + { + $this->table = $table; + } + + private function getTables() + { + return $this->tables; + } + + private function setTables($tables) + { + $this->tables = $tables; + } +} \ No newline at end of file