function weather_google_conditions($lat, $lon)
{
    $enc_lat = geo_utils_prepare_coordinate($lat);
    $enc_lon = geo_utils_prepare_coordinate($lon);
    $query = array('weather' => ",,,{$enc_lat},{$enc_lon}");
    $url = $GLOBALS['weather_google_endpoint'] . "?" . http_build_query($query);
    $rsp = http_get($url);
    if (!$rsp['ok']) {
        return $rsp;
    }
    libxml_use_internal_errors(true);
    $doc = new DOMDocument();
    $doc->loadXML($rsp['body']);
    $xpath = new DOMXpath($doc);
    $cond = $xpath->query("*/current_conditions");
    $current = array();
    foreach ($cond as $c) {
        foreach ($c->childNodes as $node) {
            $k = $node->nodeName;
            $v = $node->getAttribute("data");
            if ($k == 'icon') {
                continue;
            }
            $current[$k] = $v;
        }
        break;
    }
    if (!count($current)) {
        return not_okay("failed to parse conditions");
    }
    $rsp = array('latitude' => $lat, 'longitude' => $lon, 'timestamp' => time(), 'source' => 'google', 'conditions' => $current);
    return okay($rsp);
}
function flickr_photos_exif_read(&$photo)
{
    $map = flickr_photos_media_map();
    if ($map[$photo['media']] == 'video') {
        return not_okay("video does not contain EXIF data");
    }
    $fname = "{$photo['id']}_{$photo['originalsecret']}_o.{$photo['originalformat']}";
    $froot = $GLOBALS['cfg']['flickr_static_path'] . flickr_photos_id_to_path($photo['id']);
    $path = "{$froot}/{$fname}";
    if (!preg_match("/\\.jpe?g\$/i", $path)) {
        return not_okay("not a JPEG photo");
    }
    if (!file_exists($path)) {
        return not_okay("original photo not found");
    }
    if (!filesize($path)) {
        return not_okay("original photo is empty");
    }
    # TO DO: cache me?
    $exif = exif_read_data($path);
    if (!$exif) {
        return not_okay("failed to read EXIF data");
    }
    # TO DO: expand EXIF tag values
    $to_simplejoin = array('SubjectLocation', 'GPSLatitude', 'GPSLongitude', 'GPSTimeStamp');
    foreach ($to_simplejoin as $tag) {
        if (is_array($exif[$tag])) {
            $exif[$tag] = implode(",", $exif[$tag]);
        }
    }
    # TO DO: work out how/where individual EXIF tags get
    # "prettified" ...
    ksort($exif);
    return okay(array("rows" => $exif));
}
function api_spec_utils_example_for_method($method)
{
    $path = FLAMEWORK_INCLUDE_DIR . "config.api.examples/{$method}.json";
    if (!file_exists($path)) {
        return not_okay("no example defined for {$method} method");
    }
    return okay(array('example' => file_get_contents($path)));
}
function flickr_geobookmarks_import_for_nsid($nsid, $more = array())
{
    $flickr_user = flickr_users_get_by_nsid($nsid);
    $user = users_get_by_id($flickr_user['user_id']);
    if (!$user) {
        return not_okay("Not a valid user");
    }
    $flickr_user = flickr_users_get_by_user_id($user['id']);
    $method = 'flickr.people.geoBookmarks.getList';
    $args = array('auth_token' => $flickr_user['auth_token']);
    $rsp = flickr_api_call($method, $args);
    if (!$rsp['ok']) {
        return $rsp;
    }
    if (!$rsp['rsp']['bookmarks']['count']) {
        return okay();
    }
    $bookmarks = array();
    # mark everything as private for now since none of that stuff
    # got turned on before I left, sad face... (20120217/straup)
    $geo_perms = flickr_geo_permissions_map("string keys");
    $geo_private = $geo_perms['private'];
    foreach ($rsp['rsp']['bookmarks']['bookmark'] as $bm) {
        $bm['user_id'] = $user['id'];
        $bm['name'] = $bm['label'];
        $bm['geocontext'] = $bm['context'];
        $bm['geoperms'] = $geo_private;
        $bm['woeid'] = 0;
        unset($bm['label']);
        unset($bm['pretty_name']);
        unset($bm['context']);
        $geo_method = 'flickr.places.findByLatLon';
        $geo_args = array('lat' => $bm['latitude'], 'lon' => $bm['longitude'], 'accuracy' => $bm['accuracy']);
        $geo_rsp = flickr_api_call($geo_method, $geo_args);
        if ($geo_rsp['ok']) {
            # I still miss xpath...
            $bm['woeid'] = $geo_rsp['rsp']['places']['place'][0]['woeid'];
        }
        $bookmarks[] = $bm;
    }
    $rsp = flickr_geobookmarks_purge_for_user($user);
    if (!$rsp['ok']) {
        return $rsp;
    }
    $count = 0;
    foreach ($bookmarks as $bm) {
        $rsp = flickr_geobookmarks_add($bm);
        $count += $rsp['ok'];
    }
    return okay(array('count_imported' => $count));
}
function foursquare_api_get_auth_token($code)
{
    $callback = $GLOBALS['cfg']['abs_root_url'] . $GLOBALS['cfg']['foursquare_oauth_callback'];
    $args = array('client_id' => $GLOBALS['cfg']['foursquare_oauth_key'], 'client_secret' => $GLOBALS['cfg']['foursquare_oauth_secret'], 'grant_type' => 'authorization_code', 'redirect_uri' => $callback, 'code' => $code);
    $query = http_build_query($args);
    $url = "{$GLOBALS['foursquare_oauth_endpoint']}access_token?{$query}";
    $rsp = http_get($url);
    if (!$rsp['ok']) {
        return $rsp;
    }
    $data = json_decode($rsp['body'], 'as hash');
    if (!$data || !$data['access_token']) {
        return not_okay("failed to parse response");
    }
    return okay(array('oauth_token' => $data['access_token']));
}
function flickr_photos_geo_corrections_create($correction)
{
    $user = users_get_by_id($correction['user_id']);
    if (!$user['id']) {
        return not_okay("Invalid user ID");
    }
    $cluster_id = $user['cluster_id'];
    $correction['created'] = time();
    $insert = array();
    foreach ($correction as $k => $v) {
        $insert[$k] = AddSlashes($v);
    }
    $rsp = db_insert_users($cluster_id, 'FlickrPhotosGeoCorrections', $insert);
    if ($rsp['ok']) {
        $rsp['correction'] = $correction;
    }
    return $rsp;
}
function _reverse_geoplanet_remote($lat, $lon, $remote_endpoint)
{
    $cache_key = _reverse_geoplanet_cache_key($lat, $lon);
    $cache = cache_get($cache_key);
    if ($cache['ok']) {
        return okay($cache);
    }
    #
    $query = http_build_query(array('lat' => $lat, 'lon' => $lon));
    $url = "{$remote_endpoint}?{$query}";
    $rsp = http_get($url);
    if (!$rsp['ok']) {
        return $rsp;
    }
    $data = json_decode($rsp['body'], 'as hash');
    if (!$data) {
        return not_okay("failed to parse response");
    }
    #
    cache_set($cache_key, $data, "cache locally");
    return okay(array('data' => $data, 'source' => $remote_endpoint));
}
function reverse_geoplanet_get_by_woeid($woeid, $placetype = 'woeid')
{
    $cache_key = "reverse_geoplanet_woe_{$woeid}";
    $cache = cache_get($cache_key);
    if ($cache['ok']) {
        return $cache['data'];
    }
    $valid_placetypes = array('woeid', 'locality');
    if (!in_array($placetype, $valid_placetypes)) {
        return not_okay("invalid placetype");
    }
    $enc_id = AddSlashes($woeid);
    $sql = "SELECT * FROM reverse_geoplanet WHERE `{$placetype}`='{$enc_id}'";
    $rsp = db_fetch($sql);
    $row = db_single($rsp);
    if (!$row) {
        return;
    }
    if ($row['placetype'] == 22) {
        # This is a combination of my shitty code while I was
        # at Flickr (sorry) and the part where reverse_geoplanet
        # records the neighbourhood name even if it's only storing
        # cities (because names were never critical and a bit of
        # an afterthought... (20120229/straup)
        $parts = explode(", ", $row['name']);
        $country = array_pop($parts);
        array_pop($parts);
        array_shift($parts);
        # argh...
        if ($woeid == 2459115 && $parts[0] != 'New York') {
            array_unshift($parts, "New York");
        }
        $parts[] = $country;
        $row['name'] = implode(", ", $parts);
    }
    cache_set($cache_key, $row, "cache locally");
    return $row;
}
function flickr_photos_places_contexts_for_user_and_place(&$user, &$place, $more = array())
{
    $defaults = array('viewer_id' => 0);
    $more = array_merge($defaults, $more);
    $more['enforce_geoperms'] = 1;
    if (!flickr_places_is_valid_placetype($place['place_type'])) {
        return not_okay("not a valid placetype");
    }
    $query = array("user_id" => $user['id'], $place['place_type'] => $place['woeid']);
    $map = flickr_photos_geo_context_map();
    $contexts = array();
    foreach ($map as $ctx => $ignore) {
        $contexts[$ctx] = 0;
    }
    $rsp = flickr_photos_search_facet($query, 'geocontext', $more);
    if (!$rsp['ok']) {
        return $rsp;
    }
    foreach ($rsp['facets'] as $ctx => $count) {
        $contexts[$ctx] = $count;
    }
    return okay(array('contexts' => $contexts));
}
function flickr_photos_search_index_photo(&$photo, $meta = array())
{
    if (!$GLOBALS['cfg']['enable_feature_solr']) {
        return not_okay('search indexing is disabled');
    }
    if (!$meta) {
        $meta = flickr_photos_metadata_load($photo);
    }
    # really exit or just ignore all the $meta stuff below?
    if (!$meta['ok']) {
        return not_okay('failed to load photo metadata');
    }
    $meta = $meta['data']['photo'];
    $doc = array('id' => $photo['id'], 'user_id' => $photo['user_id'], 'title' => $photo['title'], 'perms' => $photo['perms'], 'datetaken' => solr_dates_prep_mysql_datetime($photo['datetaken']), 'dateupload' => solr_dates_prep_mysql_datetime($photo['dateupload']));
    $tags = array();
    $machinetags = array();
    if (isset($meta['tags']['tag'])) {
        foreach ($meta['tags']['tag'] as $tag) {
            $tags[] = $tag['raw'];
            if ($tag['machinetag']) {
                $machinetags = array_merge($machinetags, solr_machinetags_explode($tag['raw']));
            }
        }
    }
    if (count($tags)) {
        $doc['tags'] = $tags;
    }
    if (count($machinetags)) {
        $doc['machinetags'] = $machinetags;
    }
    if ($photo['hasgeo']) {
        $doc['location'] = "{$photo['latitude']},{$photo['longitude']}";
        $doc['accuracy'] = $photo['accuracy'];
        $doc['geoperms'] = $photo['geoperms'];
        $doc['geocontext'] = $photo['geocontext'];
        foreach (array('neighbourhood', 'locality', 'county', 'region', 'country', 'continent') as $place) {
            if (isset($meta['location'][$place])) {
                $doc[$place] = $meta['location'][$place]['woeid'];
            }
        }
        if ($place = flickr_places_get_by_woeid($photo['woeid'])) {
            $doc['timezone'] = $place['timezone'];
            $doc['place'] = $place['place_url'];
        }
    }
    # pull in some EXIF data (if present)
    $rsp = flickr_photos_exif_read($photo);
    if ($rsp['ok']) {
        $exif = $rsp['rows'];
        if (isset($exif['Make'])) {
            if ($make = exif_tools_scrub_string($exif['Make'])) {
                $doc['camera_make'] = ucwords($make);
            }
        }
        if (isset($exif['Model'])) {
            if ($model = exif_tools_scrub_string($exif['Model'])) {
                $doc['camera_model'] = $model;
            }
        }
        # EXIF: what else?
        if (isset($exif['FocalLength'])) {
            $doc['focal_length'] = exif_tools_rational2float($exif['FocalLength']);
        }
        if (isset($exif['ApetureValue'])) {
            $doc['apeture'] = exif_tools_rational2float($exif['ApetureValue']);
        }
        if (isset($exif['ShutterSpeedValue'])) {
            $doc['shutter_speed'] = exif_tools_rational2float($exif['ShutterSpeedValue']);
        }
        if (isset($exif['ISOSpeedRatings'])) {
            $doc['iso_speed'] = intval($exif['ISOSpeedRatings']);
        }
        # http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html
        if (isset($exif['GPSAltitude'])) {
            $altitude = exif_tools_explode_gps_altitude($exif['GPSAltitude'], $exif['GPSAltitudeRef']);
            $doc['altitude'] = $altitude;
        }
        if (isset($exif['GPSImgDirection'])) {
            $direction = exif_tools_explode_gps_img_direction($exif['GPSImgDirection'], $exif['GPSImgDirectionRef']);
            $doc['direction'] = $direction;
        }
    }
    # go!
    $docs = array($doc);
    $rsp = solr_add($docs);
    return $rsp;
}
function flickr_photos_import_get_recent($nsid, $more = array())
{
    $flickr_user = flickr_users_get_by_nsid($nsid);
    $user = users_get_by_id($flickr_user['user_id']);
    if (!$user) {
        return array('ok' => 0, 'error' => 'not a valid user');
    }
    $method = 'flickr.photos.recentlyUpdated';
    if (!isset($more['min_date'])) {
        $offset_days = 1;
        $offset = intval(60 * 60 * 24 * $offset_days);
        $min_date = time() - $offset;
    } else {
        $min_date = intval($more['min_date']);
    }
    $args = array('auth_token' => $flickr_user['auth_token'], 'min_date' => $min_date, 'extras' => 'original_format,tags,media,date_upload,date_taken,geo', 'per_page' => 100, 'page' => 1);
    $pages = null;
    $imported = 0;
    # TO DO: capture dateupdate for each photo and return that
    # if there's a fatal error so that the FlickrBackups database
    # can be set with something other than 0 (20111206/straup)
    while (!isset($pages) || $pages >= $args['page']) {
        # because the Flickr API has an annoying habit of
        # timing out and this causes an initial import of
        # photos to fail and be repeated in-toto over and
        # over again (20111206/straup)
        $tries = 1;
        $max_tries = 5;
        $ok = 0;
        while (!$ok && $tries < $max_tries) {
            $rsp = flickr_api_call($method, $args);
            $ok = $rsp['ok'];
            $tries++;
            if ($ok) {
                $photos = $rsp['rsp']['photos']['photo'];
                if (!is_array($photos)) {
                    $rsp = not_okay("no photos");
                    $ok = 0;
                }
            }
        }
        if (!$ok) {
            return $rsp;
        }
        if (!isset($pages)) {
            $pages = $rsp['rsp']['photos']['pages'];
        }
        # TO DO: date update stuff (see above)
        foreach ($photos as $photo) {
            flickr_photos_import_photo($photo, $more);
            $imported++;
        }
        $args['page'] += 1;
    }
    return okay(array('count_imported' => $imported));
}
function flickr_backups_get_contacts(&$user)
{
    $backups = flickr_backups_for_user($user);
    if (!isset($backups['contacts'])) {
        return not_okay("backups not registered");
    }
    $backup = $backups['contacts'];
    $update = array();
    $start_time = time();
    $flickr_user = flickr_users_get_by_user_id($user['id']);
    $more = array('purge_existing_contacts' => 1);
    $rsp = flickr_contacts_import_for_nsid($flickr_user['nsid'], $more);
    if ($rsp['ok']) {
        $update['date_lastupdate'] = $start_time;
        $update['details'] = "count: {$rsp['count_imported']}";
        if (!$backup['date_firstupdate']) {
            $update['date_firstupdate'] = $update['date_lastupdate'];
        }
    } else {
        $update['details'] = "update failed ({$start_time}) : {$rsp['error']}";
    }
    flickr_backups_update($backup, $update);
    return $rsp;
}
function flickr_contacts_import_for_nsid($nsid, $more = array())
{
    $flickr_user = flickr_users_get_by_nsid($nsid);
    $user = users_get_by_id($flickr_user['user_id']);
    if (!$user) {
        return array('ok' => 0, 'error' => 'not a valid user');
    }
    $method = 'flickr.contacts.getList';
    $all_contacts = array();
    $count_contacts = 0;
    $args = array('auth_token' => $flickr_user['auth_token'], 'per_page' => 100, 'page' => 1);
    $pages = null;
    while (!isset($pages) || $pages >= $args['page']) {
        $api_ok = 0;
        $api_error = '';
        # Can I just say this is so profoundly annoying. Why why why
        # are API calls to a federated database table failing? Anyway.
        # (20120201/straup)
        $retries = 0;
        $max_retries = 10;
        while (!$api_ok) {
            $retries += 1;
            $rsp = flickr_api_call($method, $args);
            $api_ok = $rsp['ok'];
            if (!$api_ok) {
                $api_error = "The Flickr API is wigging out: {$rsp['error']}";
            } else {
                $contacts = $rsp['rsp']['contacts']['contact'];
                if (!is_array($contacts)) {
                    $api_error = "The Flickr API did not return any contacts";
                    $api_ok = 0;
                }
            }
            echo "page: {$args['page']}/{$pages} tries: {$retries}/{$max_retries} ok: {$api_ok}\n";
            if (!$api_ok) {
                if ($retries == $max_retries) {
                    return not_okay("Unable to fetch contacts: {$api_error}");
                }
            }
        }
        if (!isset($pages)) {
            $pages = $rsp['rsp']['contacts']['pages'];
        }
        foreach ($contacts as $contact) {
            $contact_nsid = $contact['nsid'];
            $contact_username = $contact['username'];
            $flickr_contact = flickr_users_get_by_nsid($contact_nsid);
            if (!$flickr_contact) {
                $password = random_string(32);
                $user_contact = users_create_user(array("username" => $contact_username, "email" => "{$contact_username}@donotsend-flickr.com", "password" => $password));
                #
                $method = 'flickr.people.getInfo';
                $args = array('user_id' => $contact_nsid);
                $rsp = flickr_api_call($method, $args);
                $path_alias = $rsp['ok'] ? $rsp['rsp']['person']['path_alias'] : '';
                #
                $flickr_contact = flickr_users_create_user(array('user_id' => $user_contact['id'], 'nsid' => $contact_nsid, 'path_alias' => $path_alias));
            }
            $rel = flickr_contacts_calculate_relationship($contact);
            # echo "{$contact_username} : {$rel} ({$contact['friend']} {$contact['family']})\n";
            $insert = array('user_id' => $user['id'], 'contact_id' => $flickr_contact['user_id'], 'rel' => $rel);
            $all_contacts[] = $insert;
        }
        $args['page'] += 1;
    }
    if (isset($more['purge_existing_contacts'])) {
        $rsp = flickr_contacts_purge_contacts($user);
        if (!$rsp['ok']) {
            return not_okay("failed to purge existing contacts: {$rsp['error']}");
        }
    }
    # echo "import " . count($all_contacts) . " contacts\n";
    foreach ($all_contacts as $insert) {
        if (flickr_contacts_add_contact($insert)) {
            $count_contacts++;
        }
    }
    return array('ok' => 1, 'count_imported' => $count_contacts);
}
function flickr_photos_get_bookends_for_user(&$user, $more = array())
{
    $defaults = array('viewer_id' => 0, 'context' => 'datetaken');
    $more = array_merge($defaults, $more);
    if (!in_array($more['context'], array('datetaken', 'dateupload'))) {
        return not_okay("invalid date context");
    }
    $cluster_id = $user['cluster_id'];
    $enc_user = AddSlashes($user['id']);
    if ($perms = flickr_photos_permissions_photos_where($user['id'], $more['viewer_id'])) {
        $str_perms = implode(",", $perms);
        $extra = " AND perms IN ({$str_perms})";
    }
    # TO DO: INDEXES
    $sql = "SELECT MIN(`{$more['context']}`) AS start, MAX(`{$more['context']}`) AS end FROM FlickrPhotos WHERE user_id = '{$enc_user}' {$extra}";
    $rsp = db_fetch_users($cluster_id, $sql);
    if (!$rsp['ok']) {
        return $rsp;
    }
    $row = db_single($rsp);
    if (!$row) {
        return not_okay("no photos to bookend!");
    }
    return okay($row);
}