2016-05-11 00:26:44 +00:00
|
|
|
#!/usr/bin/env php
|
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Sabre\VObject;
|
|
|
|
|
|
|
|
// This sucks.. we have to try to find the composer autoloader. But chances
|
|
|
|
// are, we can't find it this way. So we'll do our bestest
|
|
|
|
$paths = [
|
|
|
|
__DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly
|
|
|
|
__DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency.
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($paths as $path) {
|
|
|
|
if (file_exists($path)) {
|
|
|
|
include $path;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!class_exists('Sabre\\VObject\\Version')) {
|
|
|
|
fwrite(STDERR, "Composer autoloader could not be loaded.\n");
|
|
|
|
die(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
echo "sabre/vobject ", Version::VERSION, " duplicate contact merge tool\n";
|
|
|
|
|
|
|
|
if ($argc < 3) {
|
|
|
|
|
|
|
|
echo "\n";
|
|
|
|
echo "Usage: ", $argv[0], " input.vcf output.vcf [debug.log]\n";
|
|
|
|
die(1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
$input = fopen($argv[1], 'r');
|
|
|
|
$output = fopen($argv[2], 'w');
|
|
|
|
$debug = isset($argv[3]) ? fopen($argv[3], 'w') : null;
|
|
|
|
|
|
|
|
$splitter = new Splitter\VCard($input);
|
|
|
|
|
|
|
|
// The following properties are ignored. If they appear in some vcards
|
|
|
|
// but not in others, we don't consider them for the sake of finding
|
|
|
|
// differences.
|
|
|
|
$ignoredProperties = [
|
|
|
|
"PRODID",
|
|
|
|
"VERSION",
|
|
|
|
"REV",
|
|
|
|
"UID",
|
|
|
|
"X-ABLABEL",
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$collectedNames = [];
|
|
|
|
|
|
|
|
$stats = [
|
|
|
|
"Total vcards" => 0,
|
|
|
|
"No FN property" => 0,
|
|
|
|
"Ignored duplicates" => 0,
|
|
|
|
"Merged values" => 0,
|
|
|
|
"Error" => 0,
|
|
|
|
"Unique cards" => 0,
|
|
|
|
"Total written" => 0,
|
|
|
|
];
|
|
|
|
|
|
|
|
function writeStats() {
|
|
|
|
|
|
|
|
global $stats;
|
|
|
|
foreach ($stats as $name => $value) {
|
|
|
|
echo str_pad($name, 23, " ", STR_PAD_RIGHT), str_pad($value, 6, " ", STR_PAD_LEFT), "\n";
|
|
|
|
}
|
|
|
|
// Moving cursor back a few lines.
|
|
|
|
echo "\033[" . count($stats) . "A";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function write($vcard) {
|
|
|
|
|
|
|
|
global $stats, $output;
|
|
|
|
|
|
|
|
$stats["Total written"]++;
|
|
|
|
fwrite($output, $vcard->serialize() . "\n");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
while ($vcard = $splitter->getNext()) {
|
|
|
|
|
|
|
|
$stats["Total vcards"]++;
|
|
|
|
writeStats();
|
|
|
|
|
|
|
|
$fn = isset($vcard->FN) ? (string)$vcard->FN : null;
|
|
|
|
|
|
|
|
if (empty($fn)) {
|
|
|
|
|
|
|
|
// Immediately write this vcard, we don't compare it.
|
|
|
|
$stats["No FN property"]++;
|
|
|
|
$stats['Unique cards']++;
|
|
|
|
write($vcard);
|
|
|
|
$vcard->destroy();
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($collectedNames[$fn])) {
|
|
|
|
|
|
|
|
$collectedNames[$fn] = $vcard;
|
|
|
|
$stats['Unique cards']++;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Starting comparison for all properties. We only check if properties
|
|
|
|
// in the current vcard exactly appear in the earlier vcard as well.
|
|
|
|
foreach ($vcard->children() as $newProp) {
|
|
|
|
|
|
|
|
if (in_array($newProp->name, $ignoredProperties)) {
|
|
|
|
// We don't care about properties such as UID and REV.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$ok = false;
|
|
|
|
foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) {
|
|
|
|
|
|
|
|
if ($compareProp->serialize() === $newProp->serialize()) {
|
|
|
|
$ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$ok) {
|
|
|
|
|
|
|
|
if ($newProp->name === 'EMAIL' || $newProp->name === 'TEL') {
|
|
|
|
|
|
|
|
// We're going to make another attempt to find this
|
|
|
|
// property, this time just by value. If we find it, we
|
|
|
|
// consider it a success.
|
|
|
|
foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) {
|
|
|
|
|
|
|
|
if ($compareProp->getValue() === $newProp->getValue()) {
|
|
|
|
$ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$ok) {
|
|
|
|
|
|
|
|
// Merging the new value in the old vcard.
|
|
|
|
$collectedNames[$fn]->add(clone $newProp);
|
|
|
|
$ok = true;
|
|
|
|
$stats['Merged values']++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$ok) {
|
|
|
|
|
|
|
|
// echo $newProp->serialize() . " does not appear in earlier vcard!\n";
|
|
|
|
$stats['Error']++;
|
2016-10-17 21:26:48 +00:00
|
|
|
if ($debug) fwrite($debug, "Missing '" . $newProp->name . "' property in duplicate. Earlier vcard:\n" . $collectedNames[$fn]->serialize() . "\n\nLater:\n" . $vcard->serialize() . "\n\n");
|
2016-05-11 00:26:44 +00:00
|
|
|
|
|
|
|
$vcard->destroy();
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
$vcard->destroy();
|
|
|
|
$stats['Ignored duplicates']++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($collectedNames as $vcard) {
|
|
|
|
|
|
|
|
// Overwriting any old PRODID
|
|
|
|
$vcard->PRODID = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN';
|
|
|
|
write($vcard);
|
|
|
|
writeStats();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
echo str_repeat("\n", count($stats)), "\nDone.\n";
|