aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/sabre/vobject/bin/mergeduplicates.php
blob: 1662e7bf3b9991635b2d6fd1d4d60d62afbbed0a (plain) (tree)























































































































































































                                                                                                                                                                                                            
#!/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']++;
                if ($debug) fwrite($debug, "Missing '" . $newProp->name .  "' property in duplicate. Earlier vcard:\n" . $collectedNames[$fn]->serialize() . "\n\nLater:\n" . $vcard->serialize() . "\n\n");
                
                $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";