Copyright (C) 2013 - 2023 All Rights Reserved. Contributor(s): Mark J Crane */ //define the schema class if (!class_exists('schema')) { class schema { //define variables private $database; public $apps; public $db_type; public $result; public $data_types; //class constructor public function __construct() { //includes files require dirname(__DIR__, 2) . "/resources/require.php"; //connect to the database $this->database = database::new(); //get the list of installed apps from the core and mod directories $config_list = glob($_SERVER["DOCUMENT_ROOT"] . PROJECT_PATH . "/*/*/app_config.php"); $x=0; foreach ($config_list as $config_path) { try { include($config_path); } catch (Exception $e) { //echo 'Caught exception: ', $e->getMessage(), "\n"; } $x++; } $this->apps = $apps; } //create the database schema public function sql() { $sql = ''; $sql_schema = ''; foreach ($this->apps as $app) { if (isset($app['db']) && count($app['db'])) { foreach ($app['db'] as $row) { //create the sql string $table_name = $row['table']['name']; $sql = "CREATE TABLE " . $row['table']['name'] . " (\n"; $field_count = 0; foreach ($row['fields'] as $field) { if (!empty($field['deprecated']) and ($field['deprecated'] == "true")) { //skip this field } else { if ($field_count > 0 ) { $sql .= ",\n"; } if (is_array($field['name'])) { $sql .= $field['name']['text']." "; } else { $sql .= $field['name']." "; } if (is_array($field['type'])) { $sql .= $field['type'][$this->db_type]; } else { $sql .= $field['type']; } if (isset($field['key']) && isset($field['key']['type']) && ($field['key']['type'] == "primary")) { $sql .= " PRIMARY KEY"; } if (isset($field['key']) && isset($field['key']['type']) && ($field['key']['type'] == "foreign")) { if ($this->db_type == "pgsql") { //$sql .= " references ".$field['key']['reference']['table']."(".$field['key']['reference']['field'].")"; } if ($this->db_type == "sqlite") { //$sql .= " references ".$field['key']['reference']['table']."(".$field['key']['reference']['field'].")"; } if ($this->db_type == "mysql") { //$sql .= " references ".$field['key']['reference']['table']."(".$field['key']['reference']['field'].")"; } } $field_count++; } } if ($this->db_type == "mysql") { $sql .= ") ENGINE=INNODB;"; } else { $sql .= ");"; } $this->result['sql'][] = $sql; unset($sql); } } } } //create the database schema public function exec() { foreach ($this->result['sql'] as $sql) { //start the sql transaction $this->database->beginTransaction(); //execute the sql query try { $this->database->query($sql); } catch (PDOException $error) { echo "error: " . $error->getMessage() . " sql: $sql
"; } //complete the transaction $this->database->commit(); } } //check if a column exists in sqlite private function sqlite_column_exists($table_info, $column_name) { foreach ($table_info as $key => $row) { if ($row['name'] == $column_name) { return true; } } return $false; } //check if a column exists public function column_exists ($db_name, $table_name, $column_name) { if ($this->db_type == "sqlite") { $table_info = $this->table_info($db_name, $table_name); if ($this->sqlite_column_exists($table_info, $column_name)) { return true; } else { return false; } } if ($this->db_type == "pgsql") { $sql = "SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = '$table_name' limit 1) AND attname = '$column_name'; "; } if ($this->db_type == "mysql") { //$sql .= "SELECT * FROM information_schema.COLUMNS where TABLE_SCHEMA = '$db_name' and TABLE_NAME = '$table_name' and COLUMN_NAME = '$column_name' "; $sql = "show columns from $table_name where field = '$column_name' "; } if ($sql) { $prep_statement = $this->database->db->prepare($sql); $prep_statement->execute(); $result = $prep_statement->fetchAll(PDO::FETCH_NAMED); if (!$result) { return false; } if (count($result) > 0) { return true; } else { return false; } unset ($prep_statement); } } //get the table information public function table_info($db_name, $table_name) { if (empty($table_name)) { return false; } if ($this->db_type == "sqlite") { $sql = "PRAGMA table_info(".$table_name.");"; } if ($this->db_type == "pgsql") { $sql = "SELECT ordinal_position, "; $sql .= "column_name, "; $sql .= "data_type, "; $sql .= "column_default, "; $sql .= "is_nullable, "; $sql .= "character_maximum_length, "; $sql .= "numeric_precision "; $sql .= "FROM information_schema.columns "; $sql .= "WHERE table_name = '".$table_name."' "; $sql .= "and table_catalog = '".$db_name."' "; $sql .= "ORDER BY ordinal_position; "; } if ($this->db_type == "mysql") { $sql = "describe ".$table_name.";"; } $prep_statement = $this->database->db->prepare($sql); $prep_statement->execute(); return $prep_statement->fetchAll(PDO::FETCH_ASSOC); } //database table exists alternate private function db_table_exists_alternate ($db_type, $table_name) { $sql = "select count(*) from $table_name "; $result = $this->database->query($sql); if ($result > 0) { return true; //table exists } else { return false; //table doesn't exist } } //database table exists private function db_table_exists ($db_type, $db_name, $table_name) { $sql = ""; if ($db_type == "sqlite") { $sql .= "SELECT * FROM sqlite_master WHERE type='table' and name='$table_name' "; } if ($db_type == "pgsql") { $sql .= "select * from pg_tables where schemaname='public' and tablename = '$table_name' "; } if ($db_type == "mysql") { $sql .= "SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = '$db_name' and TABLE_NAME = '$table_name' "; } $prep_statement = $this->database->db->prepare(check_sql($sql)); $prep_statement->execute(); $result = $prep_statement->fetchAll(PDO::FETCH_NAMED); if (count($result) > 0) { return true; //table exists } else { return false; //table doesn't exist } } //database table information private function db_table_info($db_name, $db_type, $table_name) { if (empty($table_name)) { return false; } if ($db_type == "sqlite") { $sql = "PRAGMA table_info(".$table_name.");"; } if ($db_type == "pgsql") { $sql = "SELECT ordinal_position, "; $sql .= "column_name, "; $sql .= "data_type, "; $sql .= "column_default, "; $sql .= "is_nullable, "; $sql .= "character_maximum_length, "; $sql .= "numeric_precision "; $sql .= "FROM information_schema.columns "; $sql .= "WHERE table_name = '".$table_name."' "; $sql .= "and table_catalog = '".$db_name."' "; $sql .= "ORDER BY ordinal_position; "; } if ($db_type == "mysql") { $sql = "describe ".$table_name.";"; } $prep_statement = $this->database->db->prepare($sql); $prep_statement->execute(); return $prep_statement->fetchAll(PDO::FETCH_ASSOC); } //database type private function db_data_type($db_type, $table_info, $column_name) { if ($db_type == "sqlite") { foreach ($table_info as $key => $row) { if ($row['name'] == $column_name) { return $row['type']; } } } if ($db_type == "pgsql") { foreach ($table_info as $key => $row) { if ($row['column_name'] == $column_name) { return $row['data_type']; } } } if ($db_type == "mysql") { foreach ($table_info as $key => $row) { if ($row['Field'] == $column_name) { return $row['Type']; } } } } //sqlite column exists private function db_sqlite_column_exists($table_info, $column_name) { foreach ($table_info as $key => $row) { if ($row['name'] == $column_name) { return true; } } return $false; } //database column exists private function db_column_exists ($db_type, $db_name, $table_name, $column_name) { if ($db_type == "sqlite") { $table_info = $this->db_table_info($db_name, $db_type, $table_name); if ($this->db_sqlite_column_exists($table_info, $column_name)) { return true; } else { return false; } } if ($db_type == "pgsql") { $sql = "SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = '$table_name' limit 1) AND attname = '$column_name'; "; } if ($db_type == "mysql") { //$sql .= "SELECT * FROM information_schema.COLUMNS where TABLE_SCHEMA = '$db_name' and TABLE_NAME = '$table_name' and COLUMN_NAME = '$column_name' "; $sql = "show columns from $table_name where field = '$column_name' "; } if ($sql) { $prep_statement = $this->database->db->prepare(check_sql($sql)); $prep_statement->execute(); $result = $prep_statement->fetchAll(PDO::FETCH_NAMED); if (!empty($result)) { return true; } else { return false; } unset ($prep_statement); } } //database column data type private function db_column_data_type ($db_type, $db_name, $table_name, $column_name) { $table_info = $this->db_table_info($db_name, $db_type, $table_name); return $this->db_data_type($db_type, $table_info, $column_name); } //database create table public function db_create_table ($apps, $db_type, $table) { if (empty($apps)) { return false; } if (is_array($apps)) foreach ($apps as $x => $app) { if (!empty($app['db']) && is_array($app['db'])) foreach ($app['db'] as $y => $row) { if (!empty($row['table']['name']) && is_array($row['table']['name'])) { $table_name = $row['table']['name']['text']; } else { $table_name = $row['table']['name']; } if ($table_name == $table) { $sql = "CREATE TABLE " . $table_name . " (\n"; (int)$field_count = 0; if (!empty($row['fields']) && is_array($row['fields'])) foreach ($row['fields'] as $field) { if (!empty($field['deprecated']) && $field['deprecated'] == "true") { //skip this row } else { if ($field_count > 0 ) { $sql .= ",\n"; } if (!empty($field['name']) &&is_array($field['name'])) { $sql .= $field['name']['text'] . " "; } else { $sql .= $field['name'] . " "; } if (!empty($field['type']) &&is_array($field['type'])) { $sql .= $field['type'][$db_type]; } else { $sql .= $field['type']; } if (!empty($field['key']['type']) && $field['key']['type'] == "primary") { $sql .= " PRIMARY KEY"; } $field_count++; } } $sql .= ");\n"; return $sql; } } } } //database insert private function db_insert_into ($apps, $db_type, $table) { global $db_name; foreach ($apps as $x => $app) { foreach ($app['db'] as $y => $row) { if ($row['table']['name'] == $table) { $sql = "INSERT INTO " . $row['table']['name'] . " ("; $field_count = 0; foreach ($row['fields'] as $field) { if (!empty($field['deprecated']) && $field['deprecated'] == "true") { //skip this field } else { if ($field_count > 0 ) { $sql .= ","; } if (is_array($field['name'])) { $sql .= $field['name']['text']; } else { $sql .= $field['name']; } $field_count++; } } $sql .= ")\n"; $sql .= "SELECT "; $field_count = 0; foreach ($row['fields'] as $field) { if (!empty($field['deprecated']) && $field['deprecated'] == "true") { //skip this field } else { if ($field_count > 0 ) { $sql .= ","; } if (is_array($field['name'])) { if ($field['exists'] == "false") { if (is_array($field['name']['deprecated'])) { $found = false; foreach ($field['name']['deprecated'] as $row) { if ($this->db_column_exists ($db_type, $db_name, 'tmp_'.$table, $row)) { $sql .= $row; $found = true; break; } } if (!$found) { $sql .= "''"; } } else { if ($this->db_column_exists ($db_type, $db_name, 'tmp_'.$table, $field['name']['deprecated'])) { $sql .= $field['name']['deprecated']; } else { $sql .= "''"; } } } else { $sql .= $field['name']['text']; } } else { $sql .= $field['name']; } $field_count++; } } $sql .= " FROM tmp_".$table.";\n"; return $sql; } } } } //datatase schema public function schema ($format = '') { //set the global variable global $text, $output_format; if ($format == '') $format = $output_format; //get the db variables //require_once "resources/classes/config.php"; //$config = new config; //$config_exists = $config->exists(); //$config_path = $config->find(); //$config->get(); //$db_type = $config->db_type; //$db_name = $config->db_name; //$db_username = $config->db_username; //$db_password = $config->db_password; //$db_host = $config->db_host; //$db_path = $config->db_path; //$db_port = $config->db_port; //includes files require dirname(__DIR__, 2) . "/resources/require.php"; //add multi-lingual support if (!isset($text)) { $language = new text; $text = $language->get(null,'core/upgrade'); } //PHP PDO check if table or column exists //check if table exists // SELECT * FROM sqlite_master WHERE type='table' AND name='v_cdr' //check if column exists // SELECT * FROM sqlite_master WHERE type='table' AND name='v_cdr' AND sql LIKE '%caller_id_name TEXT,%' //aditional information // http://www.sqlite.org/faq.html#q9 //postgresql //list all tables in the database // SELECT table_name FROM pg_tables WHERE schemaname='public'; //check if table exists // SELECT * FROM pg_tables WHERE schemaname='public' AND table_name = 'v_groups' //check if column exists // SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'v_cdr') AND attname = 'caller_id_name'; //mysql //list all tables in the database // SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = 'fusionpbx' //check if table exists // SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = 'fusionpbx' AND TABLE_NAME = 'v_groups' //check if column exists // SELECT * FROM information_schema.COLUMNS where TABLE_SCHEMA = 'fusionpbx' AND TABLE_NAME = 'v_cdr' AND COLUMN_NAME = 'context' //oracle //check if table exists // SELECT TABLE_NAME FROM ALL_TABLES //update the app db array add exists true or false $sql = ''; foreach ($this->apps as $x => $app) { if (isset($app['db'])) foreach ($app['db'] as $y => $row) { if (isset($row['table']['name'])) { if (is_array($row['table']['name'])) { $table_name = $row['table']['name']['text']; } else { $table_name = $row['table']['name']; } } else { //old array syntax if (is_array($row['table'])) { $table_name = $row['table']['text']; } else { $table_name = $row['table']; } } if (!empty($table_name)) { //check if the table exists if ($this->db_table_exists($db_type, $db_name, $table_name)) { $this->apps[$x]['db'][$y]['exists'] = 'true'; } else { $this->apps[$x]['db'][$y]['exists'] = 'false'; } //check if the column exists foreach ($row['fields'] as $z => $field) { if (!empty($field['deprecated']) && $field['deprecated'] == "true") { //skip this field } else { if (is_array($field['name'])) { $field_name = $field['name']['text']; } else { $field_name = $field['name']; } if (!empty($field_name)) { if ($this->db_column_exists ($db_type, $db_name, $table_name, $field_name)) { //found $this->apps[$x]['db'][$y]['fields'][$z]['exists'] = 'true'; } else { //not found $this->apps[$x]['db'][$y]['fields'][$z]['exists'] = 'false'; } } unset($field_name); } } unset($table_name); } } } //prepare the variables $sql_update = ''; //add missing tables and fields foreach ($this->apps as $x => $app) { if (isset($app['db'])) foreach ($app['db'] as $y => $row) { if (is_array($row['table']['name'])) { $table_name = $row['table']['name']['text']; if ($this->db_table_exists($db_type, $db_name, $row['table']['name']['deprecated'])) { $row['exists'] = "false"; //testing if ($db_type == "pgsql") { $sql_update .= "ALTER TABLE ".$row['table']['name']['deprecated']." RENAME TO ".$row['table']['name']['text'].";\n"; } if ($db_type == "mysql") { $sql_update .= "RENAME TABLE ".$row['table']['name']['deprecated']." TO ".$row['table']['name']['text'].";\n"; } if ($db_type == "sqlite") { $sql_update .= "ALTER TABLE ".$row['table']['name']['deprecated']." RENAME TO ".$row['table']['name']['text'].";\n"; } } else { if ($this->db_table_exists($db_type, $db_name, $row['table']['name']['text'])) { $row['exists'] = "true"; } else { $row['exists'] = "false"; $sql_update .= $this->db_create_table($this->apps, $db_type, $row['table']['name']['text']); } } } else { if ($this->db_table_exists($db_type, $db_name, $row['table']['name'])) { $row['exists'] = "true"; } else { $row['exists'] = "false"; } $table_name = $row['table']['name']; } //check if the table exists if ($row['exists'] == "true") { if (count($row['fields']) > 0) { foreach ($row['fields'] as $z => $field) { if (!empty($field['deprecated']) && $field['deprecated'] == "true") { //skip this field } else { //get the data type if (is_array($field['type'])) { $field_type = $field['type'][$db_type]; } else { $field_type = $field['type']; } //get the field name if (is_array($field['name'])) { $field_name = $field['name']['text']; } else { $field_name = $field['name']; } //check if the field exists // if ($this->db_column_exists($db_type, $db_name, $table_name, $field_name)) { // $field['exists'] = "true"; // } // else { // $field['exists'] = "false"; // } //add or rename fields if (isset($field['name']['deprecated']) && $this->db_column_exists ($db_type, $db_name, $table_name, $field['name']['deprecated'])) { if ($db_type == "pgsql") { $sql_update .= "ALTER TABLE ".$table_name." RENAME COLUMN ".$field['name']['deprecated']." to ".$field['name']['text'].";\n"; } if ($db_type == "mysql") { $field_type = str_replace("AUTO_INCREMENT PRIMARY KEY", "", $field_type); $sql_update .= "ALTER TABLE ".$table_name." CHANGE ".$field['name']['deprecated']." ".$field['name']['text']." ".$field_type.";\n"; } if ($db_type == "sqlite") { //a change has been made to the field name $this->apps[$x]['db'][$y]['rebuild'] = 'true'; } } else { //find missing fields and add them if ($field['exists'] == "false") { $sql_update .= "ALTER TABLE ".$table_name." ADD ".$field_name." ".$field_type.";\n"; } } //change the data type if it has been changed //if the data type in the app db array is different than the type in the database then change the data type if ($this->data_types) { $db_field_type = $this->db_column_data_type ($db_type, $db_name, $table_name, $field_name); $field_type_array = explode("(", $field_type); $field_type = $field_type_array[0]; if (trim($db_field_type) != trim($field_type) && !empty($db_field_type)) { if ($db_type == "pgsql") { if (strtolower($field_type) == "uuid") { $sql_update .= "ALTER TABLE ".$table_name." ALTER COLUMN ".$field_name." TYPE uuid USING\n"; $sql_update .= "CAST(regexp_replace(".$field_name.", '([A-Z0-9]{4})([A-Z0-9]{12})', E'\\1-\\2')\n"; $sql_update .= "AS uuid);\n"; } else { //field type has not changed if ($db_field_type == "integer" && strtolower($field_type) == "serial") { } else if ($db_field_type == "timestamp without time zone" && strtolower($field_type) == "timestamp") { } else if ($db_field_type == "timestamp without time zone" && strtolower($field_type) == "datetime") { } else if ($db_field_type == "timestamp with time zone" && strtolower($field_type) == "timestamptz") { } else if ($db_field_type == "integer" && strtolower($field_type) == "numeric") { } else if ($db_field_type == "character" && strtolower($field_type) == "char") { } //field type has changed else { switch ($field_type) { case 'numeric': $using = $field_name."::numeric"; break; case 'timestamp': case 'datetime': $using = $field_name."::timestamp without time zone"; break; case 'timestamptz': $using = $field_name."::timestamp with time zone"; break; case 'boolean': $using = $field_name."::boolean"; break; default: unset($using); } $sql_update .= "ALTER TABLE ".$table_name." ALTER COLUMN ".$field_name." TYPE ".$field_type." ".($using ? "USING ".$using : null).";\n"; } } } if ($db_type == "mysql") { $type = explode("(", $db_field_type); if ($type[0] == $field_type) { //do nothing } else if ($field_type == "numeric" && $type[0] == "decimal") { //do nothing } else { $sql_update .= "ALTER TABLE ".$table_name." modify ".$field_name." ".$field_type.";\n"; } unset($type); } if ($db_type == "sqlite") { //a change has been made to the field type $this->apps[$x]['db'][$y]['rebuild'] = 'true'; } } } } } } } elseif (!is_array($row['table']['name'])) { //create table $sql_update .= $this->db_create_table($this->apps, $db_type, $row['table']['name']); } } } //rebuild and populate the table foreach ($this->apps as $x => $app) { if (isset($app['db'])) foreach ($app['db'] as $y => $row) { if (is_array($row['table']['name'])) { $table_name = $row['table']['name']['text']; } else { $table_name = $row['table']['name']; } if (!empty($field['rebuild']) && $row['rebuild'] == "true") { if ($db_type == "sqlite") { //start the transaction //$sql_update .= "BEGIN TRANSACTION;\n"; //rename the table $sql_update .= "ALTER TABLE ".$table_name." RENAME TO tmp_".$table_name.";\n"; //create the table $sql_update .= $this->db_create_table($this->apps, $db_type, $table_name); //insert the data into the new table $sql_update .= $this->db_insert_into($this->apps, $db_type, $table_name); //drop the old table $sql_update .= "DROP TABLE tmp_".$table_name.";\n"; //commit the transaction //$sql_update .= "COMMIT;\n"; } } } } // initialize response variable $response = ''; //display results as html if ($format == "html") { //show the database type $response .= "".$text['header-database_type'].": ".$db_type. "

"; //start the table $response .= "\n"; //show the changes if (!empty($sql_update)) { $response .= "\n"; $response .= "\n"; $response .= "\n"; } //list all tables $response .= "\n"; $response .= "\n"; $response .= "\n"; $response .= "\n"; $response .= "\n"; //build the html while looping through the app db array $sql = ''; foreach ($this->apps as $app) { if (isset($app['db'])) foreach ($app['db'] as $row) { if (is_array($row['table']['name'])) { $table_name = $row['table']['name']['text']; } else { $table_name = $row['table']['name']; } $response .= "\n"; //check if the table exists if ($row['exists'] == "true") { $response .= "\n"; $response .= "\n"; if (count($row['fields']) > 0) { $response .= "\n"; } } else { $response .= "\n"; $response .= "\n"; $response .= "\n"; } $response .= "\n"; } } //end the list of tables $response .= "
\n"; $response .= "
\n"; $response .= "".$text['label-sql_changes'].":
\n"; $response .= "
\n";
								$response .= $sql_update;
								$response .= "
\n"; $response .= "
\n"; $response .= "
".$text['label-table']."".$text['label-exists']."".$text['label-details']."
".$table_name."".$text['option-true']."\n"; //show the list of columns $response .= "\n"; $response .= "\n"; $response .= "\n"; $response .= "\n"; $response .= "\n"; $response .= "\n"; foreach ($row['fields'] as $field) { if (!empty($field['deprecated']) && $field['deprecated'] == "true") { //skip this field } else { if (is_array($field['name'])) { $field_name = $field['name']['text']; } else { $field_name = $field['name']; } if (is_array($field['type'])) { $field_type = $field['type'][$db_type]; } else { $field_type = $field['type']; } $response .= "\n"; $response .= "\n"; $response .= "\n"; if ($field['exists'] == "true") { $response .= "\n"; $response .= "\n"; } else { $response .= "\n"; $response .= "\n"; } $response .= "\n"; } } $response .= "
".$text['label-name']."".$text['label-type']."".$text['label-exists']."
".$field_name."".$field_type."".$text['option-true']." ".$text['option-false']." 
\n"; $response .= "
$table_name".$text['label-exists']."
".$text['option-false']."
 
\n"; $response .= "
\n"; } //loop line by line through all the lines of sql code $x = 0; if (empty($sql_update) && $format == "text") { $response .= " ".$text['label-schema'].": ".$text['label-no_change']."\n"; } else { if ($format == "text") { $response .= " ".$text['label-schema']."\n"; } //$this->db->beginTransaction(); $update_array = explode(";", $sql_update); foreach($update_array as $sql) { if (strlen(trim($sql))) { try { $this->database->db->query(trim($sql)); if ($format == "text") { $response .= " $sql;\n"; } } catch (PDOException $error) { $response .= " error: " . $error->getMessage() . " sql: $sql\n"; } } } //$this->db->commit(); $response .= "\n"; unset ($sql_update, $sql); } //refresh each postgresql subscription with its publication if ($db_type == "pgsql") { //get the list of postgresql subscriptions $sql = "select subname from pg_subscription; "; $pg_subscriptions = $this->database->select($sql, null, 'all'); unset($sql, $parameters); //refresh each subscription publication foreach ($pg_subscriptions as $row) { $sql = "ALTER SUBSCRIPTION ".$row['subname']." REFRESH PUBLICATION;"; $response .= $sql; $this->database->execute($sql); } } //handle response //if ($output == "echo") { // echo $response; //} //else if ($output == "return") { return $response; //} } //end function } } //example use //require_once "resources/classes/schema.php"; //$obj = new schema; //$obj->db_type = $db_type; //$obj->schema(); //$result_array = $schema->obj['sql']; //print_r($result_array); ?>