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