function flickr_faves_add_fave(&$viewer, &$photo, $date_faved = 0)
{
    if (!$date_faved) {
        $date_faved = time();
    }
    $cluster_id = $viewer['cluster_id'];
    $fave = array('user_id' => $viewer['id'], 'photo_id' => $photo['id'], 'owner_id' => $photo['user_id'], 'date_faved' => $date_faved);
    $insert = array();
    foreach ($fave as $k => $v) {
        $insert[$k] = AddSlashes($v);
    }
    $rsp = db_insert_users($cluster_id, 'FlickrFaves', $insert);
    if (!$rsp['ok'] && $rsp['error_code'] != 1062) {
        return $rsp;
    }
    # now update the photo owner side of things
    $owner = users_get_by_id($photo['user_id']);
    $cluster_id = $owner['cluster_id'];
    $fave = array('user_id' => $owner['id'], 'photo_id' => $photo['id'], 'viewer_id' => $viewer['id']);
    $insert = array();
    foreach ($fave as $k => $v) {
        $insert[$k] = AddSlashes($v);
    }
    $rsp = db_insert_users($cluster_id, 'FlickrFavesUsers', $insert);
    if (!$rsp['ok'] && $rsp['error_code'] != 1062) {
        return $rsp;
    }
    # TO DO: index/update the photo in solr and insert $viewer['id']
    # into the faved_by column (20111123/straup)
    return okay();
}
function privatesquare_export_geojson($fh, $checkins, $more = array())
{
    $features = array();
    $swlat = null;
    $swlon = null;
    $nelat = null;
    $nelon = null;
    foreach ($checkins as $row) {
        # See notes in privatesquare_export_csv for why we're
        # doing this explicitly (20120227/straup)
        $more = array('inflate_weather' => 1);
        privatesquare_export_massage_checkin($row, $more);
        $lat = floatval($row['latitude']);
        $lon = floatval($row['longitude']);
        $swlat = isset($swlat) ? min($swlat, $lat) : $lat;
        $swlon = isset($swlon) ? min($swlon, $lon) : $lon;
        $nelat = isset($nelat) ? max($nelat, $lat) : $lat;
        $nelon = isset($nelon) ? max($nelon, $lon) : $lon;
        $features[] = array('type' => 'Feature', 'id' => $row['id'], 'properties' => $row, 'geometry' => array('type' => 'Point', 'coordinates' => array($lon, $lat)));
    }
    $geojson = array('type' => 'FeatureCollection', 'bbox' => array($swlon, $swlat, $nelon, $nelat), 'features' => $features);
    fwrite($fh, json_encode($geojson));
    if (isset($more['donot_send'])) {
        return okay();
    }
    $map = privatesquare_export_valid_formats();
    $headers = array('Content-type' => $map['geojson']);
    privatesquare_export_send($fh, $headers, $more);
}
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 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 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_metadata_fetch(&$photo, $inflate = 0)
{
    loadlib("flickr_api");
    loadlib("flickr_users");
    $flickr_user = flickr_users_get_by_user_id($photo['user_id']);
    $method = 'flickr.photos.getInfo';
    $args = array('photo_id' => $photo['id'], 'auth_token' => $flickr_user['auth_token']);
    $more = array();
    if (!$inflate) {
        $more['raw'] = 1;
    }
    $rsp = flickr_api_call($method, $args, $more);
    if ($rsp['ok']) {
        $data = $inflate ? $rsp['rsp'] : $rsp['body'];
        $rsp = okay(array('data' => $data));
    }
    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 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 privatesquare_export_csv($fh, $checkins, $more = array())
{
    # TO DO: this works fine until we want to "explode" specific
    # fields (like 'weather') because we need to find all the distinct
    # keys that might be used (across both fields and potential
    # providers: google weather versus some other api). This is
    # either a head scratch or a pain in the ass or both...
    # (2012027/straup)
    $header = 0;
    foreach ($checkins as $row) {
        privatesquare_export_massage_checkin($row);
        if (!$header) {
            fputcsv($fh, array_keys($row));
            $header = 1;
        }
        fputcsv($fh, array_values($row));
    }
    if (isset($more['donot_send'])) {
        return okay();
    }
    $map = privatesquare_export_valid_formats();
    $headers = array('Content-type' => $map['csv']);
    privatesquare_export_send($fh, $headers, $more);
}
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_toggle_push_subscription(&$backup, $enable)
{
    $push_features = array("flickr_push", "flickr_push_backups");
    if (!features_is_enabled($push_features)) {
        return null;
    }
    # First, figure out if this backup type is a valid push
    # backup topic - this duplicates most/all of the code in
    # flickr_backups_is_push_backups but since we'll need the
    # stub subscription (and the topic map) in order to create
    # new subscriptions we're just not going to worry about it
    # too much (20120608/straup)
    $type_id = $backup['type_id'];
    $map = flickr_backups_push_topics_map();
    if (!isset($map[$type_id])) {
        return null;
    }
    # Stub subscription data
    $user = users_get_by_id($backup['user_id']);
    $topic_id = $map[$type_id];
    $sub = array('user_id' => $user['id'], 'topic_id' => $topic_id);
    if (!flickr_backups_is_registered_push_subscription($sub)) {
        return null;
    }
    if ($enable) {
        $push_rsp = flickr_push_subscriptions_register_subscription($sub);
    } else {
        # Okay, now fetch the actual subscription in order to unsubscribe
        $sub = flickr_push_subscriptions_get_by_user_and_topic($user, $topic_id);
        # Keeping in mind that it may not actually exist...
        if (!$sub) {
            return okay();
        }
        $push_rsp = flickr_push_subscriptions_remove_subscription($sub, 1);
    }
    return $push_rsp;
}
function privatesquare_checkins_for_user_nearby(&$user, $lat, $lon, $more = array())
{
    loadlib("geo_utils");
    $dist = isset($more['dist']) ? floatval($more['dist']) : 0.2;
    $unit = geo_utils_is_valid_unit($more['unit']) ? $more['unit'] : 'm';
    # TO DO: sanity check to ensure max $dist
    $bbox = geo_utils_bbox_from_point($lat, $lon, $dist, $unit);
    $cluster_id = $user['cluster_id'];
    $enc_user = AddSlashes($user['id']);
    # TO DO: group by venue_id in memory since the following will always
    # result in a filesort (20120301/straup)
    $sql = "SELECT venue_id, COUNT(id) AS count FROM PrivatesquareCheckins WHERE user_id='{$enc_user}'";
    $sql .= " AND latitude BETWEEN {$bbox[0]} AND {$bbox[2]} AND longitude BETWEEN {$bbox[1]} AND {$bbox[3]}";
    $sql .= " GROUP BY venue_id";
    $rsp = db_fetch_users($cluster_id, $sql, $more);
    if (!$rsp['ok']) {
        return $rsp;
    }
    $tmp = array();
    foreach ($rsp['rows'] as $row) {
        $tmp[$row['venue_id']] = $row['count'];
    }
    arsort($tmp);
    $venues = array();
    foreach ($tmp as $venue_id => $count) {
        $venue = foursquare_venues_get_by_venue_id($venue_id);
        $venue['count_checkins'] = $count;
        $venues[] = $venue;
    }
    return okay(array('rows' => $venues));
}
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);
}