function remove_obsolete_hublocs() { logger('remove_obsolete_hublocs', LOGGER_DEBUG); // Get rid of any hublocs which are ours but aren't valid anymore - // e.g. they point to a different and perhaps transient URL that we aren't using. // I need to stress that this shouldn't happen. fix_system_urls() fixes hublocs // when it discovers the URL has changed. So it's unclear how we could end up // with URLs pointing to the old site name. But it happens. This may be an artifact // of an old bug or maybe a regression in some newer code. In any event, they // mess up communications and we have to take action if we find any. // First make sure we have any hublocs (at all) with this URL and sitekey. // We don't want to perform this operation while somebody is in the process // of renaming their hub or installing certs. $r = q("select hubloc_id from hubloc where hubloc_url = '%s' and hubloc_sitekey = '%s'", dbesc(z_root()), dbesc(get_config('system', 'pubkey'))); if (!$r || !count($r)) { return; } $channels = array(); // Good. We have at least one *valid* hubloc. // Do we have any invalid ones? $r = q("select hubloc_id from hubloc where hubloc_sitekey = '%s' and hubloc_url != '%s'", dbesc(get_config('system', 'pubkey')), dbesc(z_root())); $p = q("select hubloc_id from hubloc where hubloc_sitekey != '%s' and hubloc_url = '%s'", dbesc(get_config('system', 'pubkey')), dbesc(z_root())); if (is_array($r) && is_array($p)) { $r = array_merge($r, $p); } if (!$r) { return; } // We've got invalid hublocs. Get rid of them. logger('remove_obsolete_hublocs: removing ' . count($r) . ' hublocs.'); $interval = get_config('system', 'delivery_interval') !== false ? intval(get_config('system', 'delivery_interval')) : 2; foreach ($r as $rr) { q("update hubloc set hubloc_deleted = 1 where hubloc_id = %d", intval($rr['hubloc_id'])); $x = q("select channel_id from channel where channel_hash = '%s' limit 1", dbesc($rr['hubloc_hash'])); if ($x) { Zotlabs\Daemon\Master::Summon(array('Notifier', 'location', $x[0]['channel_id'])); if ($interval) { @time_sleep_until(microtime(true) + (double) $interval); } } } }
function drop_item($id, $interactive = true, $stage = DROPITEM_NORMAL, $force = false) { // locate item to be deleted $r = q("SELECT * FROM item WHERE id = %d LIMIT 1", intval($id)); if (!$r || intval($r[0]['item_deleted']) && $stage === DROPITEM_NORMAL) { if (!$interactive) { return 0; } notice(t('Item not found.') . EOL); goaway(z_root() . '/' . $_SESSION['return_url']); } $item = $r[0]; $linked_item = $item['resource_id'] ? true : false; $ok_to_delete = false; // system deletion if (!$interactive) { $ok_to_delete = true; } // owner deletion if (local_channel() && local_channel() == $item['uid']) { $ok_to_delete = true; } // sys owned item, requires site admin to delete $sys = get_sys_channel(); if (is_site_admin() && $sys['channel_id'] == $item['uid']) { $ok_to_delete = true; } // author deletion $observer = App::get_observer(); if ($observer && $observer['xchan_hash'] && $observer['xchan_hash'] === $item['author_xchan']) { $ok_to_delete = true; } if ($ok_to_delete) { // set the deleted flag immediately on this item just in case the // hook calls a remote process which loops. We'll delete it properly in a second. if ($linked_item && !$force) { $r = q("UPDATE item SET item_hidden = 1 WHERE id = %d", intval($item['id'])); } else { $r = q("UPDATE item SET item_deleted = 1 WHERE id = %d", intval($item['id'])); } $arr = array('item' => $item, 'interactive' => $interactive, 'stage' => $stage); call_hooks('drop_item', $arr); $notify_id = intval($item['id']); $items = q("select * from item where parent = %d and uid = %d", intval($item['id']), intval($item['uid'])); if ($items) { foreach ($items as $i) { delete_item_lowlevel($i, $stage, $force); } } else { delete_item_lowlevel($item, $stage, $force); } if (!$interactive) { return 1; } // send the notification upstream/downstream as the case may be // only send notifications to others if this is the owner's wall item. // This isn't optimal. We somehow need to pass to this function whether or not // to call the notifier, or we need to call the notifier from the calling function. // We'll rely on the undocumented behaviour that DROPITEM_PHASE1 is (hopefully) only // set if we know we're going to send delete notifications out to others. if (intval($item['item_wall']) && $stage != DROPITEM_PHASE2 || $stage == DROPITEM_PHASE1) { Zotlabs\Daemon\Master::Summon(array('Notifier', 'drop', $notify_id)); } goaway(z_root() . '/' . $_SESSION['return_url']); } else { if (!$interactive) { return 0; } notice(t('Permission denied.') . EOL); goaway(z_root() . '/' . $_SESSION['return_url']); } }
/** * @brief * * If somebody arrives at our site using a zid, add their xchan to our DB if we don't have it already. * And if they aren't already authenticated here, attempt reverse magic auth. * * * @hooks 'zid_init' * string 'zid' - their zid * string 'url' - the destination url */ function zid_init() { $tmp_str = get_my_address(); if (validate_email($tmp_str)) { Zotlabs\Daemon\Master::Summon(array('Gprobe', bin2hex($tmp_str))); $arr = array('zid' => $tmp_str, 'url' => App::$cmd); call_hooks('zid_init', $arr); if (!local_channel()) { $r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_connected desc limit 1", dbesc($tmp_str)); if ($r && remote_channel() && remote_channel() === $r[0]['hubloc_hash']) { return; } logger('zid_init: not authenticated. Invoking reverse magic-auth for ' . $tmp_str); // try to avoid recursion - but send them home to do a proper magic auth $query = App::$query_string; $query = str_replace(array('?zid=', '&zid='), array('?rzid=', '&rzid='), $query); $dest = '/' . urlencode($query); if ($r && $r[0]['hubloc_url'] != z_root() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) { goaway($r[0]['hubloc_url'] . '/magic' . '?f=&rev=1&dest=' . z_root() . $dest); } else { logger('zid_init: no hubloc found.'); } } } }
/** * @brief Send warnings every 3-5 days if cron is not running. */ function check_cron_broken() { $d = get_config('system', 'lastcron'); if (!$d || $d < datetime_convert('UTC', 'UTC', 'now - 4 hours')) { Zotlabs\Daemon\Master::Summon(array('Cron')); } $t = get_config('system', 'lastcroncheck'); if (!$t) { // never checked before. Start the timer. set_config('system', 'lastcroncheck', datetime_convert()); return; } if ($t > datetime_convert('UTC', 'UTC', 'now - 3 days')) { // Wait for 3 days before we do anything so as not to swamp the admin with messages return; } set_config('system', 'lastcroncheck', datetime_convert()); if ($d && $d > datetime_convert('UTC', 'UTC', 'now - 3 days')) { // Scheduled tasks have run successfully in the last 3 days. return; } $email_tpl = get_intltext_template("cron_bad_eml.tpl"); $email_msg = replace_macros($email_tpl, array('$sitename' => App::$config['system']['sitename'], '$siteurl' => z_root(), '$error' => t('Cron/Scheduled tasks not running.'), '$lastdate' => $d ? $d : t('never'))); $subject = email_header_encode(sprintf(t('[hubzilla] Cron tasks not running on %s'), App::get_hostname())); mail(App::$config['system']['admin_email'], $subject, $email_msg, 'From: Administrator' . '@' . App::get_hostname() . "\n" . 'Content-type: text/plain; charset=UTF-8' . "\n" . 'Content-transfer-encoding: 8bit'); return; }
function import_mail($channel, $mails, $sync = false) { if ($channel && $mails) { foreach ($mails as $mail) { if (array_key_exists('flags', $mail) && in_array('deleted', $mail['flags'])) { q("delete from mail where mid = '%s' and uid = %d limit 1", dbesc($mail['message_id']), intval($channel['channel_id'])); continue; } if (array_key_exists('flags', $mail) && in_array('recalled', $mail['flags'])) { q("update mail set mail_recalled = 1 where mid = '%s' and uid = %d limit 1", dbesc($mail['message_id']), intval($channel['channel_id'])); continue; } $m = get_mail_elements($mail); if (!$m) { continue; } $m['aid'] = $channel['channel_account_id']; $m['uid'] = $channel['channel_id']; $mail_id = mail_store($m); if ($sync && $mail_id) { Zotlabs\Daemon\Master::Summon(array('Notifier', 'single_mail', $mail_id)); } } } }
/** * @brief Process a message request. * * If a site receives a comment to a post but finds they have no parent to attach it with, they * may send a 'request' packet containing the message_id of the missing parent. This is the handler * for that packet. We will create a message_list array of the entire conversation starting with * the missing parent and invoke delivery to the sender of the packet. * * include/deliver.php (for local delivery) and mod/post.php (for web delivery) detect the existence of * this 'message_list' at the destination and split it into individual messages which are * processed/delivered in order. * * Called from mod/post.php * * @param array $data * @return array */ function zot_reply_message_request($data) { $ret = array('success' => false); if (!$data['message_id']) { $ret['message'] = 'no message_id'; logger('no message_id'); json_return_and_die($ret); } $sender = $data['sender']; $sender_hash = make_xchan_hash($sender['guid'], $sender['guid_sig']); /* * Find the local channel in charge of this post (the first and only recipient of the request packet) */ $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); $c = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_hash = '%s' limit 1", dbesc($recip_hash)); if (!$c) { logger('recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; json_return_and_die($ret); } /* * fetch the requested conversation */ $messages = zot_feed($c[0]['channel_id'], $sender_hash, array('message_id' => $data['message_id'])); if ($messages) { $env_recips = null; $r = q("select * from hubloc where hubloc_hash = '%s' and hubloc_error = 0 and hubloc_deleted = 0 \n\t\t\tgroup by hubloc_sitekey", dbesc($sender_hash)); if (!$r) { logger('no hubs'); json_return_and_die($ret); } $hubs = $r; $private = array_key_exists('flags', $messages[0]) && in_array('private', $messages[0]['flags']) ? true : false; if ($private) { $env_recips = array('guid' => $sender['guid'], 'guid_sig' => $sender['guid_sig'], 'hash' => $sender_hash); } $data_packet = json_encode(array('message_list' => $messages)); foreach ($hubs as $hub) { $hash = random_string(); /* * create a notify packet and drop the actual message packet in the queue for pickup */ $n = zot_build_packet($c[0], 'notify', $env_recips, $private ? $hub['hubloc_sitekey'] : null, $hash, array('message_id' => $data['message_id'])); queue_insert(array('hash' => $hash, 'account_id' => $c[0]['channel_account_id'], 'channel_id' => $c[0]['channel_id'], 'posturl' => $hub['hubloc_callback'], 'notify' => $n, 'msg' => $data_packet)); /* * invoke delivery to send out the notify packet */ Zotlabs\Daemon\Master::Summon(array('Deliver', $hash)); } } $ret['success'] = true; json_return_and_die($ret); }
function get() { $sort_type = 0; $o = ''; if (!local_channel()) { notice(t('Permission denied.') . EOL); return login(); } $channel = \App::get_channel(); $my_perms = get_channel_default_perms(local_channel()); $role = get_pconfig(local_channel(), 'system', 'permissions_role'); if ($role) { $x = get_role_perms($role); if ($x['perms_accept']) { $my_perms = $x['perms_accept']; } } $yes_no = array(t('No'), t('Yes')); if ($my_perms) { $o .= "<script>function connectDefaultShare() {\n\t\t\t\$('.abook-edit-me').each(function() {\n\t\t\t\tif(! \$(this).is(':disabled'))\n\t\t\t\t\t\$(this).prop('checked', false);\n\t\t\t});\n\n"; $perms = get_perms(); foreach ($perms as $p => $v) { if ($my_perms & $v[1]) { $o .= "\$('#me_id_perms_" . $p . "').prop('checked', true); \n"; } } $o .= " }\n</script>\n"; } if (argc() == 3) { $contact_id = intval(argv(1)); if (!$contact_id) { return; } $cmd = argv(2); $orig_record = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash\n\t\t\t\tWHERE abook_id = %d AND abook_channel = %d AND abook_self = 0 LIMIT 1", intval($contact_id), intval(local_channel())); if (!count($orig_record)) { notice(t('Could not access address book record.') . EOL); goaway(z_root() . '/connections'); } if ($cmd === 'update') { // pull feed and consume it, which should subscribe to the hub. \Zotlabs\Daemon\Master::Summon(array('Poller', $contact_id)); goaway(z_root() . '/connedit/' . $contact_id); } if ($cmd === 'refresh') { if ($orig_record[0]['xchan_network'] === 'zot') { if (!zot_refresh($orig_record[0], \App::get_channel())) { notice(t('Refresh failed - channel is currently unavailable.')); } } else { // if you are on a different network we'll force a refresh of the connection basic info Zotlabs\Daemon\Master::Summon(array('Notifier', 'permission_update', $contact_id)); } goaway(z_root() . '/connedit/' . $contact_id); } if ($cmd === 'block') { if (abook_toggle_flag($orig_record[0], ABOOK_FLAG_BLOCKED)) { $this->connedit_clone($a); } else { notice(t('Unable to set address book parameters.') . EOL); } goaway(z_root() . '/connedit/' . $contact_id); } if ($cmd === 'ignore') { if (abook_toggle_flag($orig_record[0], ABOOK_FLAG_IGNORED)) { $this->connedit_clone($a); } else { notice(t('Unable to set address book parameters.') . EOL); } goaway(z_root() . '/connedit/' . $contact_id); } if ($cmd === 'archive') { if (abook_toggle_flag($orig_record[0], ABOOK_FLAG_ARCHIVED)) { $this->connedit_clone($a); } else { notice(t('Unable to set address book parameters.') . EOL); } goaway(z_root() . '/connedit/' . $contact_id); } if ($cmd === 'hide') { if (abook_toggle_flag($orig_record[0], ABOOK_FLAG_HIDDEN)) { $this->connedit_clone($a); } else { notice(t('Unable to set address book parameters.') . EOL); } goaway(z_root() . '/connedit/' . $contact_id); } // We'll prevent somebody from unapproving an already approved contact. // Though maybe somebody will want this eventually (??) if ($cmd === 'approve') { if (intval($orig_record[0]['abook_pending'])) { if (abook_toggle_flag($orig_record[0], ABOOK_FLAG_PENDING)) { $this->connedit_clone($a); } else { notice(t('Unable to set address book parameters.') . EOL); } } goaway(z_root() . '/connedit/' . $contact_id); } if ($cmd === 'drop') { // FIXME // We need to send either a purge or a refresh packet to the other side (the channel being unfriended). // The issue is that the abook DB record _may_ get destroyed when we call contact_remove. As the notifier runs // in the background there could be a race condition preventing this packet from being sent in all cases. // PLACEHOLDER contact_remove(local_channel(), $orig_record[0]['abook_id']); build_sync_packet(0, array('abook' => array(array('abook_xchan' => $orig_record[0]['abook_xchan'], 'entry_deleted' => true)))); info(t('Connection has been removed.') . EOL); if (x($_SESSION, 'return_url')) { goaway(z_root() . '/' . $_SESSION['return_url']); } goaway(z_root() . '/contacts'); } } if (\App::$poi) { $contact_id = \App::$poi['abook_id']; $contact = \App::$poi; $tools = array('view' => array('label' => t('View Profile'), 'url' => chanlink_cid($contact['abook_id']), 'sel' => '', 'title' => sprintf(t('View %s\'s profile'), $contact['xchan_name'])), 'refresh' => array('label' => t('Refresh Permissions'), 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/refresh', 'sel' => '', 'title' => t('Fetch updated permissions')), 'recent' => array('label' => t('Recent Activity'), 'url' => z_root() . '/network/?f=&cid=' . $contact['abook_id'], 'sel' => '', 'title' => t('View recent posts and comments')), 'block' => array('label' => intval($contact['abook_blocked']) ? t('Unblock') : t('Block'), 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/block', 'sel' => intval($contact['abook_blocked']) ? 'active' : '', 'title' => t('Block (or Unblock) all communications with this connection'), 'info' => intval($contact['abook_blocked']) ? t('This connection is blocked!') : ''), 'ignore' => array('label' => intval($contact['abook_ignored']) ? t('Unignore') : t('Ignore'), 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/ignore', 'sel' => intval($contact['abook_ignored']) ? 'active' : '', 'title' => t('Ignore (or Unignore) all inbound communications from this connection'), 'info' => intval($contact['abook_ignored']) ? t('This connection is ignored!') : ''), 'archive' => array('label' => intval($contact['abook_archived']) ? t('Unarchive') : t('Archive'), 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/archive', 'sel' => intval($contact['abook_archived']) ? 'active' : '', 'title' => t('Archive (or Unarchive) this connection - mark channel dead but keep content'), 'info' => intval($contact['abook_archived']) ? t('This connection is archived!') : ''), 'hide' => array('label' => intval($contact['abook_hidden']) ? t('Unhide') : t('Hide'), 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/hide', 'sel' => intval($contact['abook_hidden']) ? 'active' : '', 'title' => t('Hide or Unhide this connection from your other connections'), 'info' => intval($contact['abook_hidden']) ? t('This connection is hidden!') : ''), 'delete' => array('label' => t('Delete'), 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/drop', 'sel' => '', 'title' => t('Delete this connection'))); $self = false; if (intval($contact['abook_self'])) { $self = true; } $tpl = get_markup_template("abook_edit.tpl"); if (feature_enabled(local_channel(), 'affinity')) { $labels = array(t('Me'), t('Family'), t('Friends'), t('Acquaintances'), t('All')); call_hooks('affinity_labels', $labels); $label_str = ''; if ($labels) { foreach ($labels as $l) { if ($label_str) { $label_str .= ", '|'"; $label_str .= ", '" . $l . "'"; } else { $label_str .= "'" . $l . "'"; } } } $slider_tpl = get_markup_template('contact_slider.tpl'); $slide = replace_macros($slider_tpl, array('$min' => 1, '$val' => $contact['abook_closeness'] ? $contact['abook_closeness'] : 99, '$labels' => $label_str)); } $rating_val = 0; $rating_text = ''; $xl = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", dbesc($channel['channel_hash']), dbesc($contact['xchan_hash'])); if ($xl) { $rating_val = intval($xl[0]['xlink_rating']); $rating_text = $xl[0]['xlink_rating_text']; } $poco_rating = get_config('system', 'poco_rating_enable'); // if unset default to enabled if ($poco_rating === false) { $poco_rating = true; } if ($poco_rating) { $rating = replace_macros(get_markup_template('rating_slider.tpl'), array('$min' => -10, '$val' => $rating_val)); } else { $rating = false; } $perms = array(); $channel = \App::get_channel(); $global_perms = get_perms(); $existing = get_all_perms(local_channel(), $contact['abook_xchan']); $unapproved = array('pending', t('Approve this connection'), '', t('Accept connection to allow communication'), array(t('No'), 'Yes')); $multiprofs = feature_enabled(local_channel(), 'multi_profiles') ? true : false; if ($slide && !$multiprofs) { $affinity = t('Set Affinity'); } if (!$slide && $multiprofs) { $affinity = t('Set Profile'); } if ($slide && $multiprofs) { $affinity = t('Set Affinity & Profile'); } foreach ($global_perms as $k => $v) { $thisperm = $contact['abook_my_perms'] & $v[1] ? "1" : ''; $checkinherited = $channel[$v[0]] && $channel[$v[0]] != PERMS_SPECIFIC ? "1" : ''; // For auto permissions (when $self is true) we don't want to look at existing // permissions because they are enabled for the channel owner if (!$self && $existing[$k]) { $thisperm = "1"; } $perms[] = array('perms_' . $k, $v[3], $contact['abook_their_perms'] & $v[1] ? "1" : "", $thisperm, $v[1], $channel[$v[0]] == PERMS_SPECIFIC ? '' : '1', $v[4], $checkinherited); } $locstr = ''; $locs = q("select hubloc_addr as location from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s'\n\t\t\t\tand hubloc_deleted = 0 and site_dead = 0", dbesc($contact['xchan_hash'])); if ($locs) { foreach ($locs as $l) { if (!$l['location']) { continue; } if (strpos($locstr, $l['location']) !== false) { continue; } if (strlen($locstr)) { $locstr .= ', '; } $locstr .= $l['location']; } } else { $locstr = t('none'); } $o .= replace_macros($tpl, array('$header' => $self ? t('Connection Default Permissions') : sprintf(t('Connection: %s'), $contact['xchan_name']), '$autoperms' => array('autoperms', t('Apply these permissions automatically'), get_pconfig(local_channel(), 'system', 'autoperms') ? 1 : 0, t('Connection requests will be approved without your interaction'), $yes_no), '$addr' => $contact['xchan_addr'], '$addr_text' => t('This connection\'s primary address is'), '$loc_text' => t('Available locations:'), '$locstr' => $locstr, '$notself' => $self ? '' : '1', '$self' => $self ? '1' : '', '$autolbl' => t('The permissions indicated on this page will be applied to all new connections.'), '$tools_label' => t('Connection Tools'), '$tools' => $self ? '' : $tools, '$lbl_slider' => t('Slide to adjust your degree of friendship'), '$lbl_rating' => t('Rating'), '$lbl_rating_label' => t('Slide to adjust your rating'), '$lbl_rating_txt' => t('Optionally explain your rating'), '$connfilter' => feature_enabled(local_channel(), 'connfilter'), '$connfilter_label' => t('Custom Filter'), '$incl' => array('abook_incl', t('Only import posts with this text'), $contact['abook_incl'], t('words one per line or #tags or /patterns/ or lang=xx, leave blank to import all posts')), '$excl' => array('abook_excl', t('Do not import posts with this text'), $contact['abook_excl'], t('words one per line or #tags or /patterns/ or lang=xx, leave blank to import all posts')), '$rating_text' => array('rating_text', t('Optionally explain your rating'), $rating_text, ''), '$rating_info' => t('This information is public!'), '$rating' => $rating, '$rating_val' => $rating_val, '$slide' => $slide, '$affinity' => $affinity, '$pending_label' => t('Connection Pending Approval'), '$is_pending' => intval($contact['abook_pending']) ? 1 : '', '$unapproved' => $unapproved, '$inherited' => t('inherited'), '$submit' => t('Submit'), '$lbl_vis2' => sprintf(t('Please choose the profile you would like to display to %s when viewing your profile securely.'), $contact['xchan_name']), '$close' => $contact['abook_closeness'], '$them' => t('Their Settings'), '$me' => t('My Settings'), '$perms' => $perms, '$permlbl' => t('Individual Permissions'), '$permnote' => t('Some permissions may be inherited from your channel\'s <a href="settings"><strong>privacy settings</strong></a>, which have higher priority than individual settings. You can <strong>not</strong> change those settings here.'), '$permnote_self' => t('Some permissions may be inherited from your channel\'s <a href="settings"><strong>privacy settings</strong></a>, which have higher priority than individual settings. You can change those settings here but they wont have any impact unless the inherited setting changes.'), '$lastupdtext' => t('Last update:'), '$last_update' => relative_date($contact['abook_connected']), '$profile_select' => contact_profile_assign($contact['abook_profile']), '$multiprofs' => $multiprofs, '$contact_id' => $contact['abook_id'], '$name' => $contact['xchan_name'])); $arr = array('contact' => $contact, 'output' => $o); call_hooks('contact_edit', $arr); return $arr['output']; } }
function randpost_fetch(&$a, &$b) { $fort_server = get_config('fortunate', 'server'); if (!$fort_server) { return; } $r = q("select * from pconfig where cat = 'randpost' and k = 'enable'"); if ($r) { foreach ($r as $rr) { if (!$rr['v']) { continue; } // logger('randpost'); // cronhooks run every 10-15 minutes typically // try to keep from posting frequently. $test = mt_rand(0, 100); if ($test == 25) { $c = q("select * from channel where channel_id = %d limit 1", intval($rr['uid'])); if (!$c) { continue; } $mention = ''; require_once 'include/html2bbcode.php'; $s = z_fetch_url('http://' . $fort_server . '/cookie.php?numlines=2&equal=1&rand=' . mt_rand()); if (!$s['success']) { continue; } $x = array(); $x['uid'] = $c[0]['channel_id']; $x['aid'] = $c[0]['channel_account_id']; $x['mid'] = $x['parent_mid'] = item_message_id(); $x['author_xchan'] = $x['owner_xchan'] = $c[0]['channel_hash']; $x['item_thread_top'] = 1; $x['item_origin'] = 1; $x['item_verified'] = 1; $x['item_wall'] = 1; // if it might be a quote make it a quote if (strpos($s['body'], '--')) { $x['body'] = $mention . '[quote]' . html2bbcode($s['body']) . '[/quote]'; } else { $x['body'] = $mention . html2bbcode($s['body']); } $x['sig'] = base64url_encode(rsa_sign($x['body'], $c[0]['channel_prvkey'])); $post = item_store($x); $post_id = $post['item_id']; $x['id'] = $post_id; call_hooks('post_local_end', $x); Zotlabs\Daemon\Master::Summon(array('Notifier', 'wall-new', $post_id)); } } } }
function openclipatar_content(&$a) { if (!local_channel()) { return; } $o = ''; if (argc() == 3 && argv(1) == 'use') { $id = argv(2); $chan = App::get_channel(); $x = z_fetch_url('https://openclipart.org/image/250px/svg_to_png/' . $id . '/' . $id . '.png', true); if ($x['success']) { $imagedata = $x['body']; } $ph = photo_factory($imagedata, 'image/png'); if (!$ph->is_valid()) { return t('Unknown error. Please try again later.'); } // create a unique resource_id $hash = photo_new_resource(); $width = $ph->getWidth(); $height = $ph->getHeight(); // save an original or "scale 0" image $p = array('aid' => get_account_id(), 'uid' => local_channel(), 'resource_id' => $hash, 'filename' => $id . '.png', 'album' => t('Profile Photos'), 'imgscale' => 0); $r = $ph->save($p); if ($r) { if (($width > 1024 || $height > 1024) && !$errors) { $ph->scaleImage(1024); } $p['imgscale'] = 1; $r1 = $ph->save($p); if (($width > 640 || $height > 640) && !$errors) { $ph->scaleImage(640); } $p['imgscale'] = 2; $r2 = $ph->save($p); if (($width > 320 || $height > 320) && !$errors) { $ph->scaleImage(320); } $p['imgscale'] = 3; $r3 = $ph->save($p); // ensure squareness at first, subsequent scales keep ratio $ph->scaleImageSquare(175); $p['imgscale'] = 4; $r = $ph->save($p); if ($r === false) { $photo_failure = true; } $ph->scaleImage(80); $p['imgscale'] = 5; $r = $ph->save($p); if ($r === false) { $photo_failure = true; } $ph->scaleImage(48); $p['imgscale'] = 6; $r = $ph->save($p); if ($r === false) { $photo_failure = true; } } $is_default_profile = 1; if ($_REQUEST['profile']) { $r = q("select id, is_default from profile where id = %d and uid = %d limit 1", intval($_REQUEST['profile']), intval(local_channel())); if ($r && !intval($r[0]['is_default'])) { $is_default_profile = 0; } } if ($is_default_profile) { // unset any existing profile photos $r = q("UPDATE photo SET photo_usage = %d WHERE photo_usage = %d AND uid = %d", intval(PHOTO_NORMAL), intval(PHOTO_PROFILE), intval(local_channel())); // set all sizes of this one as profile photos $r = q("UPDATE photo SET photo_usage = %d WHERE uid = %d AND resource_id = '%s'", intval(PHOTO_PROFILE), intval(local_channel()), dbesc($hash)); require_once 'include/photos.php'; profile_photo_set_profile_perms(local_channel()); //Reset default profile photo permissions to public // only the default needs reload since it uses canonical url -- despite the slightly ambiguous message, left it so as to re-use translations info(t('Shift-reload the page or clear browser cache if the new photo does not display immediately.') . EOL); } else { // not the default profile, set the path in the correct entry in the profile DB $r = q("update profile set photo = '%s', thumb = '%s' where id = %d and uid = %d", dbesc(z_root() . '/photo/' . $hash . '-4'), dbesc(z_root() . '/photo/' . $hash . '-5'), intval($_REQUEST['profile']), intval(local_channel())); info(t('Profile photo updated successfully.') . EOL); } // set a new photo_date on our xchan so that we can tell everybody to update their cached copy $r = q("UPDATE xchan set xchan_photo_date = '%s' where xchan_hash = '%s'", dbesc(datetime_convert()), dbesc($chan['xchan_hash'])); // Similarly, tell the nav bar to bypass the cache and update the avater image. $_SESSION['reload_avatar'] = true; // tell everybody Zotlabs\Daemon\Master::Summon(array('Directory', local_channel())); $returnafter = get_config('openclipatar', 'returnafter'); $returnafter_urls = array(0 => z_root() . '/profile/' . ($_REQUEST['profile'] ? $_REQUEST['profile'] . '/view' : $chan['channel_address']), 1 => z_root() . '/profiles/' . ($_REQUEST['profile'] ? $_REQUEST['profile'] : App::$profile_uid), 2 => z_root() . '/profiles'); goaway($returnafter_urls[$returnafter]); } else { //invoked as module, we place in content pane the same as we would for the end of the profile photo page. Also handles json for endless scroll for either invokation. openclipatar_profile_photo_content_end($a, $o); } return $o; }
/** @file */ function profile_activity($changed, $value) { if (!local_channel() || !is_array($changed) || !count($changed)) { return; } if (!get_pconfig(local_channel(), 'system', 'post_profilechange')) { return; } require_once 'include/items.php'; $self = App::get_channel(); if (!count($self)) { return; } $arr = array(); $arr['mid'] = $arr['parent_mid'] = item_message_id(); $arr['uid'] = local_channel(); $arr['aid'] = $self['channel_account_id']; $arr['owner_xchan'] = $arr['author_xchan'] = $self['xchan_hash']; $arr['item_wall'] = 1; $arr['item_origin'] = 1; $arr['item_thread_top'] = 1; $arr['verb'] = ACTIVITY_UPDATE; $arr['obj_type'] = ACTIVITY_OBJ_PROFILE; $arr['plink'] = z_root() . '/channel/' . $self['channel_address'] . '/?f=&mid=' . $arr['mid']; $A = '[url=' . z_root() . '/channel/' . $self['channel_address'] . ']' . $self['channel_name'] . '[/url]'; $changes = ''; $t = count($changed); $z = 0; foreach ($changed as $ch) { if (strlen($changes)) { if ($z == $t - 1) { $changes .= t(' and '); } else { $changes .= ', '; } } $z++; $changes .= $ch; } $prof = '[url=' . z_root() . '/profile/' . $self['channel_address'] . ']' . t('public profile') . '[/url]'; if ($t == 1 && strlen($value)) { // if it's a url, the HTML quotes will mess it up, so link it and don't try and zidify it because we don't know what it points to. $value = preg_replace_callback("/([^\\]\\='" . '"' . "]|^|\\#\\^)(https?\\:\\/\\/[a-zA-Z0-9\\:\\/\\-\\?\\&\\;\\.\\=\\@\\_\\~\\#\\%\$\\!\\+\\,]+)/ism", 'red_zrl_callback', $value); // take out the bookmark indicator if (substr($value, 0, 2) === '#^') { $value = str_replace('#^', '', $value); } $message = sprintf(t('%1$s changed %2$s to “%3$s”'), $A, $changes, $value); $message .= "\n\n" . sprintf(t('Visit %1$s\'s %2$s'), $A, $prof); } else { $message = sprintf(t('%1$s has an updated %2$s, changing %3$s.'), $A, $prof, $changes); } $arr['body'] = $message; $links = array(); $links[] = array('rel' => 'alternate', 'type' => 'text/html', 'href' => z_root() . '/profile/' . $self['channel_address']); $links[] = array('rel' => 'photo', 'type' => $self['xchan_photo_mimetype'], 'href' => $self['xchan_photo_l']); $arr['object'] = json_encode(array('type' => ACTIVITY_OBJ_PROFILE, 'title' => $self['channel_name'], 'id' => $self['xchan_url'] . '/' . $self['xchan_hash'], 'link' => $links)); $arr['allow_cid'] = $self['channel_allow_cid']; $arr['allow_gid'] = $self['channel_allow_gid']; $arr['deny_cid'] = $self['channel_deny_cid']; $arr['deny_gid'] = $self['channel_deny_gid']; $res = item_store($arr); $i = $res['item_id']; if ($i) { // FIXME - limit delivery in notifier.php to those specificed in the perms argument Zotlabs\Daemon\Master::Summon(array('Notifier', 'activity', $i, 'PERMS_R_PROFILE')); } }
/** * @brief Create a new move in the game by generating a child item for the game post * * @param $observer Authenticated observer (remote or local channel) viewing the page * @param $newPosFEN New board position in FEN-format * @param $g Game post item table record with all the game information * @return array Success status and array of new post data */ function chess_make_move($observer, $newPosFEN, $g) { $resource_type = 'chess'; $resource_id = $g['resource_id']; $mid = item_message_id(); $arr = array(); // Initialize the array of parameters for the post $objtype = ACTIVITY_OBJ_CHESSGAME; $object = json_encode(array('id' => z_root() . '/chess/game/' . $resource_id, 'position' => $newPosFEN, 'version' => chess_get_version())); $item_hidden = 0; // TODO: Allow form creator to send post to ACL about new game automatically $r = q("select channel_address from channel where channel_id = %d limit 1", intval($g['uid'])); $channel_address = ''; if ($r) { $channel_address = $r[0]['channel_address']; } $arr['aid'] = $g['aid']; $arr['uid'] = $g['uid']; $arr['mid'] = $mid; $arr['parent_mid'] = $g['mid']; $arr['item_hidden'] = $item_hidden; $arr['resource_type'] = $resource_type; $arr['resource_id'] = $resource_id; // Game ID $arr['owner_xchan'] = $g['owner_xchan']; // Tracks the owner of the game $arr['author_xchan'] = $observer['xchan_hash']; // Denotes which player made this move // Store info about the type of chess item using the "title" field // Other types include 'move' for children items but may in the future include // additional types that will determine how the "object" field is interpreted $arr['title'] = 'move'; $arr['item_wall'] = 1; $arr['item_origin'] = 1; $arr['item_thread_top'] = 0; $arr['item_private'] = 1; $arr['verb'] = ACTIVITY_POST; $arr['obj_type'] = $objtype; $arr['obj'] = $object; $arr['body'] = 'New position (FEN format): ' . $newPosFEN; $post = item_store($arr); $item_id = $post['item_id']; if ($item_id) { Zotlabs\Daemon\Master::Summon(['Notifier', 'activity', $item_id]); return array('item' => $arr, 'status' => true); } else { return array('item' => null, 'status' => false); } }
function channel_remove($channel_id, $local = true, $unset_session = false) { if (!$channel_id) { return; } logger('Removing channel: ' . $channel_id); logger('channel_remove: local only: ' . intval($local)); $r = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); if (!$r) { logger('channel_remove: channel not found: ' . $channel_id); return; } $channel = $r[0]; call_hooks('channel_remove', $r[0]); if (!$local) { $r = q("update channel set channel_deleted = '%s', channel_removed = 1, channel_r_stream = 0, channel_r_profile = 0,\n\t\t\tchannel_r_photos = 0, channel_r_abook = 0, channel_w_stream = 0, channel_w_wall = 0, channel_w_tagwall = 0,\n\t\t\tchannel_w_comment = 0, channel_w_mail = 0, channel_w_photos = 0, channel_w_chat = 0, channel_a_delegate = 0,\n\t\t\tchannel_r_storage = 0, channel_w_storage = 0, channel_r_pages = 0, channel_w_pages = 0, channel_a_republish = 0 \n\t\t\twhere channel_id = %d", dbesc(datetime_convert()), intval($channel_id)); logger('deleting hublocs', LOGGER_DEBUG); $r = q("update hubloc set hubloc_deleted = 1 where hubloc_hash = '%s'", dbesc($channel['channel_hash'])); $r = q("update xchan set xchan_deleted = 1 where xchan_hash = '%s'", dbesc($channel['channel_hash'])); Zotlabs\Daemon\Master::Summon(array('Notifier', 'purge_all', $channel_id)); } $r = q("select * from iconfig left join item on item.id = iconfig.iid\n\t\twhere item.uid = %d", intval($channel_id)); if ($r) { foreach ($r as $rr) { q("delete from iconfig where iid = %d", intval($rr['iid'])); } } q("DELETE FROM `groups` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `group_member` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `event` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `item` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `mail` WHERE `channel_id` = %d", intval($channel_id)); q("DELETE FROM `notify` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `photo` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `attach` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `profile` WHERE `uid` = %d", intval($channel_id)); q("DELETE FROM `pconfig` WHERE `uid` = %d", intval($channel_id)); // @FIXME At this stage we need to remove the file resources located under /store/$nickname q("delete from abook where abook_xchan = '%s' and abook_self = 1 ", dbesc($channel['channel_hash'])); $r = q("update channel set channel_deleted = '%s', channel_removed = 1 where channel_id = %d", dbesc(datetime_convert()), intval($channel_id)); // if this was the default channel, set another one as default if (App::$account['account_default_channel'] == $channel_id) { $r = q("select channel_id from channel where channel_account_id = %d and channel_removed = 0 limit 1", intval(App::$account['account_id']), intval(PAGE_REMOVED)); if ($r) { $rr = q("update account set account_default_channel = %d where account_id = %d", intval($r[0]['channel_id']), intval(App::$account['account_id'])); logger("Default channel deleted, changing default to channel_id " . $r[0]['channel_id']); } else { $rr = q("update account set account_default_channel = 0 where account_id = %d", intval(App::$account['account_id'])); } } logger('deleting hublocs', LOGGER_DEBUG); $r = q("update hubloc set hubloc_deleted = 1 where hubloc_hash = '%s' and hubloc_url = '%s' ", dbesc($channel['channel_hash']), dbesc(z_root())); // Do we have any valid hublocs remaining? $hublocs = 0; $r = q("select hubloc_id from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0", dbesc($channel['channel_hash'])); if ($r) { $hublocs = count($r); } if (!$hublocs) { $r = q("update xchan set xchan_deleted = 1 where xchan_hash = '%s' ", dbesc($channel['channel_hash'])); } //remove from file system $r = q("select channel_address from channel where channel_id = %d limit 1", intval($channel_id)); if ($r) { $channel_address = $r[0]['channel_address']; } if ($channel_address !== '') { $f = 'store/' . $channel_address . '/'; logger('delete ' . $f); if (is_dir($f)) { @rrmdir($f); } } Zotlabs\Daemon\Master::Summon(array('Directory', $channel_id)); if ($channel_id == local_channel() && $unset_session) { App::$session->nuke(); goaway(z_root()); } }
function extcron_init(&$a) { Zotlabs\Daemon\Master::Summon(array('Cron')); killme(); }
/** * @brief * * @param array $channel * @param array $observer * @param array $args * @return array */ function photo_upload($channel, $observer, $args) { $ret = array('success' => false); $channel_id = $channel['channel_id']; $account_id = $channel['channel_account_id']; if (!perm_is_allowed($channel_id, $observer['xchan_hash'], 'write_storage')) { $ret['message'] = t('Permission denied.'); return $ret; } // call_hooks('photo_upload_begin', $args); /* * Determine the album to use */ $album = $args['album']; if (intval($args['visible']) || $args['visible'] === 'true') { $visible = 1; } else { $visible = 0; } $deliver = true; if (array_key_exists('deliver', $args)) { $deliver = intval($args['deliver']); } // Set to default channel permissions. If the parent directory (album) has permissions set, // use those instead. If we have specific permissions supplied, they take precedence over // all other settings. 'allow_cid' being passed from an external source takes priority over channel settings. // ...messy... needs re-factoring once the photos/files integration stabilises $acl = new Zotlabs\Access\AccessList($channel); if (array_key_exists('directory', $args) && $args['directory']) { $acl->set($args['directory']); } if (array_key_exists('allow_cid', $args)) { $acl->set($args); } if (array_key_exists('group_allow', $args) || array_key_exists('contact_allow', $args) || array_key_exists('group_deny', $args) || array_key_exists('contact_deny', $args)) { $acl->set_from_array($args); } $ac = $acl->get(); $os_storage = 0; if ($args['os_path'] && $args['getimagesize']) { $imagedata = @file_get_contents($args['os_path']); $filename = $args['filename']; $filesize = strlen($imagedata); // this is going to be deleted if it exists $src = '/tmp/deletemenow'; $type = $args['getimagesize']['mime']; $os_storage = 1; } elseif ($args['data'] || $args['content']) { // allow an import from a binary string representing the image. // This bypasses the upload step and max size limit checking $imagedata = $args['content'] ? $args['content'] : $args['data']; $filename = $args['filename']; $filesize = strlen($imagedata); // this is going to be deleted if it exists $src = '/tmp/deletemenow'; $type = $args['mimetype'] ? $args['mimetype'] : $args['type']; } else { $f = array('src' => '', 'filename' => '', 'filesize' => 0, 'type' => ''); // call_hooks('photo_upload_file',$f); if (x($f, 'src') && x($f, 'filesize')) { $src = $f['src']; $filename = $f['filename']; $filesize = $f['filesize']; $type = $f['type']; } else { $src = $_FILES['userfile']['tmp_name']; $filename = basename($_FILES['userfile']['name']); $filesize = intval($_FILES['userfile']['size']); $type = $_FILES['userfile']['type']; } if (!$type) { $type = guess_image_type($filename); } logger('photo_upload: received file: ' . $filename . ' as ' . $src . ' (' . $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG); $maximagesize = get_config('system', 'maximagesize'); if ($maximagesize && $filesize > $maximagesize) { $ret['message'] = sprintf(t('Image exceeds website size limit of %lu bytes'), $maximagesize); @unlink($src); call_hooks('photo_upload_end', $ret); return $ret; } if (!$filesize) { $ret['message'] = t('Image file is empty.'); @unlink($src); call_hooks('photo_post_end', $ret); return $ret; } logger('photo_upload: loading the contents of ' . $src, LOGGER_DEBUG); $imagedata = @file_get_contents($src); } $r = q("select sum(filesize) as total from photo where aid = %d and imgscale = 0 ", intval($account_id)); $limit = engr_units_to_bytes(service_class_fetch($channel_id, 'photo_upload_limit')); if ($r && $limit !== false && $r[0]['total'] + strlen($imagedata) > $limit) { $ret['message'] = upgrade_message(); @unlink($src); call_hooks('photo_post_end', $ret); return $ret; } $ph = photo_factory($imagedata, $type); if (!$ph->is_valid()) { $ret['message'] = t('Unable to process image'); logger('photo_upload: unable to process image'); @unlink($src); call_hooks('photo_upload_end', $ret); return $ret; } $exif = $ph->orient($args['os_path'] ? $args['os_path'] : $src); @unlink($src); $max_length = get_config('system', 'max_image_length'); if (!$max_length) { $max_length = MAX_IMAGE_LENGTH; } if ($max_length > 0) { $ph->scaleImage($max_length); } $width = $ph->getWidth(); $height = $ph->getHeight(); $smallest = 0; $photo_hash = $args['resource_id'] ? $args['resource_id'] : photo_new_resource(); $visitor = ''; if ($channel['channel_hash'] !== $observer['xchan_hash']) { $visitor = $observer['xchan_hash']; } $errors = false; $p = array('aid' => $account_id, 'uid' => $channel_id, 'xchan' => $visitor, 'resource_id' => $photo_hash, 'filename' => $filename, 'album' => $album, 'imgscale' => 0, 'photo_usage' => PHOTO_NORMAL, 'allow_cid' => $ac['allow_cid'], 'allow_gid' => $ac['allow_gid'], 'deny_cid' => $ac['deny_cid'], 'deny_gid' => $ac['deny_gid'], 'os_storage' => $os_storage, 'os_path' => $args['os_path']); if ($args['created']) { $p['created'] = $args['created']; } if ($args['edited']) { $p['edited'] = $args['edited']; } if ($args['title']) { $p['title'] = $args['title']; } if ($args['description']) { $p['description'] = $args['description']; } $link = array(); $r0 = $ph->save($p); $link[0] = array('rel' => 'alternate', 'type' => 'text/html', 'href' => z_root() . '/photo/' . $photo_hash . '-0.' . $ph->getExt(), 'width' => $ph->getWidth(), 'height' => $ph->getHeight()); if (!$r0) { $errors = true; } unset($p['os_storage']); unset($p['os_path']); if (($width > 1024 || $height > 1024) && !$errors) { $ph->scaleImage(1024); } $p['imgscale'] = 1; $r1 = $ph->save($p); $link[1] = array('rel' => 'alternate', 'type' => 'text/html', 'href' => z_root() . '/photo/' . $photo_hash . '-1.' . $ph->getExt(), 'width' => $ph->getWidth(), 'height' => $ph->getHeight()); if (!$r1) { $errors = true; } if (($width > 640 || $height > 640) && !$errors) { $ph->scaleImage(640); } $p['imgscale'] = 2; $r2 = $ph->save($p); $link[2] = array('rel' => 'alternate', 'type' => 'text/html', 'href' => z_root() . '/photo/' . $photo_hash . '-2.' . $ph->getExt(), 'width' => $ph->getWidth(), 'height' => $ph->getHeight()); if (!$r2) { $errors = true; } if (($width > 320 || $height > 320) && !$errors) { $ph->scaleImage(320); } $p['imgscale'] = 3; $r3 = $ph->save($p); $link[3] = array('rel' => 'alternate', 'type' => 'text/html', 'href' => z_root() . '/photo/' . $photo_hash . '-3.' . $ph->getExt(), 'width' => $ph->getWidth(), 'height' => $ph->getHeight()); if (!$r3) { $errors = true; } if ($errors) { q("delete from photo where resource_id = '%s' and uid = %d", dbesc($photo_hash), intval($channel_id)); $ret['message'] = t('Photo storage failed.'); logger('photo_upload: photo store failed.'); call_hooks('photo_upload_end', $ret); return $ret; } $item_hidden = $visible ? 0 : 1; $lat = $lon = null; if ($exif && $exif['GPS']) { if (feature_enabled($channel_id, 'photo_location')) { $lat = getGps($exif['GPS']['GPSLatitude'], $exif['GPS']['GPSLatitudeRef']); $lon = getGps($exif['GPS']['GPSLongitude'], $exif['GPS']['GPSLongitudeRef']); } } $title = $args['description'] ? $args['description'] : $args['filename']; $large_photos = feature_enabled($channel['channel_id'], 'large_photos'); linkify_tags($a, $args['body'], $channel_id); if ($large_photos) { $scale = 1; $width = $link[1]['width']; $height = $link[1]['height']; $tag = $r1 ? '[zmg=' . $width . 'x' . $height . ']' : '[zmg]'; } else { $scale = 2; $width = $link[2]['width']; $height = $link[2]['height']; $tag = $r2 ? '[zmg=' . $width . 'x' . $height . ']' : '[zmg]'; } $author_link = '[zrl=' . z_root() . '/channel/' . $channel['channel_address'] . ']' . $channel['channel_name'] . '[/zrl]'; $photo_link = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash . ']' . t('a new photo') . '[/zrl]'; $album_link = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/album/' . bin2hex($album) . ']' . (strlen($album) ? $album : '/') . '[/zrl]'; $activity_format = sprintf(t('%1$s posted %2$s to %3$s', 'photo_upload'), $author_link, $photo_link, $album_link); $summary = ($args['body'] ? $args['body'] : '') . '[footer]' . $activity_format . '[/footer]'; $obj_body = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash . ']' . $tag . z_root() . "/photo/{$photo_hash}-{$scale}." . $ph->getExt() . '[/zmg]' . '[/zrl]'; // Create item object $object = array('type' => ACTIVITY_OBJ_PHOTO, 'title' => $title, 'created' => $p['created'], 'edited' => $p['edited'], 'id' => z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash, 'link' => $link, 'body' => $obj_body); $target = array('type' => ACTIVITY_OBJ_ALBUM, 'title' => $album ? $album : '/', 'id' => z_root() . '/photos/' . $channel['channel_address'] . '/album/' . bin2hex($album)); // Create item container if ($args['item']) { foreach ($args['item'] as $i) { $item = get_item_elements($i); $force = false; if ($item['mid'] === $item['parent_mid']) { $item['body'] = $summary; $item['obj_type'] = ACTIVITY_OBJ_PHOTO; $item['obj'] = json_encode($object); $item['tgt_type'] = ACTIVITY_OBJ_ALBUM; $item['target'] = json_encode($target); if ($item['author_xchan'] === $channel['channel_hash']) { $item['sig'] = base64url_encode(rsa_sign($item['body'], $channel['channel_prvkey'])); $item['item_verified'] = 1; } else { $item['sig'] = ''; } $force = true; } $r = q("select id, edited from item where mid = '%s' and uid = %d limit 1", dbesc($item['mid']), intval($channel['channel_id'])); if ($r) { if ($item['edited'] > $r[0]['edited'] || $force) { $item['id'] = $r[0]['id']; $item['uid'] = $channel['channel_id']; item_store_update($item, false, $deliver); continue; } } else { $item['aid'] = $channel['channel_account_id']; $item['uid'] = $channel['channel_id']; $item_result = item_store($item, false, $deliver); } } } else { $mid = item_message_id(); $arr = array(); if ($lat && $lon) { $arr['coord'] = $lat . ' ' . $lon; } $arr['aid'] = $account_id; $arr['uid'] = $channel_id; $arr['mid'] = $mid; $arr['parent_mid'] = $mid; $arr['item_hidden'] = $item_hidden; $arr['resource_type'] = 'photo'; $arr['resource_id'] = $photo_hash; $arr['owner_xchan'] = $channel['channel_hash']; $arr['author_xchan'] = $observer['xchan_hash']; $arr['title'] = $title; $arr['allow_cid'] = $ac['allow_cid']; $arr['allow_gid'] = $ac['allow_gid']; $arr['deny_cid'] = $ac['deny_cid']; $arr['deny_gid'] = $ac['deny_gid']; $arr['verb'] = ACTIVITY_POST; $arr['obj_type'] = ACTIVITY_OBJ_PHOTO; $arr['obj'] = json_encode($object); $arr['tgt_type'] = ACTIVITY_OBJ_ALBUM; $arr['target'] = json_encode($target); $arr['item_wall'] = 1; $arr['item_origin'] = 1; $arr['item_thread_top'] = 1; $arr['item_private'] = intval($acl->is_private()); $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid']; $arr['body'] = $summary; // this one is tricky because the item and the photo have the same permissions, those of the photo. // Use the channel read_stream permissions to get the correct public_policy for the item and recalculate the // private flag accordingly. This may cause subtle bugs due to custom permissions roles. We want to use // public policy when federating items to other sites, but should probably ignore them when accessing the item // in the photos pages - using the photos permissions instead. We need the public policy to keep the photo // linked item from leaking into the feed when somebody has a channel with read_stream restrictions. $arr['public_policy'] = map_scope($channel['channel_r_stream'], true); if ($arr['public_policy']) { $arr['item_private'] = 1; } $result = item_store($arr, false, $deliver); $item_id = $result['item_id']; if ($visible && $deliver) { Zotlabs\Daemon\Master::Summon(array('Notifier', 'wall-new', $item_id)); } } $ret['success'] = true; $ret['item'] = $arr; $ret['body'] = $obj_body; $ret['resource_id'] = $photo_hash; $ret['photoitem_id'] = $item_id; call_hooks('photo_upload_end', $ret); return $ret; }
/** * @brief Activity for files. * * @param int $channel_id * @param array $object * @param string $allow_cid * @param string $allow_gid * @param string $deny_cid * @param string $deny_gid * @param string $verb * @param boolean $notify */ function file_activity($channel_id, $object, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $verb, $notify) { require_once 'include/items.php'; $poster = App::get_observer(); //if we got no object something went wrong if (!$object) { return; } //turn strings into arrays $arr_allow_cid = expand_acl($allow_cid); $arr_allow_gid = expand_acl($allow_gid); $arr_deny_cid = expand_acl($deny_cid); $arr_deny_gid = expand_acl($deny_gid); //filter out receivers which do not have permission to view filestorage $arr_allow_cid = check_list_permissions($channel_id, $arr_allow_cid, 'view_storage'); $is_dir = intval($object['is_dir']) ? true : false; //do not send activity for folders for now if ($is_dir) { return; } //check for recursive perms if we are in a folder if ($object['folder']) { $folder_hash = $object['folder']; $r_perms = recursive_activity_recipients($arr_allow_cid, $arr_allow_gid, $arr_deny_cid, $arr_deny_gid, $folder_hash); //split up returned perms $arr_allow_cid = $r_perms['allow_cid']; $arr_allow_gid = $r_perms['allow_gid']; $arr_deny_cid = $r_perms['deny_cid']; $arr_deny_gid = $r_perms['deny_gid']; //filter out receivers which do not have permission to view filestorage $arr_allow_cid = check_list_permissions($channel_id, $arr_allow_cid, 'view_storage'); } $mid = item_message_id(); $objtype = ACTIVITY_OBJ_FILE; $arr = array(); $arr['aid'] = get_account_id(); $arr['uid'] = $channel_id; $arr['item_wall'] = 1; $arr['item_origin'] = 1; $arr['item_unseen'] = 1; $arr['author_xchan'] = $poster['xchan_hash']; $arr['owner_xchan'] = $poster['xchan_hash']; $arr['title'] = ''; $arr['item_hidden'] = 1; $arr['obj_type'] = $objtype; $arr['resource_id'] = $object['hash']; $arr['resource_type'] = 'attach'; $private = $arr_allow_cid[0] || $arr_allow_gid[0] || $arr_deny_cid[0] || $arr_deny_gid[0] ? 1 : 0; $jsonobject = json_encode($object); //check if item for this object exists $y = q("SELECT mid FROM item WHERE verb = '%s' AND obj_type = '%s' AND resource_id = '%s' AND uid = %d LIMIT 1", dbesc(ACTIVITY_POST), dbesc($objtype), dbesc($object['hash']), intval(local_channel())); if ($y) { $update = true; $object['d_mid'] = $y[0]['mid']; //attach mid of the old object $u_jsonobject = json_encode($object); //we have got the relevant info - delete the old item before we create the new one $z = q("DELETE FROM item WHERE obj_type = '%s' AND verb = '%s' AND mid = '%s'", dbesc(ACTIVITY_OBJ_FILE), dbesc(ACTIVITY_POST), dbesc($y[0]['mid'])); } //send update activity and create a new one if ($update && $verb == 'post') { //updates should be sent to everybody with recursive perms and all eventual former allowed members ($object['allow_cid'] etc.). $u_arr_allow_cid = array_unique(array_merge($arr_allow_cid, expand_acl($object['allow_cid']))); $u_arr_allow_gid = array_unique(array_merge($arr_allow_gid, expand_acl($object['allow_gid']))); $u_arr_deny_cid = array_unique(array_merge($arr_deny_cid, expand_acl($object['deny_cid']))); $u_arr_deny_gid = array_unique(array_merge($arr_deny_gid, expand_acl($object['deny_gid']))); $private = $u_arr_allow_cid[0] || $u_arr_allow_gid[0] || $u_arr_deny_cid[0] || $u_arr_deny_gid[0] ? 1 : 0; $u_mid = item_message_id(); $arr['mid'] = $u_mid; $arr['parent_mid'] = $u_mid; $arr['allow_cid'] = perms2str($u_arr_allow_cid); $arr['allow_gid'] = perms2str($u_arr_allow_gid); $arr['deny_cid'] = perms2str($u_arr_deny_cid); $arr['deny_gid'] = perms2str($u_arr_deny_gid); $arr['item_private'] = $private; $arr['verb'] = ACTIVITY_UPDATE; $arr['obj'] = $u_jsonobject; $arr['body'] = ''; $post = item_store($arr); $item_id = $post['item_id']; if ($item_id) { Zotlabs\Daemon\Master::Summon(array('Notifier', 'activity', $item_id)); } call_hooks('post_local_end', $arr); $update = false; //notice( t('File activity updated') . EOL); } //don't create new activity if notify was not enabled if (!$notify) { return; } //don't create new activity if we have an update request but there is no item to update //this can e.g. happen when deleting images if (!$y && $verb == 'update') { return; } $arr['mid'] = $mid; $arr['parent_mid'] = $mid; $arr['allow_cid'] = perms2str($arr_allow_cid); $arr['allow_gid'] = perms2str($arr_allow_gid); $arr['deny_cid'] = perms2str($arr_deny_cid); $arr['deny_gid'] = perms2str($arr_deny_gid); $arr['item_private'] = $private; $arr['verb'] = $update ? ACTIVITY_UPDATE : ACTIVITY_POST; $arr['obj'] = $update ? $u_jsonobject : $jsonobject; $arr['body'] = ''; $post = item_store($arr); $item_id = $post['item_id']; if ($item_id) { Zotlabs\Daemon\Master::Summon(array('Notifier', 'activity', $item_id)); } call_hooks('post_local_end', $arr); //(($verb === 'post') ? notice( t('File activity posted') . EOL) : notice( t('File activity dropped') . EOL)); return; }
function diaspora_signed_retraction($importer, $xml, $msg) { // obsolete - see https://github.com/SuperTux88/diaspora_federation/issues/27 $guid = notags(diaspora_get_target_guid($xml)); $diaspora_handle = notags(diaspora_get_author($xml)); $type = notags(diaspora_get_type($xml)); $sig = notags(unxmlify($xml['target_author_signature'])); $parent_author_signature = $xml['parent_author_signature'] ? notags(unxmlify($xml['parent_author_signature'])) : ''; $contact = diaspora_get_contact_by_handle($importer['channel_id'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['channel_id']); return; } $signed_data = $guid . ';' . $type; $key = $msg['key']; /* How Diaspora performs relayable_retraction signature checking: - If an item has been sent by the item author to the top-level post owner to relay on to the rest of the contacts on the top-level post, the top-level post owner checks the author_signature, then creates a parent_author_signature before relaying the item on - If an item has been relayed on by the top-level post owner, the contacts who receive it check only the parent_author_signature. Basically, they trust that the top-level post owner has already verified the authenticity of anything he/she sends out - In either case, the signature that get checked is the signature created by the person who sent the salmon */ if ($parent_author_signature) { $parent_author_signature = base64_decode($parent_author_signature); if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_signed_retraction: top-level post owner verification failed'); return; } } else { $sig_decode = base64_decode($sig); if (!rsa_verify($signed_data, $sig_decode, $key, 'sha256')) { logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg, true)); return; } } if ($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($guid), intval($importer['channel_id'])); if ($r) { if ($r[0]['author_xchan'] == $contact['xchan_hash']) { drop_item($r[0]['id'], false, DROPITEM_PHASE1); // Now check if the retraction needs to be relayed by us // // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select item_flags from item where parent = %d and id = %d limit 1", $r[0]['parent'], $r[0]['parent']); if ($p) { if (intval($p[0]['item_origin']) && !$parent_author_signature) { // the existence of parent_author_signature would have meant the parent_author or owner // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); Zotlabs\Daemon\Master::Summon(array('Notifier', 'drop', $r[0]['id'])); } } } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
function new_contact($uid, $url, $channel, $interactive = false, $confirm = false) { $result = array('success' => false, 'message' => ''); $is_red = false; $is_http = strpos($url, '://') !== false ? true : false; if ($is_http && substr($url, -1, 1) === '/') { $url = substr($url, 0, -1); } if (!allowed_url($url)) { $result['message'] = t('Channel is blocked on this site.'); return $result; } if (!$url) { $result['message'] = t('Channel location missing.'); return $result; } // check service class limits $r = q("select count(*) as total from abook where abook_channel = %d and abook_self = 0 ", intval($uid)); if ($r) { $total_channels = $r[0]['total']; } if (!service_class_allows($uid, 'total_channels', $total_channels)) { $result['message'] = upgrade_message(); return $result; } $arr = array('url' => $url, 'channel' => array()); call_hooks('follow', $arr); if ($arr['channel']['success']) { $ret = $arr['channel']; } elseif (!$is_http) { $ret = Zotlabs\Zot\Finger::run($url, $channel); } if ($ret && is_array($ret) && $ret['success']) { $is_red = true; $j = $ret; } $my_perms = get_channel_default_perms($uid); $role = get_pconfig($uid, 'system', 'permissions_role'); if ($role) { $x = \Zotlabs\Access\PermissionRoles::role_perms($role); if ($x['perms_connect']) { $my_perms = $x['perms_connect']; } } if ($is_red && $j) { logger('follow: ' . $url . ' ' . print_r($j, true), LOGGER_DEBUG); if (!($j['success'] && $j['guid'])) { $result['message'] = t('Response from remote channel was incomplete.'); logger('mod_follow: ' . $result['message']); return $result; } // Premium channel, set confirm before callback to avoid recursion if (array_key_exists('connect_url', $j) && $interactive && !$confirm) { goaway(zid($j['connect_url'])); } // do we have an xchan and hubloc? // If not, create them. $x = import_xchan($j); if (array_key_exists('deleted', $j) && intval($j['deleted'])) { $result['message'] = t('Channel was deleted and no longer exists.'); return $result; } if (!$x['success']) { return $x; } $xchan_hash = $x['hash']; if (array_key_exists('permissions', $j) && array_key_exists('data', $j['permissions'])) { $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']); if ($permissions) { $permissions = json_decode($permissions, true); } logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA); } else { $permissions = $j['permissions']; } if (is_array($permissions) && $permissions) { foreach ($permissions as $k => $v) { set_abconfig($channel['channel_uid'], $xchan_hash, 'their_perms', $k, intval($v)); } } } else { $xchan_hash = ''; $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); if (!$r) { // attempt network auto-discovery $d = discover_by_webbie($url); if (!$d && $is_http) { // try RSS discovery if (get_config('system', 'feed_contacts')) { $d = discover_by_url($url); } else { $result['message'] = t('Protocol disabled.'); return $result; } } if ($d) { $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); } } // if discovery was a success we should have an xchan record in $r if ($r) { $xchan = $r[0]; $xchan_hash = $r[0]['xchan_hash']; $their_perms = 0; } } if (!$xchan_hash) { $result['message'] = t('Channel discovery failed.'); logger('follow: ' . $result['message']); return $result; } $allowed = $is_red || $r[0]['xchan_network'] === 'rss' ? 1 : 0; $x = array('channel_id' => $uid, 'follow_address' => $url, 'xchan' => $r[0], 'allowed' => $allowed, 'singleton' => 0); call_hooks('follow_allow', $x); if (!$x['allowed']) { $result['message'] = t('Protocol disabled.'); return $result; } $singleton = intval($x['singleton']); $aid = $channel['channel_account_id']; $hash = get_observer_hash(); $default_group = $channel['channel_default_group']; if ($xchan['xchan_network'] === 'rss') { // check service class feed limits $r = q("select count(*) as total from abook where abook_account = %d and abook_feed = 1 ", intval($aid)); if ($r) { $total_feeds = $r[0]['total']; } if (!service_class_allows($uid, 'total_feeds', $total_feeds)) { $result['message'] = upgrade_message(); return $result; } } if ($hash == $xchan_hash) { $result['message'] = t('Cannot connect to yourself.'); return $result; } $r = q("select abook_xchan, abook_instance from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($is_http) { // Always set these "remote" permissions for feeds since we cannot interact with them // to negotiate a suitable permission response set_abconfig($uid, $xchan_hash, 'their_perms', 'view_stream', 1); set_abconfig($uid, $xchan_hash, 'their_perms', 'republish', 1); } if ($r) { $abook_instance = $r[0]['abook_instance']; if ($singleton && strpos($abook_instance, z_root()) === false) { if ($abook_instance) { $abook_instance .= ','; } $abook_instance .= z_root(); } $x = q("update abook set abook_instance = '%s' where abook_id = %d", dbesc($abook_instance), intval($r[0]['abook_id'])); } else { $closeness = get_pconfig($uid, 'system', 'new_abook_closeness'); if ($closeness === false) { $closeness = 80; } $r = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_feed, abook_created, abook_updated, abook_instance )\n\t\t\tvalues( %d, %d, %d, '%s', %d, '%s', '%s', '%s' ) ", intval($aid), intval($uid), intval($closeness), dbesc($xchan_hash), intval($is_http ? 1 : 0), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($singleton ? z_root() : '')); } if (!$r) { logger('mod_follow: abook creation failed'); } $all_perms = \Zotlabs\Access\Permissions::Perms(); if ($all_perms) { foreach ($all_perms as $k => $v) { if (in_array($k, $my_perms)) { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 1); } else { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 0); } } } $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash \n\t\twhere abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($r) { $result['abook'] = $r[0]; Zotlabs\Daemon\Master::Summon(array('Notifier', 'permission_create', $result['abook']['abook_id'])); } $arr = array('channel_id' => $uid, 'channel' => $channel, 'abook' => $result['abook']); call_hooks('follow', $arr); /** If there is a default group for this channel, add this connection to it */ if ($default_group) { require_once 'include/group.php'; $g = group_rec_byhash($uid, $default_group); if ($g) { group_add_member($uid, '', $xchan_hash, $g['id']); } } $result['success'] = true; return $result; }
function do_delivery($deliveries) { if (!(is_array($deliveries) && count($deliveries))) { return; } $interval = get_config('system', 'delivery_interval') !== false ? intval(get_config('system', 'delivery_interval')) : 2; $deliveries_per_process = intval(get_config('system', 'delivery_batch_count')); if ($deliveries_per_process <= 0) { $deliveries_per_process = 1; } $deliver = array(); foreach ($deliveries as $d) { if (!$d) { continue; } $deliver[] = $d; if (count($deliver) >= $deliveries_per_process) { Zotlabs\Daemon\Master::Summon(array('Deliver', $deliver)); $deliver = array(); if ($interval) { @time_sleep_until(microtime(true) + (double) $interval); } } } // catch any stragglers if ($deliver) { Zotlabs\Daemon\Master::Summon(array('Deliver', $deliver)); } }
function gnusoc_follow_from_feed(&$a, &$b) { $item = $b['item']; $importer = $b['channel']; $xchan = $b['xchan']; $author = $b['author']; $b['caught'] = true; logger('follow activity received'); if ($author && !$xchan) { $r = q("select * from xchan where xchan_guid = '%s' limit 1", dbesc($author['author_link'])); if (!$r) { if (discover_by_webbie($author['author_link'])) { $r = q("select * from xchan where xchan_guid = '%s' limit 1", dbesc($author['author_link'])); if (!$r) { logger('discovery failed'); return; } } $xchan = $r[0]; } $x = \Zotlabs\Access\PermissionRoles::role_perms('social'); $their_perms = \Zotlabs\Access\Permissions::FilledPerms($x['perms_connect']); $r = q("select * from abook where abook_channel = %d and abook_xchan = '%s' limit 1", intval($importer['channel_id']), dbesc($xchan['xchan_hash'])); if ($r) { $contact = $r[0]; $abook_instance = $contact['abook_instance']; if ($abook_instance) { $abook_instance .= ','; } $abook_instance .= z_root(); $r = q("update abook set abook_instance = '%s' where abook_id = %d and abook_channel = %d", dbesc($abook_instance), intval($contact['abook_id']), intval($importer['channel_id'])); foreach ($their_perms as $k => $v) { set_abconfig($importer['channel_id'], $contact['abook_xchan'], 'their_perms', $k, $v); } } else { $role = get_pconfig($importer['channel_id'], 'system', 'permissions_role'); if ($role) { $x = \Zotlabs\Access\PermissionRoles::role_perms($role); if ($x['perms_auto']) { $my_perms = \Zotlabs\Access\Permissions::FilledPerms($x['perms_connect']); } } if (!$my_perms) { $my_perms = \Zotlabs\Access\Permissions::FilledAutoperms($importer['channel_id']); } $closeness = get_pconfig($importer['channel_id'], 'system', 'new_abook_closeness'); if ($closeness === false) { $closeness = 80; } $r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_closeness, abook_created, abook_updated, abook_connected, abook_dob, abook_pending, abook_instance ) values ( %d, %d, '%s', %d, '%s', '%s', '%s', '%s', %d, '%s' )", intval($importer['channel_account_id']), intval($importer['channel_id']), dbesc($xchan['xchan_hash']), intval($closeness), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc(NULL_DATE), intval($my_perms ? 0 : 1), dbesc(z_root())); if ($r) { if ($my_perms) { foreach ($my_perms as $k => $v) { set_abconfig($importer['channel_id'], $xchan['xchan_hash'], 'my_perms', $k, $v); } } if ($their_perms) { foreach ($their_perms as $k => $v) { set_abconfig($importer['channel_id'], $xchan['xchan_hash'], 'their_perms', $k, $v); } } logger("New GNU-Social follower received for {$importer['channel_name']}"); $new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash left join hubloc on hubloc_hash = xchan_hash where abook_channel = %d and abook_xchan = '%s' order by abook_created desc limit 1", intval($importer['channel_id']), dbesc($xchan['xchan_hash'])); if ($new_connection) { \Zotlabs\Lib\Enotify::submit(array('type' => NOTIFY_INTRO, 'from_xchan' => $xchan['xchan_hash'], 'to_xchan' => $importer['channel_hash'], 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'])); if ($default_perms) { // Send back a sharing notification to them $deliver = gnusoc_remote_follow($importer, $new_connection[0]); if ($deliver) { Zotlabs\Daemon\Master::Summon(array('Deliver', $deliver)); } } $clone = array(); foreach ($new_connection[0] as $k => $v) { if (strpos($k, 'abook_') === 0) { $clone[$k] = $v; } } unset($clone['abook_id']); unset($clone['abook_account']); unset($clone['abook_channel']); $abconfig = load_abconfig($importer['channel_id'], $clone['abook_xchan']); if ($abconfig) { $clone['abconfig'] = $abconfig; } build_sync_packet($importer['channel_id'], array('abook' => array($clone))); } } } return; } }
function send_message($uid = 0, $recipient = '', $body = '', $subject = '', $replyto = '', $expires = '') { $ret = array('success' => false); $is_reply = false; $a = get_app(); $observer_hash = get_observer_hash(); if (!$recipient) { $ret['message'] = t('No recipient provided.'); return $ret; } if (!strlen($subject)) { $subject = t('[no subject]'); } // if(! $expires) // $expires = NULL_DATE; // else // $expires = datetime_convert(date_default_timezone_get(),'UTC',$expires); if ($uid) { $r = q("select * from channel where channel_id = %d limit 1", intval($uid)); if ($r) { $channel = $r[0]; } } else { $channel = App::get_channel(); } if (!$channel) { $ret['message'] = t('Unable to determine sender.'); return $ret; } // look for any existing conversation structure $conv_guid = ''; if (strlen($replyto)) { $is_reply = true; $r = q("select conv_guid from mail where channel_id = %d and ( mid = '%s' or parent_mid = '%s' ) limit 1", intval(local_channel()), dbesc($replyto), dbesc($replyto)); if ($r) { $conv_guid = $r[0]['conv_guid']; } } if (!$conv_guid) { // create a new conversation $conv_guid = random_string(); $recip = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($recipient)); if ($recip) { $recip_handle = $recip[0]['xchan_addr']; } $sender_handle = $channel['channel_address'] . '@' . App::get_hostname(); $handles = $recip_handle . ';' . $sender_handle; if ($subject) { $nsubject = str_rot47(base64url_encode($subject)); } $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ", intval(local_channel()), dbesc($conv_guid), dbesc($sender_handle), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($nsubject), dbesc($handles)); $r = q("select * from conv where guid = '%s' and uid = %d limit 1", dbesc($conv_guid), intval(local_channel())); if ($r) { $retconv = $r[0]; $retconv['subject'] = base64url_decode(str_rot47($retconv['subject'])); } } if (!$retconv) { $r = q("select * from conv where guid = '%s' and uid = %d limit 1", dbesc($conv_guid), intval(local_channel())); if ($r) { $retconv = $r[0]; $retconv['subject'] = base64url_decode(str_rot47($retconv['subject'])); } } if (!$retconv) { $ret['message'] = 'conversation not found'; return $ret; } // generate a unique message_id do { $dups = false; $hash = random_string(); $mid = $hash . '@' . App::get_hostname(); $r = q("SELECT id FROM mail WHERE mid = '%s' LIMIT 1", dbesc($mid)); if (count($r)) { $dups = true; } } while ($dups == true); if (!strlen($replyto)) { $replyto = $mid; } /** * * When a photo was uploaded into the message using the (profile wall) ajax * uploader, The permissions are initially set to disallow anybody but the * owner from seeing it. This is because the permissions may not yet have been * set for the post. If it's private, the photo permissions should be set * appropriately. But we didn't know the final permissions on the post until * now. So now we'll look for links of uploaded messages that are in the * post and set them to the same permissions as the post itself. * */ $match = null; $images = null; if (preg_match_all("/\\[zmg\\=([0-9]*)x([0-9]*)\\](.*?)\\[\\/zmg\\]/", strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $match)) { $images = $match[3]; } $match = false; if (preg_match_all("/\\[attachment\\](.*?)\\[\\/attachment\\]/", strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $match)) { $attaches = $match[1]; } $attachments = ''; if (preg_match_all('/(\\[attachment\\](.*?)\\[\\/attachment\\])/', $body, $match)) { $attachments = array(); foreach ($match[2] as $mtch) { $hash = substr($mtch, 0, strpos($mtch, ',')); $rev = intval(substr($mtch, strpos($mtch, ','))); $r = attach_by_hash_nodata($hash, $rev); if ($r['success']) { $attachments[] = array('href' => z_root() . '/attach/' . $r['data']['hash'], 'length' => $r['data']['filesize'], 'type' => $r['data']['filetype'], 'title' => urlencode($r['data']['filename']), 'revision' => $r['data']['revision']); } $body = trim(str_replace($match[1], '', $body)); } } $jattach = $attachments ? json_encode($attachments) : ''; if ($subject) { $subject = str_rot47(base64url_encode($subject)); } if ($body) { $body = str_rot47(base64url_encode($body)); } $r = q("INSERT INTO mail ( account_id, conv_guid, mail_obscured, channel_id, from_xchan, to_xchan, title, body, attach, mid, parent_mid, created, expires, mail_isreply )\n\t\tVALUES ( %d, '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d )", intval($channel['channel_account_id']), dbesc($conv_guid), intval(1), intval($channel['channel_id']), dbesc($channel['channel_hash']), dbesc($recipient), dbesc($subject), dbesc($body), dbesc($jattach), dbesc($mid), dbesc($replyto), dbesc(datetime_convert()), dbescdate($expires), intval($is_reply)); // verify the save $r = q("SELECT * FROM mail WHERE mid = '%s' and channel_id = %d LIMIT 1", dbesc($mid), intval($channel['channel_id'])); if ($r) { $post_id = $r[0]['id']; $retmail = $r[0]; xchan_mail_query($retmail); } else { $ret['message'] = t('Stored post could not be verified.'); return $ret; } if (count($images)) { foreach ($images as $image) { if (!stristr($image, z_root() . '/photo/')) { continue; } $image_uri = substr($image, strrpos($image, '/') + 1); $image_uri = substr($image_uri, 0, strpos($image_uri, '-')); $r = q("UPDATE photo SET allow_cid = '%s' WHERE resource_id = '%s' AND uid = %d and allow_cid = '%s'", dbesc('<' . $recipient . '>'), dbesc($image_uri), intval($channel['channel_id']), dbesc('<' . $channel['channel_hash'] . '>')); $r = q("UPDATE attach SET allow_cid = '%s' WHERE hash = '%s' AND is_photo = 1 and uid = %d and allow_cid = '%s'", dbesc('<' . $recipient . '>'), dbesc($image_uri), intval($channel['channel_id']), dbesc('<' . $channel['channel_hash'] . '>')); } } if ($attaches) { foreach ($attaches as $attach) { $hash = substr($attach, 0, strpos($attach, ',')); $rev = intval(substr($attach, strpos($attach, ','))); attach_store($channel, $observer_hash, $options = 'update', array('hash' => $hash, 'revision' => $rev, 'allow_cid' => '<' . $recipient . '>')); } } Zotlabs\Daemon\Master::Summon(array('Notifier', 'mail', $post_id)); $ret['success'] = true; $ret['message_item'] = intval($post_id); $ret['conv'] = $retconv; $ret['mail'] = $retmail; return $ret; }