Created
June 5, 2016 19:01
-
-
Save oelna/f48de497dbe12aa3bccd5b6df8791481 to your computer and use it in GitHub Desktop.
A small utility that copies mailboxes from one IMAP account to another.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
This code is based heavily on the instructions given on http://egil.biz/migrate-e-mail-over-imap-with-php/ | |
Many hours on Stackoverflow have been spent trying to make this. | |
I tried to clean it up, make it readable, add a nicer UI and feedback about errors. | |
If you like this, use this or it helps you, I'm happy to hear about it. | |
Drop me a line if you have feedback, too. | |
2016-06-05, Arno Richter <mail@arnorichter.de> | |
*/ | |
DEFINE('NL', PHP_EOL); | |
DEFINE('BR', '<br />'); | |
?><!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>IMAP Transfer</title> | |
<style> | |
* { box-sizing: border-box; } | |
html { | |
font: 100%/1.45 Helvetica, Arial, sans-serif; | |
} | |
#config, | |
#log { | |
width: 98%; | |
margin: 0.5em auto; | |
} | |
#config input[type='text'] { | |
width: 100%; | |
font-size: 1.0em; | |
padding: 0.25em; | |
margin-bottom: 0.5em; | |
} | |
.info { | |
background: #ddd; | |
padding: 0.75em; | |
margin-bottom: 1.5em; | |
} | |
p { margin-top: 0; } | |
#show-details { color: #007aff; text-decoration: underline; cursor: pointer; } | |
.error { color: #b00; } | |
.detail { display: none; } | |
.detail.show { display: block; } | |
.progress { color: #007aff; } | |
@media all and (min-width: 60em) { | |
#config, | |
#log { | |
width: 60%; | |
margin: 2em auto; | |
} | |
} | |
</style> | |
<script> | |
window.onload = function() { | |
var show = document.querySelector('#show-details'); | |
var details = document.querySelectorAll('.detail'); | |
show.addEventListener('click', function(e) { | |
e.preventDefault(); | |
for(var i=0; i<details.length; i++) { | |
details[i].classList.toggle('show'); | |
} | |
}); | |
} | |
</script> | |
</head> | |
<body> | |
<div id="config"> | |
<h1>IMAP Transfer</h1> | |
<form action="" method="post"> | |
<div class="info"> | |
<p>Enter the connection data for each of the IMAP servers as complete connection strings. Here are a few examples:</p> | |
<ul> | |
<li>{localhost:143}</li> | |
<li>{localhost:993/imap/ssl}</li> | |
<li>{localhost:993/imap/ssl/novalidate-cert}</li> | |
<li>{imap.example.org:993/imap/ssl/novalidate-cert}</li> | |
<li>{imap.example.org:993/imap/ssl/novalidate-cert}INBOX</li> | |
</ul> | |
<p>Be sure to test the connection first and do a dry run of the transfer to make sure the settings are correct. Be safe!</p> | |
<p>If you're missing some of the mailboxes after the transfer, be sure you're subscribed to them, aka. they are visible. The emails are there.</p> | |
</div> | |
<input type="text" name="source_server" value="<?php if(isset($_POST['source_server'])) echo($_POST['source_server']) ?>" placeholder="{imap.sourcehost.org:993/imap/ssl/novalidate-cert}" /><br /> | |
<input type="text" name="source_username" value="<?php if(isset($_POST['source_username'])) echo($_POST['source_username']) ?>" placeholder="Source username, usually email address" /><br /> | |
<input type="text" name="source_password" value="<?php if(isset($_POST['source_password'])) echo($_POST['source_password']) ?>" placeholder="Password" /><br /><br /> | |
<input type="text" name="target_server" value="<?php if(isset($_POST['target_server'])) echo($_POST['target_server']) ?>" placeholder="{imap.targethost.org:993/imap/ssl/novalidate-cert}" /><br /> | |
<input type="text" name="target_username" value="<?php if(isset($_POST['target_username'])) echo($_POST['target_username']) ?>" placeholder="Target username, usually email address" /><br /> | |
<input type="text" name="target_password" value="<?php if(isset($_POST['target_password'])) echo($_POST['target_password']) ?>" placeholder="Password" /> | |
<input type="submit" name="submit_test" value="Test connection" /> <input type="submit" name="submit_transfer" value="Transfer" id="transfer" /> | |
<a id="show-details">Show transfer details</a> | |
</form> | |
</div> | |
<div id="log"> | |
<?php | |
/* Write log to the filesystem */ | |
function log_error($message) { | |
$data = '['.date('F j, Y, g:i a')."]\t".$message.NL.NL; | |
file_put_contents('/tmp/imap_migration_log', $data, FILE_APPEND); | |
} | |
/* Keep track on processed folders */ | |
function migrated_folders($folder) { | |
file_put_contents('/tmp/imap_migration_history', $folder.NL, FILE_APPEND); | |
} | |
$target_folder = ''; | |
$migratedfolders = array(); | |
$total_emails = 0; | |
$total_failed = array(); | |
$is_preview = true; | |
if(empty($_POST['source_server']) || empty($_POST['source_username']) || empty($_POST['source_password']) || empty($_POST['target_server']) || empty($_POST['target_username']) || empty($_POST['target_password'])) { | |
if(!empty($_POST['submit_test']) || !empty($_POST['submit_transfer'])) echo('<p class="error">Some data is missing</p>'); | |
} else { | |
if(!empty($_POST['submit_transfer']) && strtolower($_POST['submit_transfer']) == 'transfer') { | |
$is_preview = false; //we're doing it! | |
} else { | |
echo('<h2>Working in preview mode. Nothing is being transferred yet.</h2>'.NL.NL); | |
} | |
/* Get the complete list of all source folders */ | |
$source_imap = imap_open($_POST['source_server'], $_POST['source_username'], $_POST['source_password'], null, 10) | |
or die('can\'t connect: '.imap_last_error()); | |
$folders = imap_list($source_imap, $_POST['source_server'], '*'); | |
if(empty($folders) || sizeof($folders) < 1) { | |
die('<h2 class="error">No folders found on the server</h2>'); | |
} else { | |
echo('<h2>Found '.sizeof($folders).' mailboxes to transfer:</h2>'.NL.'<p>'); | |
foreach($folders as $folder) { | |
$foldername = str_replace($_POST['source_server'], '', $folder); | |
echo($foldername.BR); | |
} | |
echo('</p>'.NL.NL); | |
} | |
imap_close($source_imap); | |
if($folders !== false) { | |
foreach($folders as $value) { | |
/* Remove connection info in the path */ | |
$folderpath = str_replace($_POST['source_server'], '', $value); | |
/* Check if the folder already are completely migrated */ | |
if(is_file('/tmp/imap_migration_history')) { | |
$migratedfolders = explode(NL, file_get_contents('/tmp/imap_migration_history')); | |
} | |
if(!in_array($folderpath, $migratedfolders)) { | |
/* Output info */ | |
echo('<h2>Beginning work on '.$folderpath.'</h2>'.NL); | |
/* Remove the folder name and keep the path */ | |
$targetpath = explode('/', $folderpath); | |
array_pop($targetpath); | |
$targetroot = mb_convert_encoding(implode('/', $targetpath), 'UTF7-IMAP', 'ISO-8859-1'); | |
/* Connect to the target server */ | |
$target_imap = imap_open($_POST['target_server'].$target_folder.$targetroot, $_POST['target_username'], $_POST['target_password']) | |
or die(BR.'can\'t connect to target server: '.BR.imap_last_error()); | |
/* Create folder if it's not there already */ | |
$foldername = mb_convert_encoding($_POST['target_server'].$target_folder.$folderpath, 'UTF7-IMAP', 'ISO-8859-1'); | |
if(!imap_createmailbox($target_imap, $foldername)) { | |
log_error('Error creating the targetfolder '.$folderpath); | |
//folder probably already exists | |
} | |
/* | |
if(!imap_subscribe($target_imap, $foldername)) { | |
log_error('Error subscribing to the targetfolder '.$folderpath); | |
} | |
*/ | |
/* Disconnect */ | |
imap_close($target_imap); | |
/* Connect to the servers */ | |
$source_imap = imap_open($_POST['source_server'].$folderpath, $_POST['source_username'], $_POST['source_password'], null, 10) | |
or die(BR.'can\'t connect to source server: '.BR.imap_last_error()); | |
$target_imap = imap_open(mb_convert_encoding($_POST['target_server'].$target_folder.$folderpath, 'UTF7-IMAP', 'ISO-8859-1'), $_POST['target_username'], $_POST['target_password'], null, 10) | |
or die(BR.'can\'t connect to target server: '.BR.imap_last_error()); | |
/* Get information about the source mailbox */ | |
$MC = imap_check($source_imap); | |
echo('<p>Found '.$MC->Nmsgs.' mails to migrate.</p>'.NL); | |
/* Fetch messages from source mailbox */ | |
$result = imap_fetch_overview($source_imap, "1:{$MC->Nmsgs}", 0); | |
$i = 0; | |
$length = sizeof($result); | |
$failed = array(); | |
foreach($result as $overview) { | |
$message_header = imap_fetchheader($source_imap, $overview->msgno); | |
$message_body = imap_body($source_imap, $overview->msgno); | |
$message = $message_header.$message_body; | |
//output the first and last emails in the folder so people can check the result | |
if($i === 0 || $i === ($length-1)) { | |
$header = imap_header($source_imap, $overview->msgno); | |
if($i > 0) echo('<span class="progress" title="'.$i.'">.</span>'); | |
echo('<p class="detail">'.(($i === 0) ? 'Earliest' : 'Latest').' email is: '); | |
echo(htmlentities($header->fromaddress.': "'.$header->Subject.'" ('.strftime('%F %T', $header->udate).')').'</p>'); | |
if($i === 0) echo('<span class="progress" title="'.$i.'">.</span>'); | |
} else { | |
echo('<span class="progress" title="'.$i.'">.</span>'); | |
} | |
/* Write message to target mailbox, if enabled */ | |
if($is_preview === false) { | |
if(!imap_append($target_imap, mb_convert_encoding($_POST['target_server'].$target_folder.$folderpath, 'UTF7-IMAP', 'ISO-8859-1'), $message,'\\Seen')) { | |
log_error('Coundn\'t migrate the email "'.$overview->subject.'" from '.$overview->date.' in '.$folderpath); | |
$failed[] = array('subject' => $overview->subject, 'date' => strtotime($overview->date), 'folder' => $folderpath); | |
} | |
} | |
echo(NL); | |
$i++; | |
$total_emails++; | |
} | |
/* close connections mailbox */ | |
imap_close($source_imap); | |
imap_close($target_imap); | |
if(!empty($failed)) { | |
echo('<p class="error">Transfer of '.sizeof($failed).' emails failed.</p>'); | |
$total_failed = array_merge($total_failed, $failed); | |
} | |
/* Write to the history that we are finished with the folder */ | |
migrated_folders($folderpath); | |
} | |
//end of folder | |
} | |
/* Delete history file */ | |
unlink('/tmp/imap_migration_history'); | |
echo('<h2>Done! Transferred '.$total_emails.' emails.</h2>'.NL); | |
$failed = sizeof($total_failed); | |
if($failed > 0) { | |
echo('<p class="error">Sadly, '.$failed.' emails could not be transferred. Here is the list:</p>'.NL.'<ul>'); | |
foreach($total_failed as $f) { | |
echo('<li>'.$f['subject'].' ('.strftime('%F %T', $f['date']).') in '.$f['folder'].'</li>'.NL); | |
} | |
echo('</ul>'); | |
} | |
} else { | |
echo('<p class="error">Couldn\'t get source IMAP trees!</p>'); | |
} | |
} | |
?> </div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment