<?php
/** iPhone SMS Database Merger script by simismail@gmail.com
* Version 0.2 Last Update: 06.08.2008
* CSS Design from http://www.maxdesign.com.au/presentation/page_layouts/
* Needs PHP 5.2.6 with php_pdo_sqlite, php_pdo activated and php_sqlite
*/

//smart print_r function
function print_r_html($myvar){
    echo 
'<pre>'.str_replace(array("\n" " "), array('<br>''&nbsp;'), print_r($myvartrue)).'</pre>';
}

//open a sqlite database
function openSQLiteDatabase($file){    
    try{
    
$dbHandle = new PDO("sqlite:$file");    
    }catch( 
PDOException $exception ){    
    die(
$exception->getMessage());
    }
    return 
$dbHandle;    
}

//query funtion with some debugging output
function query($query,$description ""){
    global 
$dbHandle;
    global 
$debug;
    global 
$counter;$counter++;
    if(
$debug) echo "--------------------------------<br />";
    if(
$debug) echo $counter." - ".$description."<br />";
    if(
$debug) echo "Query: $query<br />";    
    
$affectedrows $dbHandle->exec($query) or $error $dbHandle->errorInfo();    
    if (
$error[0]!=0){
        echo 
"Error occured while executing: $query<br />";
        
print_r_html($dbHandle->errorInfo());
        
$dbHandle null;        
        die(
"Database Query Error");
    }
    if(
$debug) echo "Query affected $affectedrows rows.<br />";    
    
flush();
}

//Starts session
session_start();    

//Set Debug environment
if ($_POST['debug']){
    
$debug true;
    } else {
    
$debug false;
}
//Set Query Counter
$counter 0;

//Send sms.db from user if requested
if ($_GET['getFile']==1){
    
$file $_SESSION['filename'];     
    if (!
file_exists($file)){
        echo 
"File does not exist, all files are deleted after requesting the resulting sms.db<br />";
        echo 
"<a href='/'>Go back</a>";
        exit();
        }
    
header("Content-type: application/octet-stream");
    
header("Content-Disposition: attachment; filename=\"sms.db\"");
    
header("Content-Length: "filesize($file));
    
readfile($file);
    
unlink($file);
    
unlink($_SESSION['filename2']);
    exit;
}
//Send myself in formated code if requested
if($_GET['source']==1) {
    
highlight_file(__FILE__);
    exit();
}

//Filenames from upload script
$filefield_1 "sms_db";  
$filefield_2 "smsold_db";



//First sms.db file uploaded
if( isset( $_FILES[$filefield_1] ) ) {
    
$upload_dir realpath(dirname($_FILES[$filefield_1]['tmp_name']));
    
$filename_1 $upload_dir "/PHPUpload-" basename$_FILES[$filefield_1]['tmp_name']);         
    
//Move uploaded file
    
if( ! move_uploaded_file$_FILES[$filefield_1]['tmp_name'], $filename_1 ) ) {           
        die( 
"Wrong file uploaded!" );
  } 
  if(
$debug) echo "Uploaded files: <br />";
  if(
$debug) echo $_FILES[$filefield_1]['name'] . ': ' filesize($filename_1) . ' bytes<br />';
  
  
  
//Second sms.db file uoloaded
  
if( isset( $_FILES[$filefield_2] ) ) {
    
$filename_2 $upload_dir "/PHPUpload-" basename$_FILES[$filefield_2]['tmp_name']);
    
//Move uploaded file
    
if( ! move_uploaded_file$_FILES[$filefield_2]['tmp_name'], $filename_2 ) ) {
        die( 
"Wrong file uploaded!" );
    }   
    if(
$debug) echo $_FILES[$filefield_2]['name'] . ': ' filesize($filename_2) . ' bytes<br />';
    }


echo 
"<br />Started merging the databases....<br />";

// create a SQLite3 database file with PDO and return a database handle (Object Oriented)
$dbHandle openSQLiteDatabase($filename_1);


$sqlAttach "attach database '$filename_2' as smsold";
query($sqlAttach,"Attach DB...");


$sqlCreateTable 'CREATE TABLE message_tmp 
(
    ROWID INTEGER PRIMARY KEY AUTOINCREMENT,
    address TEXT,
    date INTEGER,
    text TEXT,
    flags INTEGER,
    replace INTEGER,
    svc_center TEXT,
    group_id INTEGER,
    association_id INTEGER,
    height INTEGER,
    UIFlags INTEGER,
    version INTEGER
)'
;
query($sqlCreateTable,"Create table message_tmp...");

//insert1
$sqlInsert1 'insert into message_tmp select null,address,date,text,flags,replace,svc_center,group_id,association_id,height,UIFlags,version from smsold.message
order by date asc'
;
query($sqlInsert1,"insert messages from old database") ;


//insert2
$sqlInsert2 'INSERT INTO message_tmp select null,address,date,text,flags,replace,svc_center,group_id,association_id,height,UIFlags,version from message
order by date asc'
;
query($sqlInsert2,"insert messages from new database");

//create table2
$sqlCreateTable2 'CREATE TABLE message_tmp_2 
(
    ROWID INTEGER PRIMARY KEY AUTOINCREMENT,
    address TEXT,
    date INTEGER,
    text TEXT,
    flags INTEGER,
    replace INTEGER,
    svc_center TEXT,
    group_id INTEGER,
    association_id INTEGER,
    height INTEGER,
    UIFlags INTEGER,
    version INTEGER
)'
;
query($sqlCreateTable2,"create temporary message table 2");

//insert3
$sqlInsert3 'INSERT INTO message_tmp_2 select null,address,date,text,flags,replace,svc_center,group_id,association_id,height,UIFlags,version from message_tmp
order by date asc'
;
query($sqlInsert3,"insert all messages into the new table ordered by date");

//replace (swiss numbers)
//$sqlreplace = 'update message_tmp_2 set address = replace(address,\'0041\',\'+41\')';
//query($sqlreplace,"replace the country code from swiss phone numbers");

//delete bad sms
$sqldelete 'delete from message_tmp_2 where flags = 129';
query($sqldelete,"delete bad sms");

$countout $dbHandle->query("select count(*) as c from message_tmp_2 where flags = 3");
$counto $countout->fetch(); // store result in array
$countout->closeCursor();
$countin $dbHandle->query("select count(*) as c from message_tmp_2 where flags = 2");
$counti $countin->fetch(); // store result in array
$countin->closeCursor();
//replace
$sqlu1 "update _SqliteDatabaseProperties  set value =  $counto[c] where key = 'counter_out_all' ";
query($sqlu1,"update counter_out_all");
$sqlu2 "update _SqliteDatabaseProperties  set value =  $counto[c] where key = 'counter_out_lifetime' ";
query($sqlu2,"update counter_out_lifetime");
$sqlu3 "update _SqliteDatabaseProperties  set value =  $counti[c] where key = 'counter_in_all' ";
query($sqlu3,"update counter_in_all");
$sqlu4 "update _SqliteDatabaseProperties  set value =  $counti[c] where key = 'counter_in_lifetime'";
query($sqlu4,"update counter_in_lifetime");


$sqlCreateTable3 'CREATE TABLE message_tmp_3 
(
    ROWID INTEGER PRIMARY KEY,
    address TEXT,
    date INTEGER
)'
;
query($sqlCreateTable3,"create temporary message table 3");


//insert4
$sqlInsert44 'INSERT INTO message_tmp_3 select ROWID,substr(address,length(address)-8,9),date from message_tmp_2
order by date asc'
;
query($sqlInsert44,"insert only message id and last 9 numbers of address into temporary message table");



//create table3
$sqlCreateTable3 'CREATE TABLE msg_group_tmp 
(
    ROWID INTEGER PRIMARY KEY AUTOINCREMENT,
    type INTEGER,
    newest_message INTEGER,
    unread_count INTEGER
)'
;
query($sqlCreateTable3,"create msg group table");


//insert4
$sqlInsert4 'insert into msg_group_tmp(newest_message) select max(ROWID) from message_tmp_3
group by address order by date asc'
;
query($sqlInsert4,"insert addresses into msg group table");

//update1
$sqlUpdate1 'update msg_group_tmp set type = 0, unread_count = 0';
query($sqlUpdate1,"update msg group table with default values");

//create table4
$sqlCreateTable4 '
CREATE TABLE group_member_tmp 
(
    ROWID INTEGER PRIMARY KEY AUTOINCREMENT,
    group_id INTEGER,
    address TEXT
)'
;
query($sqlCreateTable4,"create group member table");


//insert5
//$sqlInsert5 = "insert into group_member_tmp(group_id,address) select GRPID,address from (select msg_group_tmp.ROWID as 'GRPID',message_tmp_3.address as 'short_address',message_tmp_3.ROWID as 'msg_id' 
//from  msg_group_tmp 
//left join message_tmp_3 on (msg_group_tmp.newest_message=message_tmp_3.ROWID))
//left join message_tmp_2 on ( message_tmp_2.address like '%' || short_address)
//group by message_tmp_2.address
//order by grpid";
$sqlInsert5 "insert into group_member_tmp(group_id,address) select msg_group_tmp.ROWID,message_tmp_2.address  from  msg_group_tmp 
left join message_tmp_2 on (msg_group_tmp.newest_message=message_tmp_2.ROWID)"
;
query($sqlInsert5,"insert addresses into group member table");


//Proccen    
$result $dbHandle->query("select group_id,substr(address,length(address)-8,9) as 'address' from group_member_tmp order by group_id asc");
$pageView $result->fetchAll(); // store result in array
foreach ($pageView as $line){    
    
query("update message_tmp_2 set group_id = '$line[group_id]' where address like '%$line[address]'","updating groupid in all messages");
}



if(
$debug) echo "cleaning up.....<br />";
query("drop table message");
query("drop table message_tmp");
query("drop table msg_group");
query("drop table group_member");
query("drop table message_tmp_3");
query("alter table message_tmp_2 rename to message");
query("alter table msg_group_tmp rename to msg_group");
query("alter table group_member_tmp rename to group_member");

query("CREATE INDEX message_group_index ON message(group_id, ROWID);");
query("CREATE INDEX message_flags_index ON message(flags);");
query("CREATE TRIGGER insert_unread_message AFTER INSERT ON message WHEN NOT read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) + 1 WHERE ROWID = new.group_id; END;");
query("CREATE TRIGGER mark_message_unread AFTER UPDATE ON message WHEN read(old.flags) AND NOT read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) + 1 WHERE ROWID = new.group_id; END;");
query("CREATE TRIGGER mark_message_read AFTER UPDATE ON message WHEN NOT read(old.flags) AND read(new.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = new.group_id) - 1 WHERE ROWID = new.group_id; END;");
query("CREATE TRIGGER delete_message AFTER DELETE ON message WHEN NOT read(old.flags) BEGIN UPDATE msg_group SET unread_count = (SELECT unread_count FROM msg_group WHERE ROWID = old.group_id) - 1 WHERE ROWID = old.group_id; END;");
query("CREATE TRIGGER insert_newest_message AFTER INSERT ON message WHEN new.ROWID >= IFNULL((SELECT MAX(ROWID) FROM message WHERE message.group_id = new.group_id), 0) BEGIN UPDATE msg_group SET newest_message = new.ROWID WHERE ROWID = new.group_id; END;");
query("CREATE TRIGGER delete_newest_message AFTER DELETE ON message WHEN old.ROWID = (SELECT newest_message FROM msg_group WHERE ROWID = old.group_id) BEGIN UPDATE msg_group SET newest_message = (SELECT ROWID FROM message WHERE group_id = old.group_id AND ROWID = (SELECT max(ROWID) FROM message WHERE group_id = old.group_id)) WHERE ROWID = old.group_id; END;");

query("detach database smsold");
$dbHandle null;
$_SESSION['filename'] = $filename_1;
$_SESSION['filename2'] = $filename_2;
if(
$debug) echo "--------------------------------<br />";
echo 
"Merging completed! Get your new sms.db <a href=\"?getFile=1\">here</a>";
    

} else {

?>



<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
    <meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
    <title>iPhone SMS Database Merger</title>
    <style type="text/css" media="screen">
    <!--
    body
    {
        padding: 0;
        margin: 0;
        background-color: #666;
        color: #000;
        font-family: Arial, Helvetica, sans-serif;
    }
    
    #contents    
    {
        margin-top: 10px;
        margin-bottom: 10px;
        margin-left: 10px;
        margin-right: 30%;
        padding: 10px;
        background-color: #FFF;
        color: #000;
    }
    
    h1    
    {
        color: #333;
        background-color: transparent;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 20px;
    }
    
    p    
    {
        color: #333;
        background-color: transparent;
        font-family: Arial, Helvetica, sans-serif;
        
    }
    
    .code
    {
        color: #339;
        background-color: transparent;
        font-family: times, serif;
        font-size: 0.9em;
        padding-left: 40px;
    }
    -->
</style>
</head>
<body>
<div id="contents">
    <h1>
        iPhone SMS Database Merger
    </H1>

    <P>
        This script merges your 2 SMS databases into one. Please note following restrictions:
        <ul>
            <li>Conversations with multiple recipients are moved to the regular conversation per address</li>            
            <li>It does not handle duplicates</li>
            <li>If you are concerned about privacy, please download and run the script yourself. : <a href="?source=1">Show source</a></li>
            <li>You have to enable cookies to download the merged file</li>
            <li>Compatible with iPhone OS up to 2.2.1.</li>
            <li>NOT (yet) compatible with iPhone OS 3.0</li>
            <li>Send bugs, feedback, and questions to simismail@gmail.com</li>            
        </ul>
    </P>
    
    <P>    
        <form ENCTYPE="multipart/form-data" method="POST">First sms.db:<br />
         <input type="file" accept="*.db" name="sms_db"><br /> Second sms.db:<br />
        <input type="file" accept="*.db" name="smsold_db"><br />
         <input type="checkbox" name="debug" value="true">Show debug output<br />
   <input type="submit" value="Merge">
   </form>
    </P>

    
    <P>    
        How-To:
        <ol>
            <li>Jailbreak your iPhone, and enable the SSH Server. See <a href='http://winscp.net/eng/docs/faq_iphone'>http://winscp.net/eng/docs/faq_iphone</a></li>
            <li>Establish an SSH connection to your iPhone and download the sms.db file to your computer (location on phone: /var/mobile/Library/SMS/sms.db)</li>
            <li>Upload the 2 files and and click "Merge". </li>
            <li>When the merge is complete, save the new database to your folder on the iPhone (/var/mobile/Library/SMS) as sms.db (replace the old one)</li>
            <li>Restart your iPhone</li>
            <li>Enjoy! :)</li>
        </ol>
    </P>
    Caution: Don't delete your old sms.db files unless you are sure the merging worked!
</div>
 v0.2, Copyright © Simon Andres (simismail@gmail.com) </body>
</html>
<?php
}
?>