예제 #1
0
파일: zot.php 프로젝트: 23n/hubzilla
/**
 * @brief
 *
 * @param array $sender
 * @param array $arr
 * @param array $deliveries
 * @return array
 */
function process_channel_sync_delivery($sender, $arr, $deliveries)
{
    require_once 'include/import.php';
    /** @FIXME this will sync red structures (channel, pconfig and abook). Eventually we need to make this application agnostic. */
    $result = array();
    foreach ($deliveries as $d) {
        $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']));
        if (!$r) {
            $result[] = array($d['hash'], 'not found');
            continue;
        }
        $channel = $r[0];
        $max_friends = service_class_fetch($channel['channel_id'], 'total_channels');
        $max_feeds = account_service_class_fetch($channel['channel_account_id'], 'total_feeds');
        if ($channel['channel_hash'] != $sender['hash']) {
            logger('process_channel_sync_delivery: possible forgery. Sender ' . $sender['hash'] . ' is not ' . $channel['channel_hash']);
            $result[] = array($d['hash'], 'channel mismatch', $channel['channel_name'], '');
            continue;
        }
        if (array_key_exists('config', $arr) && is_array($arr['config']) && count($arr['config'])) {
            foreach ($arr['config'] as $cat => $k) {
                foreach ($arr['config'][$cat] as $k => $v) {
                    set_pconfig($channel['channel_id'], $cat, $k, $v);
                }
            }
        }
        if (array_key_exists('obj', $arr) && $arr['obj']) {
            sync_objs($channel, $arr['obj']);
        }
        if (array_key_exists('likes', $arr) && $arr['likes']) {
            import_likes($channel, $arr['likes']);
        }
        if (array_key_exists('app', $arr) && $arr['app']) {
            sync_apps($channel, $arr['app']);
        }
        if (array_key_exists('chatroom', $arr) && $arr['chatroom']) {
            sync_chatrooms($channel, $arr['chatroom']);
        }
        if (array_key_exists('conv', $arr) && $arr['conv']) {
            import_conv($channel, $arr['conv']);
        }
        if (array_key_exists('mail', $arr) && $arr['mail']) {
            import_mail($channel, $arr['mail']);
        }
        if (array_key_exists('event', $arr) && $arr['event']) {
            sync_events($channel, $arr['event']);
        }
        if (array_key_exists('event_item', $arr) && $arr['event_item']) {
            sync_items($channel, $arr['event_item']);
        }
        if (array_key_exists('item', $arr) && $arr['item']) {
            sync_items($channel, $arr['item']);
        }
        if (array_key_exists('item_id', $arr) && $arr['item_id']) {
            sync_items($channel, $arr['item_id']);
        }
        if (array_key_exists('menu', $arr) && $arr['menu']) {
            sync_menus($channel, $arr['menu']);
        }
        if (array_key_exists('channel', $arr) && is_array($arr['channel']) && count($arr['channel'])) {
            if (array_key_exists('channel_pageflags', $arr['channel']) && intval($arr['channel']['channel_pageflags'])) {
                // These flags cannot be sync'd.
                // remove the bits from the incoming flags.
                // These correspond to PAGE_REMOVED and PAGE_SYSTEM on redmatrix
                if ($arr['channel']['channel_pageflags'] & 0x8000) {
                    $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] - 0x8000;
                }
                if ($arr['channel']['channel_pageflags'] & 0x1000) {
                    $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] - 0x1000;
                }
            }
            $disallowed = array('channel_id', 'channel_account_id', 'channel_primary', 'channel_prvkey', 'channel_address', 'channel_notifyflags', 'channel_removed', 'channel_deleted', 'channel_system');
            $clean = array();
            foreach ($arr['channel'] as $k => $v) {
                if (in_array($k, $disallowed)) {
                    continue;
                }
                $clean[$k] = $v;
            }
            if (count($clean)) {
                foreach ($clean as $k => $v) {
                    $r = dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) . "' where channel_id = " . intval($channel['channel_id']));
                }
            }
        }
        if (array_key_exists('abook', $arr) && is_array($arr['abook']) && count($arr['abook'])) {
            $total_friends = 0;
            $total_feeds = 0;
            $r = q("select abook_id, abook_feed from abook where abook_channel = %d", intval($channel['channel_id']));
            if ($r) {
                // don't count yourself
                $total_friends = count($r) > 0 ? count($r) - 1 : 0;
                foreach ($r as $rr) {
                    if (intval($rr['abook_feed'])) {
                        $total_feeds++;
                    }
                }
            }
            $disallowed = array('abook_id', 'abook_account', 'abook_channel', 'abook_rating', 'abook_rating_text');
            foreach ($arr['abook'] as $abook) {
                if (!array_key_exists('abook_blocked', $abook)) {
                    // convert from redmatrix
                    $abook['abook_blocked'] = $abook['abook_flags'] & 0x1 ? 1 : 0;
                    $abook['abook_ignored'] = $abook['abook_flags'] & 0x2 ? 1 : 0;
                    $abook['abook_hidden'] = $abook['abook_flags'] & 0x4 ? 1 : 0;
                    $abook['abook_archived'] = $abook['abook_flags'] & 0x8 ? 1 : 0;
                    $abook['abook_pending'] = $abook['abook_flags'] & 0x10 ? 1 : 0;
                    $abook['abook_unconnected'] = $abook['abook_flags'] & 0x20 ? 1 : 0;
                    $abook['abook_self'] = $abook['abook_flags'] & 0x80 ? 1 : 0;
                    $abook['abook_feed'] = $abook['abook_flags'] & 0x100 ? 1 : 0;
                }
                $clean = array();
                if ($abook['abook_xchan'] && $abook['entry_deleted']) {
                    logger('process_channel_sync_delivery: removing abook entry for ' . $abook['abook_xchan']);
                    require_once 'include/Contact.php';
                    $r = q("select abook_id, abook_feed from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", dbesc($abook['abook_xchan']), intval($channel['channel_id']));
                    if ($r) {
                        contact_remove($channel['channel_id'], $r[0]['abook_id']);
                        if ($total_friends) {
                            $total_friends--;
                        }
                        if (intval($r[0]['abook_feed'])) {
                            $total_feeds--;
                        }
                    }
                    continue;
                }
                // Perform discovery if the referenced xchan hasn't ever been seen on this hub.
                // This relies on the undocumented behaviour that red sites send xchan info with the abook
                // and import_author_xchan will look them up on all federated networks
                if ($abook['abook_xchan'] && $abook['xchan_addr']) {
                    $h = zot_get_hublocs($abook['abook_xchan']);
                    if (!$h) {
                        $xhash = import_author_xchan(encode_item_xchan($abook));
                        if (!$xhash) {
                            logger('process_channel_sync_delivery: import of ' . $abook['xchan_addr'] . ' failed.');
                            continue;
                        }
                    }
                }
                foreach ($abook as $k => $v) {
                    if (in_array($k, $disallowed) || strpos($k, 'abook') !== 0) {
                        continue;
                    }
                    $clean[$k] = $v;
                }
                if (!array_key_exists('abook_xchan', $clean)) {
                    continue;
                }
                $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($clean['abook_xchan']), intval($channel['channel_id']));
                // make sure we have an abook entry for this xchan on this system
                if (!$r) {
                    if ($max_friends !== false && $total_friends > $max_friends) {
                        logger('process_channel_sync_delivery: total_channels service class limit exceeded');
                        continue;
                    }
                    if ($max_feeds !== false && intval($clean['abook_feed']) && $total_feeds > $max_feeds) {
                        logger('process_channel_sync_delivery: total_feeds service class limit exceeded');
                        continue;
                    }
                    q("insert into abook ( abook_xchan, abook_channel ) values ('%s', %d ) ", dbesc($clean['abook_xchan']), intval($channel['channel_id']));
                    $total_friends++;
                    if (intval($clean['abook_feed'])) {
                        $total_feeds++;
                    }
                }
                if (count($clean)) {
                    foreach ($clean as $k => $v) {
                        if ($k == 'abook_dob') {
                            $v = dbescdate($v);
                        }
                        $r = dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id']));
                    }
                }
            }
        }
        // sync collections (privacy groups) oh joy...
        if (array_key_exists('collections', $arr) && is_array($arr['collections']) && count($arr['collections'])) {
            $x = q("select * from groups where uid = %d", intval($channel['channel_id']));
            foreach ($arr['collections'] as $cl) {
                $found = false;
                if ($x) {
                    foreach ($x as $y) {
                        if ($cl['collection'] == $y['hash']) {
                            $found = true;
                            break;
                        }
                    }
                    if ($found) {
                        if ($y['name'] != $cl['name'] || $y['visible'] != $cl['visible'] || $y['deleted'] != $cl['deleted']) {
                            q("update groups set name = '%s', visible = %d, deleted = %d where hash = '%s' and uid = %d", dbesc($cl['name']), intval($cl['visible']), intval($cl['deleted']), dbesc($cl['hash']), intval($channel['channel_id']));
                        }
                        if (intval($cl['deleted']) && !intval($y['deleted'])) {
                            q("delete from group_member where gid = %d", intval($y['id']));
                        }
                    }
                }
                if (!$found) {
                    $r = q("INSERT INTO `groups` ( hash, uid, visible, deleted, name )\n\t\t\t\t\t\tVALUES( '%s', %d, %d, %d, '%s' ) ", dbesc($cl['collection']), intval($channel['channel_id']), intval($cl['visible']), intval($cl['deleted']), dbesc($cl['name']));
                }
                // now look for any collections locally which weren't in the list we just received.
                // They need to be removed by marking deleted and removing the members.
                // This shouldn't happen except for clones created before this function was written.
                if ($x) {
                    $found_local = false;
                    foreach ($x as $y) {
                        foreach ($arr['collections'] as $cl) {
                            if ($cl['collection'] == $y['hash']) {
                                $found_local = true;
                                break;
                            }
                        }
                        if (!$found_local) {
                            q("delete from group_member where gid = %d", intval($y['id']));
                            q("update groups set deleted = 1 where id = %d and uid = %d", intval($y['id']), intval($channel['channel_id']));
                        }
                    }
                }
            }
            // reload the group list with any updates
            $x = q("select * from groups where uid = %d", intval($channel['channel_id']));
            // now sync the members
            if (array_key_exists('collection_members', $arr) && is_array($arr['collection_members']) && count($arr['collection_members'])) {
                // first sort into groups keyed by the group hash
                $members = array();
                foreach ($arr['collection_members'] as $cm) {
                    if (!array_key_exists($cm['collection'], $members)) {
                        $members[$cm['collection']] = array();
                    }
                    $members[$cm['collection']][] = $cm['member'];
                }
                // our group list is already synchronised
                if ($x) {
                    foreach ($x as $y) {
                        // for each group, loop on members list we just received
                        foreach ($members[$y['hash']] as $member) {
                            $found = false;
                            $z = q("select xchan from group_member where gid = %d and uid = %d and xchan = '%s' limit 1", intval($y['id']), intval($channel['channel_id']), dbesc($member));
                            if ($z) {
                                $found = true;
                            }
                            // if somebody is in the group that wasn't before - add them
                            if (!$found) {
                                q("INSERT INTO `group_member` (`uid`, `gid`, `xchan`)\n\t\t\t\t\t\t\t\t\tVALUES( %d, %d, '%s' ) ", intval($channel['channel_id']), intval($y['id']), dbesc($member));
                            }
                        }
                        // now retrieve a list of members we have on this site
                        $m = q("select xchan from group_member where gid = %d and uid = %d", intval($y['id']), intval($channel['channel_id']));
                        if ($m) {
                            foreach ($m as $mm) {
                                // if the local existing member isn't in the list we just received - remove them
                                if (!in_array($mm['xchan'], $members[$y['hash']])) {
                                    q("delete from group_member where xchan = '%s' and gid = %d and uid = %d", dbesc($mm['xchan']), intval($y['id']), intval($channel['channel_id']));
                                }
                            }
                        }
                    }
                }
            }
        }
        if (array_key_exists('profile', $arr) && is_array($arr['profile']) && count($arr['profile'])) {
            $disallowed = array('id', 'aid', 'uid');
            foreach ($arr['profile'] as $profile) {
                $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id']));
                if (!$x) {
                    q("insert into profile ( profile_guid, aid, uid ) values ('%s', %d, %d)", dbesc($profile['profile_guid']), intval($channel['channel_account_id']), intval($channel['channel_id']));
                    $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id']));
                    if (!$x) {
                        continue;
                    }
                }
                $clean = array();
                foreach ($profile as $k => $v) {
                    if (in_array($k, $disallowed)) {
                        continue;
                    }
                    $clean[$k] = $v;
                    /**
                     * @TODO check if these are allowed, otherwise we'll error
                     * We also need to import local photos if a custom photo is selected
                     */
                }
                if (count($clean)) {
                    foreach ($clean as $k => $v) {
                        $r = dbq("UPDATE profile set `" . dbesc($k) . "` = '" . dbesc($v) . "' where profile_guid = '" . dbesc($profile['profile_guid']) . "' and uid = " . intval($channel['channel_id']));
                    }
                }
            }
        }
        if (array_key_exists('item', $arr) && $arr['item']) {
            sync_items($channel, $arr['item']);
        }
        if (array_key_exists('item_id', $arr) && $arr['item_id']) {
            sync_items($channel, $arr['item_id']);
        }
        $addon = array('channel' => $channel, 'data' => $arr);
        call_hooks('process_channel_sync_delivery', $addon);
        // we should probably do this for all items, but usually we only send one.
        require_once 'include/DReport.php';
        if (array_key_exists('item', $arr) && is_array($arr['item'][0])) {
            $DR = new DReport(z_root(), $d['hash'], $d['hash'], $arr['item'][0]['message_id'], 'channel sync processed');
            $DR->addto_recipient($channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>');
        } else {
            $DR = new DReport(z_root(), $d['hash'], $d['hash'], 'sync packet', 'channel sync delivered');
        }
        $result[] = $DR->get();
    }
    return $result;
}
예제 #2
0
function sync_files($channel, $files)
{
    require_once 'include/attach.php';
    if ($channel && $files) {
        foreach ($files as $f) {
            if (!$f) {
                continue;
            }
            $fetch_url = $f['fetch_url'];
            $oldbase = dirname($fetch_url);
            $original_channel = $f['original_channel'];
            if (!($fetch_url && $original_channel)) {
                continue;
            }
            if ($f['attach']) {
                $attachment_stored = false;
                foreach ($f['attach'] as $att) {
                    convert_oldfields($att, 'data', 'content');
                    if ($att['deleted']) {
                        attach_delete($channel, $att['hash']);
                        continue;
                    }
                    $attach_exists = false;
                    $x = attach_by_hash($att['hash']);
                    logger('sync_files duplicate check: attach_exists=' . $attach_exists, LOGGER_DEBUG);
                    logger('sync_files duplicate check: att=' . print_r($att, true), LOGGER_DEBUG);
                    logger('sync_files duplicate check: attach_by_hash() returned ' . print_r($x, true), LOGGER_DEBUG);
                    if ($x['success']) {
                        $attach_exists = true;
                        $attach_id = $x[0]['id'];
                    }
                    $newfname = 'store/' . $channel['channel_address'] . '/' . get_attach_binname($att['content']);
                    unset($att['id']);
                    $att['aid'] = $channel['channel_account_id'];
                    $att['uid'] = $channel['channel_id'];
                    // check for duplicate folder names with the same parent.
                    // If we have a duplicate that doesn't match this hash value
                    // change the name so that the contents won't be "covered over"
                    // by the existing directory. Use the same logic we use for
                    // duplicate files.
                    if (strpos($att['filename'], '.') !== false) {
                        $basename = substr($att['filename'], 0, strrpos($att['filename'], '.'));
                        $ext = substr($att['filename'], strrpos($att['filename'], '.'));
                    } else {
                        $basename = $att['filename'];
                        $ext = '';
                    }
                    $r = q("select filename from attach where ( filename = '%s' OR filename like '%s' ) and folder = '%s' and hash != '%s' ", dbesc($basename . $ext), dbesc($basename . '(%)' . $ext), dbesc($att['folder']), dbesc($att['hash']));
                    if ($r) {
                        $x = 1;
                        do {
                            $found = false;
                            foreach ($r as $rr) {
                                if ($rr['filename'] === $basename . '(' . $x . ')' . $ext) {
                                    $found = true;
                                    break;
                                }
                            }
                            if ($found) {
                                $x++;
                            }
                        } while ($found);
                        $att['filename'] = $basename . '(' . $x . ')' . $ext;
                    } else {
                        $att['filename'] = $basename . $ext;
                    }
                    // end duplicate detection
                    // @fixme - update attachment structures if they are modified rather than created
                    $att['content'] = $newfname;
                    // Note: we use $att['hash'] below after it has been escaped to
                    // fetch the file contents.
                    // If the hash ever contains any escapable chars this could cause
                    // problems. Currently it does not.
                    dbesc_array($att);
                    if ($attach_exists) {
                        logger('sync_files attach exists: ' . print_r($att, true), LOGGER_DEBUG);
                        $str = '';
                        foreach ($att as $k => $v) {
                            if ($str) {
                                $str .= ",";
                            }
                            $str .= " `" . $k . "` = '" . $v . "' ";
                        }
                        $r = dbq("update `attach` set " . $str . " where id = " . intval($attach_id));
                    } else {
                        logger('sync_files attach does not exists: ' . print_r($att, true), LOGGER_DEBUG);
                        $r = dbq("INSERT INTO attach (`" . implode("`, `", array_keys($att)) . "`) VALUES ('" . implode("', '", array_values($att)) . "')");
                    }
                    // is this a directory?
                    if ($att['filetype'] === 'multipart/mixed' && $att['is_dir']) {
                        os_mkdir($newfname, STORAGE_DEFAULT_PERMISSIONS, true);
                        $attachment_stored = true;
                        continue;
                    } else {
                        // it's a file
                        // for the sync version of this algorithm (as opposed to 'offline import')
                        // we will fetch the actual file from the source server so it can be
                        // streamed directly to disk and avoid consuming PHP memory if it's a huge
                        // audio/video file or something.
                        $time = datetime_convert();
                        $parr = array('hash' => $channel['channel_hash'], 'time' => $time, 'resource' => $att['hash'], 'revision' => 0, 'signature' => base64url_encode(rsa_sign($channel['channel_hash'] . '.' . $time, $channel['channel_prvkey'])));
                        $store_path = $newfname;
                        $fp = fopen($newfname, 'w');
                        if (!$fp) {
                            logger('failed to open storage file.', LOGGER_NORMAL, LOG_ERR);
                            continue;
                        }
                        $redirects = 0;
                        $x = z_post_url($fetch_url, $parr, $redirects, array('filep' => $fp));
                        fclose($fp);
                        if ($x['success']) {
                            $attachment_stored = true;
                        }
                        continue;
                    }
                }
            }
            if (!$attachment_stored) {
                // @TODO should we queue this and retry or delete everything or what?
                logger('attachment store failed', LOGGER_NORMAL, LOG_ERR);
            }
            if ($f['photo']) {
                foreach ($f['photo'] as $p) {
                    unset($p['id']);
                    $p['aid'] = $channel['channel_account_id'];
                    $p['uid'] = $channel['channel_id'];
                    convert_oldfields($p, 'data', 'content');
                    convert_oldfields($p, 'scale', 'imgscale');
                    convert_oldfields($p, 'size', 'filesize');
                    convert_oldfields($p, 'type', 'mimetype');
                    // if this is a profile photo, undo the profile photo bit
                    // for any other photo which previously held it.
                    if ($p['photo_usage'] == PHOTO_PROFILE) {
                        $e = q("update photo set photo_usage = %d where photo_usage = %d\n\t\t\t\t\t\t\tand resource_id != '%s' and uid = %d ", intval(PHOTO_NORMAL), intval(PHOTO_PROFILE), dbesc($p['resource_id']), intval($channel['channel_id']));
                    }
                    // same for cover photos
                    if ($p['photo_usage'] == PHOTO_COVER) {
                        $e = q("update photo set photo_usage = %d where photo_usage = %d\n\t\t\t\t\t\t\tand resource_id != '%s' and uid = %d ", intval(PHOTO_NORMAL), intval(PHOTO_COVER), dbesc($p['resource_id']), intval($channel['channel_id']));
                    }
                    if ($p['imgscale'] === 0 && $p['os_storage']) {
                        $p['content'] = $store_path;
                    } else {
                        $p['content'] = base64_decode($p['content']);
                    }
                    $exists = q("select * from photo where resource_id = '%s' and imgscale = %d and uid = %d limit 1", dbesc($p['resource_id']), intval($p['imgscale']), intval($channel['channel_id']));
                    dbesc_array($p);
                    if ($exists) {
                        $str = '';
                        foreach ($p as $k => $v) {
                            if ($str) {
                                $str .= ",";
                            }
                            $str .= " `" . $k . "` = '" . $v . "' ";
                        }
                        $r = dbq("update `photo` set " . $str . " where id = " . intval($exists[0]['id']));
                    } else {
                        $r = dbq("INSERT INTO photo (`" . implode("`, `", array_keys($p)) . "`) VALUES ('" . implode("', '", array_values($p)) . "')");
                    }
                }
            }
            if ($f['item']) {
                sync_items($channel, $f['item'], ['channel_address' => $original_channel, 'url' => $oldbase]);
            }
        }
    }
}