1#!/usr/bin/env php 2<?php 3 4namespace Sabre\VObject; 5 6// This sucks.. we have to try to find the composer autoloader. But chances 7// are, we can't find it this way. So we'll do our bestest 8$paths = [ 9 __DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly 10 __DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency. 11]; 12 13foreach ($paths as $path) { 14 if (file_exists($path)) { 15 include $path; 16 break; 17 } 18} 19 20if (!class_exists('Sabre\\VObject\\Version')) { 21 fwrite(STDERR, "Composer autoloader could not be loaded.\n"); 22 die(1); 23} 24 25echo "sabre/vobject ", Version::VERSION, " duplicate contact merge tool\n"; 26 27if ($argc < 3) { 28 29 echo "\n"; 30 echo "Usage: ", $argv[0], " input.vcf output.vcf [debug.log]\n"; 31 die(1); 32 33} 34 35$input = fopen($argv[1], 'r'); 36$output = fopen($argv[2], 'w'); 37$debug = isset($argv[3]) ? fopen($argv[3], 'w') : null; 38 39$splitter = new Splitter\VCard($input); 40 41// The following properties are ignored. If they appear in some vcards 42// but not in others, we don't consider them for the sake of finding 43// differences. 44$ignoredProperties = [ 45 "PRODID", 46 "VERSION", 47 "REV", 48 "UID", 49 "X-ABLABEL", 50]; 51 52 53$collectedNames = []; 54 55$stats = [ 56 "Total vcards" => 0, 57 "No FN property" => 0, 58 "Ignored duplicates" => 0, 59 "Merged values" => 0, 60 "Error" => 0, 61 "Unique cards" => 0, 62 "Total written" => 0, 63]; 64 65function writeStats() { 66 67 global $stats; 68 foreach ($stats as $name => $value) { 69 echo str_pad($name, 23, " ", STR_PAD_RIGHT), str_pad($value, 6, " ", STR_PAD_LEFT), "\n"; 70 } 71 // Moving cursor back a few lines. 72 echo "\033[" . count($stats) . "A"; 73 74} 75 76function write($vcard) { 77 78 global $stats, $output; 79 80 $stats["Total written"]++; 81 fwrite($output, $vcard->serialize() . "\n"); 82 83} 84 85while ($vcard = $splitter->getNext()) { 86 87 $stats["Total vcards"]++; 88 writeStats(); 89 90 $fn = isset($vcard->FN) ? (string)$vcard->FN : null; 91 92 if (empty($fn)) { 93 94 // Immediately write this vcard, we don't compare it. 95 $stats["No FN property"]++; 96 $stats['Unique cards']++; 97 write($vcard); 98 $vcard->destroy(); 99 continue; 100 101 } 102 103 if (!isset($collectedNames[$fn])) { 104 105 $collectedNames[$fn] = $vcard; 106 $stats['Unique cards']++; 107 continue; 108 109 } else { 110 111 // Starting comparison for all properties. We only check if properties 112 // in the current vcard exactly appear in the earlier vcard as well. 113 foreach ($vcard->children() as $newProp) { 114 115 if (in_array($newProp->name, $ignoredProperties)) { 116 // We don't care about properties such as UID and REV. 117 continue; 118 } 119 $ok = false; 120 foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { 121 122 if ($compareProp->serialize() === $newProp->serialize()) { 123 $ok = true; 124 break; 125 } 126 } 127 128 if (!$ok) { 129 130 if ($newProp->name === 'EMAIL' || $newProp->name === 'TEL') { 131 132 // We're going to make another attempt to find this 133 // property, this time just by value. If we find it, we 134 // consider it a success. 135 foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) { 136 137 if ($compareProp->getValue() === $newProp->getValue()) { 138 $ok = true; 139 break; 140 } 141 } 142 143 if (!$ok) { 144 145 // Merging the new value in the old vcard. 146 $collectedNames[$fn]->add(clone $newProp); 147 $ok = true; 148 $stats['Merged values']++; 149 150 } 151 152 } 153 154 } 155 156 if (!$ok) { 157 158 // echo $newProp->serialize() . " does not appear in earlier vcard!\n"; 159 $stats['Error']++; 160 if ($debug) fwrite($debug, "Missing '" . $newProp->name . "' property in duplicate. Earlier vcard:\n" . $collectedNames[$fn]->serialize() . "\n\nLater:\n" . $vcard->serialize() . "\n\n"); 161 162 $vcard->destroy(); 163 continue 2; 164 } 165 166 } 167 168 } 169 170 $vcard->destroy(); 171 $stats['Ignored duplicates']++; 172 173} 174 175foreach ($collectedNames as $vcard) { 176 177 // Overwriting any old PRODID 178 $vcard->PRODID = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN'; 179 write($vcard); 180 writeStats(); 181 182} 183 184echo str_repeat("\n", count($stats)), "\nDone.\n"; 185