<?php
#
# Copyright 2003 James Seng. See LICENSE for the Software License.
#

$GLOBALS['conf_fabook'] = "abookcache";
$GLOBALS['conf_abookcache'] = 24; // cache for 24 hours

function abook_element_start($parser,$name,$attribs) {
    if (!isset($GLOBALS['abook_load'])) return 0;
    if ($name == "OUTLINE") {
        $GLOBALS['abook_outline']++;
        if ($GLOBALS['abook_outline'] == 1) {
            $GLOBALS['abook_url']  = $attribs['TEXT'];
            $GLOBALS['abook_time'] = $attribs['TIME'];
            $GLOBALS['abook_addr'] = array();
        } else if ($GLOBALS['abook_outline'] == 2) {
            $key = $attribs['TEXT'];
            $value = $attribs['VALUE'];
            $GLOBALS['abook_addr'][$key] = $value;
        }
    }
}

function abook_element_end($parser,$name) {
    if (!isset($GLOBALS['abook_load'])) return 0;
    if ($name == "OUTLINE") {
        $GLOBALS['abook_outline']--;
        if ($GLOBALS['abook_outline'] == 0) {
            $GLOBALS['abook_load']->abook[] = array(
                    "url"  => $GLOBALS['abook_url'],
                    "time" => $GLOBALS['abook_time'],
                    "addr" => $GLOBALS['abook_addr']);
        }
    }
}


function abookauto_element_start($parser,$name,$attribs) {
    if ($name == "LINK") {
        if (strtolower($attribs['REL']) == "pong")
            $GLOBALS['abook_auto'] = $attribs['HREF'];
            return;
    }
}

function abookauto_element_end($parser,$name) {
}

class func_addressbook extends function_base {
    var $changed;
    var $foaf;
    var $abook = array();

    function func_addressbook() {
        global $config,$conf_abookcache;

        $this->changed = 0;

        $this->load($GLOBALS['conf_fabook']);
        $GLOBALS['abook'] =& $this;

        // default configuration
        $ac = $config->get('abookcache');
        if (!isset($ac) || (int) $ac == 0) {
            $config->set('abookcache',$conf_abookcache);
        }

        $purl = $config->get('purl');
        if (!isset($purl) || $purl == "") {
            // Do a best guess
            if ($_SERVER['HTTPS'] == "on") 
                $http = "https://";
            else
                $http = "http://";
            $url = $http.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; 
            $url = substr($url,0,strrpos($url,'/'));
            $url .= "/xmlrpc.php";

            $config->set('purl',$url);
        }
    }

    function print_menu($type) {
        if ($type == 'short') {
            print "<a href=\"?q=list_abook\">Address Book</a> | ";
            return;
        }

        if ($type == 'long') {
            print <<<_LONG_
<tr>
  <td align="right" width="25%" valign="top">
    <a href="?q=list_abook">Show Address Book</a></td>
  <td align="left" width="75%" valign="top">
    This is your address book, derived from your contact lists.
    The information is updated ping your abook PonG server to
    check if there is any update. You can also generate FOAF 
    file from your address book.
    </td>
  </tr>
_LONG_;
        }
    }

    function load($file = "abookcache") {
        $filename = $this->filename($file);

        $GLOBALS['abook_load'] =& $this;

        if (!file_exists($filename))
            return 0;

        if (!($fh = fopen($filename,"r")))
            return $this->error("Unable to open $filename for reading.");

        $xml_parser = xml_parser_create();
        xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING,1);
        xml_set_element_handler($xml_parser, "abook_element_start",
            "abook_element_end");

        while ($data = fread($fh, 4096)) {
            if (!xml_parse($xml_parser,$data,feof($fh))) 
                return $this->error(sprintf(
                    "%s: XML parse error %s at line %d.",
                        $filename,
                        xml_error_string(xml_get_error_code($xml_parser)),
                        xml_get_current_line_number($xml_parser)));
        }

        xml_parser_free($xml_parser);

        unset($GLOBALS['abook_load']);

        return 1;
    }

    function save($file = "abookcache") {
        global $config;

        // re-order the abook before saving
        $this->abook = array_values($this->abook);

        ignore_user_abort(true);
    
        if (!isset($config->conf['datecreated'])) {
            $config->set('datecreated',long_datetime());
        }
    
        $filename = $this->tempfile($file);
    
        if (!($fh = fopen($filename, 'w')))
            return $this->error("Unable to open $filename for writing.");
    
        if (flock($fh,LOCK_EX | LOCK_NB)) {
            fwrite($fh,$this->opml_header("Pong Address Book"));
            foreach ($this->abook as $addr) {
                $url = htmlspecialchars($addr['url']);
                $time = $addr['time'];
                $str = <<<_URL_START_
    <outline text="$url" time="$time">

_URL_START_;

                while (list($k,$v) = each($addr['addr'])) {
                    $k = htmlspecialchars($k);
                    $v = htmlspecialchars($v);
                    $str .= <<<_ADDRINFO_
      <outline text="$k" value="$v" />

_ADDRINFO_;
                }
                $str .= "      </outline>\n";
                fwrite($fh,$str);
            }
            fwrite($fh,$this->opml_footer());
            flock($fh,LOCK_UN);
        }
        fclose($fh);
    
        copy($filename,$this->filename($file));
        unlink($filename);
    
        ignore_user_abort(false);
    
        return 1;
    }

    function autodiscover($url) {
        $GLOBALS['abook_auto'] = "";

        if (!($fh = @fopen($url,"r")))
            return $GLOBALS['abook_auto'];

        $xml_parser = xml_parser_create();
        xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING,1);
        xml_set_element_handler($xml_parser, "abookauto_element_start",
            "abookauto_element_end");

        while ($data = fread($fh, 4096)) {
            if (!xml_parse($xml_parser,$data,feof($fh))) 
                return $this->error(sprintf(
                    "%s: XML parse error %s at line %d.",
                        $filename,
                        xml_error_string(xml_get_error_code($xml_parser)),
                        xml_get_current_line_number($xml_parser)));
        }

        xml_parser_free($xml_parser);

        return $GLOBALS['abook_auto'];
    }

    function update_addr($url,$userid,$passwd,$auto = 0) {
        global $config;

        $purl = parse_url($url);
        $addr = array();

        $client = new xmlrpc_client($purl['path'], $purl['host'], 
            $purl['port'], $purl['scheme']);

        $message = new xmlrpcmsg("pong.getContact",
            array(new xmlrpcval($userid,"string"), 
                  new xmlrpcval($passwd,"string"),
                  new xmlrpcval($config->get('purl'),"string")));

        $result = @$client->send($message,5);

        if (!isset($result) || $result == 0 || $result->faultCode()) {
            if (!isset($result) || $result == 0) {
                $addr['_errno'] = "-1";
                $addr['_errstr'] = "XMLRPC Send failure";
            } else {
                $addr['_errno'] = $result->faultCode();
                $addr['_errstr'] = $result->faultString();
            }

            # give one recursion in autodiscovery
            if (!$auto) {
                $this->autodiscover($url);
                $auto = $GLOBALS['abook_auto'];
                if ($auto != "") {
                    if (!preg_match("/^http/",$auto)) {
                        if (!preg_match("/^\//",$auto)) $auto = "/" . $auto;
                        if ($_SERVER['HTTPS'] == "on") 
                            $http = "https://";
                        else
                            $http = "http://";
                        $tmp = $http.$_SERVER['HTTP_HOST'].
                               $_SERVER['REQUEST_URI']; 
                        $tmp = substr($url,0,strrpos($url,'/'));
                        $auto = $tmp . $auto;
                    }
                    $ans = $this->update_addr($auto, $userid,$passwd,1);
                    $ans['url'] = $url;
                    return $ans;
                }
            }

            return array("url" => $url, "time" => -1, "addr" => $addr);
        } else {
            $aval = $result->value();
            $asize = $aval->arraysize();
            for ($i=0;$i<$asize;$i++) {
                $sval = $aval->arraymem($i);
                while (list($key,$kval) = $sval->structeach()) {
                    $value = $kval->scalarval();
                    $addr[$key] = $value;
                }
            }
        }

        return array("url" => $url, "time" => time(), "addr" => $addr);
    }

    function &get_addr($contact,$force = 0) {
        $url = $contact['url'];
        $userid = $contact['userid'];
        $passwd = $contact['passwd'];
        foreach ($this->abook as $id => $addr) {
            if ($addr['url'] == $url) {
                if (!$force) {
                    $cache = $GLOBALS['config']->get('abookcache');
                    $cache = $cache * 60 * 60;

                    if (($addr['time'] + $cache) > time()) 
                        return $this->abook[$id];
                }

                $addr = $this->update_addr($url,$userid,$passwd);

                # if error, well, use the old one or else
                if ($addr['time'] < 0 && $this->abook[id]['time'] > 0) 
                    return $this->abook[$id];
                unset($this->abook[$id]);
                $this->abook[] =& $addr;
                $this->changed = 1;

                return $addr;
            }
        }

        $addr = $this->update_addr($url,$userid,$passwd);
        $this->abook[] =& $addr;
        $this->changed = 1;
        return $addr;
    }

    function show_foaf() {
        global $profile, $config, $contacts;

        $force = $_REQUEST['force'];

        print <<<_FORM_HEAD_
<center>
<form method="POST" action="?q=generate_foaf">
<table class="table" cellpadding="5" width="60%">
<tr>
  <td class="title" colspan="2" align="center">
     FOAF</td>
  </tr>
_FORM_HEAD_;

        if (has_module("func_profile") && isset($profile->attribs)) {
            print <<<_ACLHEAD_
<tr>
  <td class="subtitle" colspan="2">
  Publish Information</td>
  </tr>

_ACLHEAD_;

            foreach ($profile->attribs as $att => $v) {
                $attrib =& $profile->attribs[$att];
                $class = get_class($attrib);
                $data = $attrib->get();
                $id = $attrib->getid();

                $checked = "";
                if ($_REQUEST["_a{$id}"] == "on") $checked = "checked";

                /*
                if (isset($this->foaf['att']))
                    foreach ($this->foaf['att'] as $v) 
                        $on[$v] = "checked";
                */

                print <<<_ATTRIB_
<tr>
  <td valign="middle" width="30%" align="left">
    <input type="checkbox" name="_a$id" $checked> &nbsp; $class
    </td>
  <td valign="middle" width="70%" align="left">
    $data
    </td>
  </tr>
_ATTRIB_;
            }
        }

        if (has_module("func_contacts") && isset($contacts->contacts)) {
            print <<<_CONTACTHEAD_
<tr>
  <td class="subtitle" colspan="2">
  Publish Friends</td>
  </tr>

_CONTACTHEAD_;
            foreach ($contacts->contacts as $id => $contact) {
                $name = "Contact";
                $addr =& $this->get_addr($contact,$force);
                if ($addr['time'] > 0 && isset($addr['addr'])) {
                    foreach ($addr['addr'] as $k => $v) {
                        if (strtolower($k) == "name")
                            $name = $v;
                    }
                }

                $url = $contact['url'];
                $surl = sha1($contact['url']);
                $checked = "";

                if ($_REQUEST["_$surl"] == on)
                    $checked = "checked";

                print <<<_CONTACT_
<tr>
  <td valign="middle" width="30%" align="left">
    <input type="checkbox" name="_$surl" $checked> &nbsp; $name
    </td>
  <td valign="middle" width="70%" align="left">
    $url</td>
  </tr>
_CONTACT_;
            }
        }

        $ffoaf = $config->get('foaf');
        print <<<_FORM_TAIL_
<tr>
  <td colspan="2" valign="middle" align="center">
    <input name="_ffoaf" value="$ffoaf" size="45"> &nbsp;
    <input class=button type=submit name=submit value=generate>
    </td>
  </tr>
<tr>
  <td class="help" colspan="2" align="left">
    Check on the boxes on the left to define the information you wish to
    in your FOAF file. (Define the default filename of foaf in "Config")
    </td>
  </tr>
</table>
</form>
</center>

_FORM_TAIL_;
        return 1;
    }

    function config() {
        return array("purl" => "PonG Server URL",
                     "foaf" => "Absolute Path of FOAF",
                     "abookcache" => "Addressbook cache (hours)");
    }
}

function cmd_list_abook() {
    global $contacts, $abook;

    $force = $_REQUEST['force'];

    $raw = "";

    print "<center>\n";
    print <<<_MENU_
<table cellpadding="2" width="50%">
<tr>
  <td align="left">
    <a href="?q=show_foaf">FOAF</a>
  <td align="right">
    <a href="?q=list_abook&force=1">Clear Cache</a>
    </td>
  </tr>
</table>
_MENU_;
    foreach ($contacts->contacts as $contact) {
        $addr =& $abook->get_addr($contact,$force);

        print <<<_LIST_
<table class="table" cellpadding="2" width="50%">
<tr>
  <td class="footnote" colspan="2" align="center">
    ${addr['url']}</td>
  </tr>

_LIST_;

        if ($addr['time'] < 0) {
            $errno = $addr['addr']['_errno'];
            $errstr = $addr['addr']['_errstr'];
            print <<<_ERROR_
<tr>
  <td colspan="2" align="center">
    <b>Error $errno</b> : <i>$errstr</i>
    </td>
_ERROR_;
        } else {
            $count = 0;
            foreach ($addr['addr'] as $key => $value) {
                $count++;
                if (preg_match("/^http/",$value))
                    $value = "<a href=\"$value\">$value</a>";
                if (preg_match("/^[A-Za-z\_\+\-]+\@/",$value))
                    $value = "<a href=\"mailto:$value\">$value</a>";
                print <<<_ABOOK_
<tr>
  <td align="right" width="20%">
    $key</td>
  <td align="left" width="80%">
    $value</td>
_ABOOK_;
            }
            if ($count == 0) {
                print <<<_ABOOK_EMPTY_
<tr>
  <td colspan="2" align="center">
    <i>No information provided.</i>
    </td>
_ABOOK_EMPTY_;
            }
        }

        print "</table>\n<p>\n";

    }
    print "</center>\n";

    if ($abook->changed) $abook->save();
}

function cmd_show_foaf() {
    global $abook;

    $abook->show_foaf();
}

function cmd_generate_foaf() {
    global $config, $profile, $abook, $contacts;

    $filename = $_REQUEST['_ffoaf'];
    if (!isset($filename) || $filename == "") {
        showmsg("error","Please define your output FOAF file.");
        return $abook->show_foaf();
    }

    if (!preg_match("/\.rdf$/",$filename) 
    &&  !preg_match("/\.foaf$/",$filename)
    &&  !preg_match("/\.xml$/",$filename)) {
        showmsg("error","$filename does not end with .rdf, .xml or .foaf.");
        return $abook->show_foaf();
    }

    if (!($fn = @fopen($filename,"w"))) {
        showmsg("error","Unable to open file for writing");
        return $abook->show_foaf();
    }

    $str = <<<_FOAF_HEAD_
<?xml version="1.0" encoding="UTF-8"?>

<rdf:RDF
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:wot="http://xmlns.com/wot/0.1/"
 xmlns:foaf="http://xmlns.com/foaf/0.1/"
 xmlns:semblog="http://www.semblog.org/ns/semblog/0.1/"
 xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">

<foaf:Person>

_FOAF_HEAD_;
    fwrite($fn,$str);

    if (has_module("func_profile") && isset($profile->attribs)) {
        foreach ($profile->attribs as $att => $v) {
            $id = $profile->attribs[$att]->getid();
            if ($_REQUEST["_a{$id}"] == "on")
                $profile->attribs[$att]->module_invoke("attrib_base",
                    "foaf_profile",$fn);
        }
    }
    if (has_module("func_contacts") && isset($contacts->contacts)) {
        fwrite($fn,"  <foaf:knows>\n");
        foreach ($contacts->contacts as $id => $contact) {
            $surl = sha1($contact['url']);
            if ($_REQUEST["_$surl"] != "on") continue;

            $addr =& $abook->get_addr($contact);
            if ($addr['time'] < 0) continue;
            fwrite($fn,"    <foaf:Person>\n");
            foreach ($addr['addr'] as $key => $value) {
                if ($key == "name")
                    fwrite($fn,"    <foaf:name>$value</foaf:name>\n");
                else if ($key == "homepage")
                    fwrite($fn,"    <foaf:homepage>$value</foaf:homepage>\n");
                else if ($key == "weblog")
                    fwrite($fn,"    <foaf:weblog>$value</foaf:weblog>\n");
                else if ($key == "email") {
                    $sha = sha1("mailto:".$value);
                    fwrite($fn,"    <foaf:mbox_sha1sum>$sha</foaf:mbox_sha1sum>\n");

                }
            }
            fwrite($fn,"    </foaf:Person>\n");
        }
        fwrite($fn,"  </foaf:knows>\n");
    }

    $str = <<<_FOAF_TAIL_
</foaf:Person>
</rdf:RDF>

_FOAF_TAIL_;
    fwrite($fn,$str);
    fclose($fn);

    showmsg("ok","FOAF file generated!");
    $abook->show_foaf();
}

?>
