Skip to content

Instantly share code, notes, and snippets.

@oelna
Created June 5, 2016 19:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oelna/f48de497dbe12aa3bccd5b6df8791481 to your computer and use it in GitHub Desktop.
Save oelna/f48de497dbe12aa3bccd5b6df8791481 to your computer and use it in GitHub Desktop.
A small utility that copies mailboxes from one IMAP account to another.
<?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