Commit 4a6d050e authored by Max Kostikov's avatar Max Kostikov

Merge branch 'sse_merge_core' into 'dev'

sse notifications

See merge request hubzilla/core!1783
parents 89342ca9 b033597a
Pipeline #184322 passed with stage
in 5 minutes and 9 seconds
......@@ -550,6 +550,11 @@ class Enotify {
if ((\App::$language === 'en' || (! \App::$language)) && strpos($msg,', '))
$msg = substr($msg,strpos($msg,', ')+1);
$datarray['id'] = $notify_id;
$datarray['msg'] = $msg;
call_hooks('enotify_store_end', $datarray);
$r = q("update notify set msg = '%s' where id = %d and uid = %d",
dbesc($msg),
intval($notify_id),
......@@ -838,7 +843,7 @@ class Enotify {
'addr' => (($item[$who]['xchan_addr']) ? $item[$who]['xchan_addr'] : $item[$who]['xchan_url']),
'url' => $item[$who]['xchan_url'],
'photo' => $item[$who]['xchan_photo_s'],
'when' => relative_date(($edit)? $item['edited'] : $item['created']),
'when' => (($edit) ? datetime_convert('UTC', date_default_timezone_get(), $item['edited']) : datetime_convert('UTC', date_default_timezone_get(), $item['created'])),
'class' => (intval($item['item_unseen']) ? 'notify-unseen' : 'notify-seen'),
'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? 'b64.' . base64url_encode($item['thr_parent']) : 'b64.' . base64url_encode($item['mid'])),
'notify_id' => 'undefined',
......@@ -846,7 +851,7 @@ class Enotify {
'message' => strip_tags(bbcode($itemem_text)),
// these are for the superblock addon
'hash' => $item[$who]['xchan_hash'],
'uid' => local_channel(),
'uid' => $item['uid'],
'display' => true
);
......@@ -858,4 +863,131 @@ class Enotify {
return $x;
}
static public function format_notify($tt) {
$message = trim(strip_tags(bbcode($tt['msg'])));
if(strpos($message, $tt['xname']) === 0)
$message = substr($message, strlen($tt['xname']) + 1);
$mid = basename($tt['link']);
$mid = ((strpos($mid, 'b64.') === 0) ? @base64url_decode(substr($mid, 4)) : $mid);
if(in_array($tt['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) {
// we need the thread parent
$r = q("select thr_parent from item where mid = '%s' and uid = %d limit 1",
dbesc($mid),
intval(local_channel())
);
$b64mid = ((strpos($r[0]['thr_parent'], 'b64.') === 0) ? $r[0]['thr_parent'] : 'b64.' . base64url_encode($r[0]['thr_parent']));
}
else {
$b64mid = ((strpos($mid, 'b64.') === 0) ? $mid : 'b64.' . base64url_encode($mid));
}
$x = [
'notify_link' => z_root() . '/notify/view/' . $tt['id'],
'name' => $tt['xname'],
'url' => $tt['url'],
'photo' => $tt['photo'],
'when' => datetime_convert('UTC', date_default_timezone_get(), $tt['created']),
'hclass' => (($tt['seen']) ? 'notify-seen' : 'notify-unseen'),
'b64mid' => (($tt['otype'] == 'item') ? $b64mid : 'undefined'),
'notify_id' => (($tt['otype'] == 'item') ? $tt['id'] : 'undefined'),
'message' => $message
];
return $x;
}
static public function format_intros($rr) {
$x = [
'notify_link' => z_root() . '/connections/ifpending',
'name' => $rr['xchan_name'],
'addr' => $rr['xchan_addr'],
'url' => $rr['xchan_url'],
'photo' => $rr['xchan_photo_s'],
'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['abook_created']),
'hclass' => ('notify-unseen'),
'message' => t('added your channel')
];
return $x;
}
static public function format_files($rr) {
$x = [
'notify_link' => z_root() . '/sharedwithme',
'name' => $rr['author']['xchan_name'],
'addr' => $rr['author']['xchan_addr'],
'url' => $rr['author']['xchan_url'],
'photo' => $rr['author']['xchan_photo_s'],
'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['created']),
'hclass' => ('notify-unseen'),
'message' => t('shared a file with you')
];
return $x;
}
static public function format_mail($rr) {
$x = [
'notify_link' => z_root() . '/mail/' . $rr['id'],
'name' => $rr['xchan_name'],
'addr' => $rr['xchan_addr'],
'url' => $rr['xchan_url'],
'photo' => $rr['xchan_photo_s'],
'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['created']),
'hclass' => (intval($rr['mail_seen']) ? 'notify-seen' : 'notify-unseen'),
'message' => t('sent you a private message'),
];
return $x;
}
static public function format_all_events($rr) {
$bd_format = t('g A l F d') ; // 8 AM Friday January 18
$strt = datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart']);
$today = ((substr($strt, 0, 10) === datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d')) ? true : false);
$when = day_translate(datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart'], $bd_format)) . (($today) ? ' ' . t('[today]') : '');
$x = [
'notify_link' => z_root() . '/cdav/calendar/' . $rr['event_hash'],
'name' => $rr['xchan_name'],
'addr' => $rr['xchan_addr'],
'url' => $rr['xchan_url'],
'photo' => $rr['xchan_photo_s'],
'when' => $when,
'hclass' => ('notify-unseen'),
'message' => t('posted an event')
];
return $x;
}
static public function format_register($rr) {
$x = [
'notify_link' => z_root() . '/admin/accounts',
'name' => $rr['account_email'],
'addr' => $rr['account_email'],
'url' => '',
'photo' => z_root() . '/' . get_default_profile_photo(48),
'when' => datetime_convert('UTC', date_default_timezone_get(),$rr['account_created']),
'hclass' => ('notify-unseen'),
'message' => t('requires approval')
];
return $x;
}
}
......@@ -369,7 +369,7 @@ class ThreadItem {
'folders' => $body['folders'],
'text' => strip_tags($body['html']),
'id' => $this->get_id(),
'mid' => $item['mid'],
'mid' => 'b64.' . base64url_encode($item['mid']),
'parent' => $item['parent'],
'author_id' => (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']),
'isevent' => $isevent,
......@@ -467,10 +467,9 @@ class ThreadItem {
'previewing' => ($conv->is_preview() ? true : false ),
'preview_lbl' => t('This is an unsaved preview'),
'wait' => t('Please wait'),
'submid' => str_replace(['+','='], ['',''], base64_encode($item['mid'])),
'thread_level' => $thread_level,
'settings' => $settings,
'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? $item['thr_parent'] : '')
'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? 'b64.' . base64url_encode($item['thr_parent']) : '')
);
$arr = array('item' => $item, 'output' => $tmp_item);
......
......@@ -145,7 +145,7 @@ class Mail extends \Zotlabs\Web\Controller {
$o = '';
nav_set_selected('Mail');
if(! local_channel()) {
notice( t('Permission denied.') . EOL);
return login();
......
<?php
namespace Zotlabs\Module;
use App;
use Zotlabs\Lib\Apps;
use Zotlabs\Web\Controller;
use Zotlabs\Lib\Enotify;
class Sse extends Controller {
public static $uid;
public static $ob_hash;
public static $vnotify;
function init() {
// this is important!
session_write_close();
$sys = get_sys_channel();
self::$uid = local_channel();
self::$ob_hash = get_observer_hash();
self::$vnotify = get_pconfig(self::$uid, 'system', 'vnotify');
$sleep_seconds = 3;
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Connection: keep-alive");
header("X-Accel-Buffering: no");
while(true) {
/**
* Update chat presence indication (if applicable)
*/
if(self::$ob_hash) {
$r = q("select cp_id, cp_room from chatpresence where cp_xchan = '%s' and cp_client = '%s' and cp_room = 0 limit 1",
dbesc(self::$ob_hash),
dbesc($_SERVER['REMOTE_ADDR'])
);
$basic_presence = false;
if($r) {
$basic_presence = true;
q("update chatpresence set cp_last = '%s' where cp_id = %d",
dbesc(datetime_convert()),
intval($r[0]['cp_id'])
);
}
if(! $basic_presence) {
q("insert into chatpresence ( cp_xchan, cp_last, cp_status, cp_client)
values( '%s', '%s', '%s', '%s' ) ",
dbesc(self::$ob_hash),
dbesc(datetime_convert()),
dbesc('online'),
dbesc($_SERVER['REMOTE_ADDR'])
);
}
}
/**
* Chatpresence continued... if somebody hasn't pinged recently, they've most likely left the page
* and shouldn't count as online anymore. We allow an expection for bots.
*/
q("delete from chatpresence where cp_last < %s - INTERVAL %s and cp_client != 'auto' ",
db_utcnow(),
db_quoteinterval('3 MINUTE')
);
$x = q("SELECT v FROM xconfig WHERE xchan = '%s' AND cat = 'sse' AND k = 'notifications'",
dbesc(self::$ob_hash)
);
if($x) {
$result = unserialize($x[0]['v']);
}
if($result) {
echo "event: notifications\n";
echo 'data: ' . json_encode($result);
echo "\n\n";
del_xconfig(self::$ob_hash, 'sse', 'notifications');
unset($result);
}
// always send heartbeat to detect disconnected clients
echo "event: heartbeat\n";
echo 'data: {}';
echo "\n\n";
ob_end_flush();
flush();
if(connection_status() != CONNECTION_NORMAL || connection_aborted()) {
break;
}
sleep($sleep_seconds);
}
}
}
This diff is collapsed.
......@@ -1811,6 +1811,8 @@ function can_view_public_stream() {
* @param string $s Text to display
*/
function notice($s) {
/*
if(! session_id())
return;
......@@ -1826,6 +1828,34 @@ function notice($s) {
if(App::$interactive) {
$_SESSION['sysmsg'][] = $s;
}
*/
$hash = get_observer_hash();
if (! $hash)
return;
$t = get_xconfig($hash, 'sse', 'timestamp');
if(datetime_convert('UTC', 'UTC', $t) < datetime_convert('UTC', 'UTC', '- 30 seconds')) {
del_xconfig($hash, 'sse', 'notifications');
}
$x = get_xconfig($hash, 'sse', 'notifications');
if ($x === false)
$x = [];
if (isset($x['notice']) && in_array($s, $x['notice']['notifications']))
return;
if (App::$interactive) {
$x['notice']['notifications'][] = $s;
set_xconfig($hash, 'sse', 'timestamp', datetime_convert());
set_xconfig($hash, 'sse', 'notifications', $x);
}
}
/**
......@@ -1839,8 +1869,11 @@ function notice($s) {
* @param string $s Text to display
*/
function info($s) {
/*
if(! session_id())
return;
if(! x($_SESSION, 'sysmsg_info'))
$_SESSION['sysmsg_info'] = array();
......@@ -1849,6 +1882,33 @@ function info($s) {
if(App::$interactive)
$_SESSION['sysmsg_info'][] = $s;
*/
$hash = get_observer_hash();
if (! $hash)
return;
$t = get_xconfig($hash, 'sse', 'timestamp');
if(datetime_convert('UTC', 'UTC', $t) < datetime_convert('UTC', 'UTC', '- 30 seconds')) {
del_xconfig($hash, 'sse', 'notifications');
}
$x = get_xconfig($hash, 'sse', 'notifications');
if($x === false)
$x = [];
if(isset($x['info']) && in_array($s, $x['info']['notifications']))
return;
if(App::$interactive) {
$x['info']['notifications'][] = $s;
set_xconfig($hash, 'sse', 'timestamp', datetime_convert());
set_xconfig($hash, 'sse', 'notifications', $x);
}
}
/**
......
......@@ -259,6 +259,9 @@ Hooks allow plugins/addons to "hook into" the code at many points and alter the
[zrl=[baseurl]/help/hook/enotify_store]enotify_store[/zrl]
called when storing a notification record
[zrl=[baseurl]/help/hook/enotify_store_end]enotify_store_end[/zrl]
called after a notification record has been stored
[zrl=[baseurl]/help/hook/event_created]event_created[/zrl]
called when an event record is created
......
......@@ -2953,3 +2953,8 @@ function pchan_to_chan($pchan) {
function channel_url($channel) {
return (($channel) ? z_root() . '/channel/' . $channel['channel_address'] : z_root());
}
function get_channel_hashes() {
$r = q("SELECT channel_hash FROM channel WHERE channel_removed = 0");
return flatten_array_recursive($r);
}
......@@ -553,9 +553,19 @@ function event_store_event($arr) {
dbesc($hash),
intval($arr['uid'])
);
if($r)
if($r) {
/**
* @hooks event_store_event_end
* Called after an event record was stored.
* * \e array \b event
*/
call_hooks('event_store_event_end', $r[0]);
return $r[0];
}
return false;
}
......
......@@ -181,7 +181,6 @@ a.wikilist {
.notifications-textinput {
padding: .75rem 0.85rem;
position: relative;
}
.notifications-textinput input {
......@@ -233,3 +232,7 @@ a.wikilist {
margin-top: 25px;
opacity: 0.8;
}
#cid-filter-wrapper {
position: relative;
}
......@@ -77,7 +77,7 @@ App::$strings["Search"] = "Suche";
App::$strings["Search site @name, !forum, #tag, ?docs, content"] = "Hub durchsuchen: @Name, !Forum, #Schlagwort, ?Dokumentation, Inhalt";
App::$strings["Admin"] = "Administration";
App::$strings["Site Setup and Configuration"] = "Seiten-Einrichtung und -Konfiguration";
App::$strings["Loading"] = "Lädt...";
App::$strings["Loading"] = "Lädt";
App::$strings["@name, !forum, #tag, ?doc, content"] = "@Name, !Forum, #Schlagwort, ?Dokumentation, Inhalt";
App::$strings["Please wait..."] = "Bitte warten...";
App::$strings["Add Apps"] = "Apps hinzufügen";
......
This diff is collapsed.
var evtSource = new EventSource('/sse');
onconnect = function(e) {
var port = e.ports[0];
port.start();
evtSource.addEventListener('notifications', function(e) {
var obj = JSON.parse(e.data);
port.postMessage(obj);
}, false);
}
......@@ -37,6 +37,7 @@ head_add_js('/library/colorbox/jquery.colorbox-min.js');
head_add_js('/library/jquery.AreYouSure/jquery.are-you-sure.js');
head_add_js('/library/tableofcontents/jquery.toc.js');
head_add_js('/vendor/desandro/imagesloaded/imagesloaded.pkgd.min.js');
/**
* Those who require this feature will know what to do with it.
* Those who don't, won't.
......
......@@ -9,7 +9,7 @@
</h3>
{{$content}}
{{if $name}}
<div class="notifications-textinput">
<div id="cid-filter-wrapper" class="notifications-textinput">
<form method="get" action="{{$name.url}}" role="search">
<div class="text-muted notifications-textinput-filter"><i class="fa fa-fw fa-filter"></i></div>
<input id="cid" type="hidden" value="" name="cid" />
......
......@@ -4,9 +4,9 @@
</div>
<div id="collapsed-comments-{{$item.id}}" class="collapsed-comments" style="display: none;">
{{/if}}
<div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite {{/if}} item_{{$item.submid}}">
<div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite{{/if}}" data-b64mid="{{$item.mid}}">
<a name="item_{{$item.id}}" ></a>
<div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" data-mid="{{$item.mid}}" id="wall-item-outside-wrapper-{{$item.id}}" >
<div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" id="wall-item-outside-wrapper-{{$item.id}}" >
<div class="clearfix wall-item-content-wrapper{{if $item.is_comment}} comment{{/if}}" id="wall-item-content-wrapper-{{$item.id}}">
{{if $item.photo}}
<div class="wall-photo-item" id="wall-photo-item-{{$item.id}}">
......
......@@ -4,7 +4,7 @@
</div>
<div id="collapsed-comments-{{$item.id}}" class="collapsed-comments" style="display: none;">
{{/if}}
<div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite {{/if}} item_{{$item.submid}}">
<div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite {{/if}}" data-b64mid="{{$item.mid}}">
<a name="item_{{$item.id}}" ></a>
<div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" id="wall-item-outside-wrapper-{{$item.id}}" >
<div class="clearfix wall-item-content-wrapper{{if $item.is_comment}} comment{{/if}}" id="wall-item-content-wrapper-{{$item.id}}">
......
......@@ -35,7 +35,7 @@
'name_ok2' : "{{$name_ok2}}",
'to_reply' : "{{$to_reply}}",
'plural_func' : "{{$plural_func}}",
'plural_func' : "{{$plural_func}}",
't01' : "{{$t01}}",
't02' : "{{$t02}}",
......
......@@ -85,7 +85,7 @@
$('#nav-{{$notification.type}}-menu [data-thread_top=false]').toggle();
$(this).toggleClass('active sticky-top');
});
$(document).on('click ', '#cn-{{$notification.type}}-input-clear', function(e) {
$(document).on('click', '#cn-{{$notification.type}}-input-clear', function(e) {
$('#cn-{{$notification.type}}-input').val('');
$('#cn-{{$notification.type}}-only').removeClass('active sticky-top');
$("#nav-{{$notification.type}}-menu .notification").removeClass('d-none');
......@@ -93,8 +93,8 @@
});
$(document).on('input', '#cn-{{$notification.type}}-input', function(e) {
var val = $('#cn-{{$notification.type}}-input').val().toString().toLowerCase();
if(val) {
val = val.indexOf('%') == 0 ? val.substring(1) : val;
$('#cn-{{$notification.type}}-only').addClass('active sticky-top');
$('#cn-{{$notification.type}}-input-clear').removeClass('d-none');
}
......@@ -135,16 +135,17 @@
{{$no_notifications}}<span class="jumping-dots"><span class="dot-1">.</span><span class="dot-2">.</span><span class="dot-3">.</span></span>
</div>
<div id="nav-notifications-template" rel="template">
<a class="list-group-item clearfix notification {6}" href="{0}" title="{3}" data-b64mid="{7}" data-notify_id="{8}" data-thread_top="{9}" data-contact_name="{2}" data-contact_addr="{3}">
<img class="menu-img-3" data-src="{1}">
<a class="list-group-item clearfix notification {6}" href="{0}" title="{3}" data-b64mid="{7}" data-notify_id="{8}" data-thread_top="{9}" data-contact_name="{2}" data-contact_addr="{3}" data-when="{5}">
<img class="menu-img-3" src="{1}">
<span class="contactname">{2}</span>
<span class="dropdown-sub-text">{4}<br>{5}</span>
<span class="dropdown-sub-text">{4}</span><br>
<span class="dropdown-sub-text notifications-autotime" title="{5}">{5}</span>
</a>
</div>
<div id="nav-notifications-forums-template" rel="template">
<a class="list-group-item clearfix notification notification-forum" href="{0}" title="{4} - {3}" data-b64mid="{7}" data-notify_id="{8}" data-thread_top="{9}" data-contact_name="{2}" data-contact_addr="{3}">
<span class="float-right badge badge-{{$notification.severity}}">{10}</span>
<img class="menu-img-1" data-src="{1}">
<span class="float-right badge badge-secondary">{10}</span>
<img class="menu-img-1" src="{1}">
<span class="">{2}</span>
<i class="fa fa-{11} text-muted"></i>
</a>
......@@ -152,11 +153,14 @@
<div id="notifications" class="navbar-nav">
{{foreach $notifications as $notification}}
<div class="collapse {{$notification.type}}-button">
<a class="list-group-item notification-link" href="#" title="{{$notification.title}}" data-target="#nav-{{$notification.type}}-sub" data-toggle="collapse" data-type="{{$notification.type}}">
<a id="notification-link-{{$notification.type}}" class="collapsed list-group-item notification-link" href="#" title="{{$notification.title}}" data-target="#nav-{{$notification.type}}-sub" data-toggle="collapse" data-sse_type="{{$notification.type}}">
<i class="fa fa-fw fa-{{$notification.icon}}"></i> {{$notification.label}}
<span class="float-right badge badge-{{$notification.severity}} {{$notification.type}}-update"></span>
<div id="notifications-spinner-{{$notification.type}}" class="float-right spinner-wrapper">
<div class="spinner s"></div>
</div>
</a>
<div id="nav-{{$notification.type}}-sub" class="collapse notification-content" data-parent="#notifications" data-type="{{$notification.type}}">
<div id="nav-{{$notification.type}}-sub" class="collapse notification-content" data-parent="#notifications" data-sse_type="{{$notification.type}}">
{{if $notification.viewall}}
<a class="list-group-item text-dark" id="nav-{{$notification.type}}-see-all" href="{{$notification.viewall.url}}">
<i class="fa fa-fw fa-external-link"></i> {{$notification.viewall.label}}
......@@ -176,14 +180,16 @@
{{if $notification.filter.name_label}}
<div class="list-group-item clearfix notifications-textinput" id="cn-{{$notification.type}}-only">
<div class="text-muted notifications-textinput-filter"><i class="fa fa-fw fa-filter"></i></div>
<input id="cn-{{$notification.type}}-input" type="text" class="form-control form-control-sm" placeholder="{{$notification.filter.name_label}}">
<input id="cn-{{$notification.type}}-input" type="text" class="notification-filter form-control form-control-sm" placeholder="{{$notification.filter.name_label}}">
<div id="cn-{{$notification.type}}-input-clear" class="text-muted notifications-textinput-clear d-none"><i class="fa fa-times"></i></div>
</div>
{{/if}}
{{/if}}
<div id="nav-{{$notification.type}}-menu" class="">
<div id="nav-{{$notification.type}}-menu"></div>
<div id="nav-{{$notification.type}}-loading" style="display: none;">
{{$loading}}<span class="jumping-dots"><span class="dot-1">.</span><span class="dot-2">.</span><span class="dot-3">.</span></span>
</div>
</div>
</div>
{{/foreach}}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment