diff options
author | Mario <mario@mariovavti.com> | 2022-02-09 09:50:08 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2022-02-09 09:50:08 +0000 |
commit | 3d318542cb182673b5eeb635916da29267b843c9 (patch) | |
tree | 88e24a6176589c4d56598e25fe07bac653fd6ffc | |
parent | 5bdc713afed394db3193a3b55cd8446cf614fb26 (diff) | |
parent | 4a8c3cdc6109c5515824082f57bdb801b13ef9e0 (diff) | |
download | volse-hubzilla-3d318542cb182673b5eeb635916da29267b843c9.tar.gz volse-hubzilla-3d318542cb182673b5eeb635916da29267b843c9.tar.bz2 volse-hubzilla-3d318542cb182673b5eeb635916da29267b843c9.zip |
Merge branch 'dev'
35 files changed, 445 insertions, 163 deletions
@@ -1,3 +1,25 @@ +Hubzilla 7.0.2 (2022-02-09) + - Update french templates + - Add charset to the PDO connection strings + - Introduce delete keytype for get_activitystreams_key() + - Fix PHP error in Daemon/Externals + - Improved actor cache handling + - Implement manual fetch of packed local links + - Add JSalmon data to the meta field instead of data in Lib/ActivityStreams + - Fix some PHP8.1 deprecation warnings + - Fix delivery report for likes not found in some cases + - Allow zotfinger to recurse through all known hublocs if the one we got does not exist (404) or got removed (410) + - Diaspora: improve relaying of comments + - Fix regression in mod hcard + - Add the LD signature in Daemon/Notifier in case where there is no signed data available + - Prevent zot6 packet being saved as AP raw message + - Attach iconfig to the activity instead of the activity object + + Addons + - Pubcrawl: make sure the sys channel falls through the app installed check + - Pubcrawl: improve local delivery of shared inbox items + + Hubzilla 7.0.1 (2022-01-28) - Fix removing contacts from privacy groups in the contact edit modal - Fix escape_tags() messing with URLs in actor_store() diff --git a/Zotlabs/Daemon/Externals.php b/Zotlabs/Daemon/Externals.php index 81414d02d..fb35a65c7 100644 --- a/Zotlabs/Daemon/Externals.php +++ b/Zotlabs/Daemon/Externals.php @@ -133,7 +133,9 @@ class Externals { continue; } - Libzot::fetch_conversation($importer, $message['object']['id']); + $obj_id = isset($message['object']['id']) ?? $message['object']; + + Libzot::fetch_conversation($importer, $obj_id); $total++; continue; } diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index 7964621c7..8aee08fe6 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -5,6 +5,7 @@ namespace Zotlabs\Daemon; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Activity; use Zotlabs\Lib\Queue; +use Zotlabs\Lib\LDSignatures; require_once('include/html2plain.php'); require_once('include/conversation.php'); @@ -336,12 +337,14 @@ class Notifier { self::$encoded_item = json_decode($m, true); } else { + self::$encoded_item = array_merge(['@context' => [ ACTIVITYSTREAMS_JSONLD_REV, 'https://w3id.org/security/v1', z_root() . ZOT_APSCHEMA_REV ]], Activity::encode_activity($target_item) ); + self::$encoded_item['signature'] = LDSignatures::sign(self::$encoded_item, self::$channel); } logger('target_item: ' . print_r($target_item, true), LOGGER_DEBUG); diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 74a146345..e51922c86 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -116,6 +116,11 @@ class Activity { $y = json_decode($x['body'], true); logger('returned: ' . json_encode($y, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOGGER_DEBUG); + + if (ActivityStreams::is_an_actor($y['type'])) { + XConfig::Set($y['id'], 'system', 'actor_record', $y); + } + return json_decode($x['body'], true); } else { @@ -529,6 +534,7 @@ class Activity { $top_level = (($i['mid'] === $i['parent_mid']) ? true : false); if ($public) { + $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; $ret['cc'] = [z_root() . '/followers/' . substr($i['author']['xchan_addr'], 0, strpos($i['author']['xchan_addr'], '@'))]; } @@ -660,11 +666,11 @@ class Activity { return $ret; } - static function encode_attachment($item) { + static function encode_attachment($item, $iconfig = false) { $ret = []; - if (array_key_exists('attach', $item)) { + if (!$iconfig && array_key_exists('attach', $item)) { $atts = ((is_array($item['attach'])) ? $item['attach'] : json_decode($item['attach'], true)); if ($atts) { foreach ($atts as $att) { @@ -677,7 +683,7 @@ class Activity { } } } - if (array_key_exists('iconfig', $item) && is_array($item['iconfig'])) { + if ($iconfig && array_key_exists('iconfig', $item) && is_array($item['iconfig'])) { foreach ($item['iconfig'] as $att) { if ($att['sharing']) { $value = ((is_string($att['v']) && preg_match('|^a:[0-9]+:{.*}$|s', $att['v'])) ? unserialize($att['v']) : $att['v']); @@ -930,6 +936,11 @@ class Activity { $ret['tag'] = $t; } + $a = self::encode_attachment($i, true); + if ($a) { + $ret['attachment'] = $a; + } + // addressing madness $public = (($i['item_private']) ? false : true); @@ -2638,33 +2649,76 @@ class Activity { } } - $zot_rawmsg = ''; + $ap_rawmsg = ''; + $diaspora_rawmsg = ''; $raw_arr = []; $raw_arr = json_decode($act->raw, true); - // This is a zot6 packet and the raw activitypub message json + // This is a zot6 packet and the raw activitypub or diaspora message json // is possibly available in the attachement. - if (array_key_exists('signed', $raw_arr) && is_array($act->obj) && is_array($act->obj['attachment'])) { + if (array_key_exists('signed', $raw_arr) && is_array($act->data['attachment'])) { + foreach($act->data['attachment'] as $a) { + if ( + isset($a['type']) && $a['type'] === 'PropertyValue' && + isset($a['name']) && $a['name'] === 'zot.activitypub.rawmsg' && + isset($a['value']) + ) { + $ap_rawmsg = $a['value']; + } + if ( + isset($a['type']) && $a['type'] === 'PropertyValue' && + isset($a['name']) && $a['name'] === 'zot.diaspora.fields' && + isset($a['value']) + ) { + $diaspora_rawmsg = $a['value']; + } + } + } + + // old style: can be removed after most hubs are on 7.0.2 + elseif (array_key_exists('signed', $raw_arr) && is_array($act->obj) && is_array($act->obj['attachment'])) { foreach($act->obj['attachment'] as $a) { if ( isset($a['type']) && $a['type'] === 'PropertyValue' && isset($a['name']) && $a['name'] === 'zot.activitypub.rawmsg' && isset($a['value']) ) { - $zot_rawmsg = $a['value']; - break; + $ap_rawmsg = $a['value']; + } + + if ( + isset($a['type']) && $a['type'] === 'PropertyValue' && + isset($a['name']) && $a['name'] === 'zot.diaspora.fields' && + isset($a['value']) + ) { + $diaspora_rawmsg = $a['value']; } } } - if ($zot_rawmsg) { - set_iconfig($s, 'activitypub', 'rawmsg', $zot_rawmsg, 1); + // catch the likes + if (!$ap_rawmsg && $response_activity) { + $ap_rawmsg = json_encode($act->data, JSON_UNESCAPED_SLASHES); } - else { + // end old style + + if (!$ap_rawmsg && array_key_exists('signed', $raw_arr)) { + // zap + $ap_rawmsg = json_encode($act->data, JSON_UNESCAPED_SLASHES); + } + + if ($ap_rawmsg) { + set_iconfig($s, 'activitypub', 'rawmsg', $ap_rawmsg, 1); + } + elseif (!array_key_exists('signed', $raw_arr)) { set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1); } + if ($diaspora_rawmsg) { + set_iconfig($s, 'diaspora', 'fields', $diaspora_rawmsg, 1); + } + set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); $hookinfo = [ @@ -3660,7 +3714,10 @@ class Activity { } static function get_cached_actor($id) { - $actor = XConfig::Get($id, 'system', 'actor_record'); + + // remove any fragments like #main-key since these won't be present in our cached data + $cache_url = ((strpos($id, '#')) ? substr($id, 0, strpos($id, '#')) : $id); + $actor = XConfig::Get($cache_url, 'system', 'actor_record'); if ($actor) { return $actor; @@ -3669,7 +3726,7 @@ class Activity { // try other get_cached_actor providers (e.g. diaspora) $hookdata = [ 'id' => $id, - 'actor' => false + 'actor' => null ]; call_hooks('get_cached_actor_provider', $hookdata); diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php index 323b0e273..275f6eff4 100644 --- a/Zotlabs/Lib/ActivityStreams.php +++ b/Zotlabs/Lib/ActivityStreams.php @@ -11,6 +11,7 @@ class ActivityStreams { public $raw = null; public $data = null; + public $meta = null; public $valid = false; public $deleted = false; public $id = ''; @@ -36,6 +37,9 @@ class ActivityStreams { */ function __construct($string) { + if(!$string) + return; + $this->raw = $string; if (is_array($string)) { @@ -57,10 +61,10 @@ class ActivityStreams { if ($ret['signer']) { $saved = json_encode($this->data, JSON_UNESCAPED_SLASHES); $this->data = $tmp; - $this->data['signer'] = $ret['signer']; - $this->data['signed_data'] = $saved; + $this->meta['signer'] = $ret['signer']; + $this->meta['signed_data'] = $saved; if ($ret['hubloc']) { - $this->data['hubloc'] = $ret['hubloc']; + $this->meta['hubloc'] = $ret['hubloc']; } } } @@ -88,7 +92,7 @@ class ActivityStreams { $this->ldsig = $this->get_compound_property('signature'); if ($this->ldsig) { - $this->signer = $this->get_compound_property('creator', $this->ldsig); + $this->signer = $this->get_actor('creator', $this->ldsig); if ($this->signer && is_array($this->signer) && array_key_exists('publicKey', $this->signer) && is_array($this->signer['publicKey']) && $this->signer['publicKey']['publicKeyPem']) { $this->sigok = LDSignatures::verify($this->data, $this->signer['publicKey']['publicKeyPem']); } @@ -344,10 +348,10 @@ class ActivityStreams { if ($ret['signer']) { $saved = json_encode($x, JSON_UNESCAPED_SLASHES); $x = $tmp; - $x['signer'] = $ret['signer']; - $x['signed_data'] = $saved; + $x['meta']['signer'] = $ret['signer']; + $x['meta']['signed_data'] = $saved; if ($ret['hubloc']) { - $x['hubloc'] = $ret['hubloc']; + $x['meta']['hubloc'] = $ret['hubloc']; } } } diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index a6b5c192c..98ebc546a 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -426,7 +426,7 @@ class Apps { self::translate_system_apps($papp); - if(trim($papp['plugin']) && (! plugin_is_installed(trim($papp['plugin'])))) + if(isset($papp['plugin']) && trim($papp['plugin']) && (! plugin_is_installed(trim($papp['plugin'])))) return ''; $papp['papp'] = self::papp_encode($papp); diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index f340514f0..e4d8d2275 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -1296,9 +1296,8 @@ class Libzot { } } } - - if ($AS->data['signed_data']) { - IConfig::Set($arr, 'activitypub', 'signed_data', $AS->data['signed_data'], false); + if ($AS->meta['signed_data']) { + IConfig::Set($arr, 'activitypub', 'signed_data', $AS->meta['signed_data'], false); } logger('Activity received: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); @@ -1927,6 +1926,7 @@ class Libzot { dbesc($a['signature']['signer']) ); + foreach ($items as $activity) { $AS = new ActivityStreams($activity); @@ -1988,9 +1988,9 @@ class Libzot { $arr['item_verified'] = true; } - if ($AS->data['signed_data']) { - IConfig::Set($arr, 'activitypub', 'signed_data', $AS->data['signed_data'], false); - $j = json_decode($AS->data['signed_data'], true); + if ($AS->meta['signed_data']) { + IConfig::Set($arr, 'activitypub', 'signed_data', $AS->meta['signed_data'], false); + $j = json_decode($AS->meta['signed_data'], true); if ($j) { IConfig::Set($arr, 'activitypub', 'rawmsg', json_encode(JSalmon::unpack($j['data'])), true); } @@ -2482,14 +2482,14 @@ class Libzot { $access_policy = ACCESS_PRIVATE; } - $directory_url = htmlspecialchars($arr['directory_url'], ENT_COMPAT, 'UTF-8', false); - $url = htmlspecialchars(strtolower($arr['url']), ENT_COMPAT, 'UTF-8', false); - $sellpage = htmlspecialchars($arr['sellpage'], ENT_COMPAT, 'UTF-8', false); - $site_location = htmlspecialchars($arr['location'], ENT_COMPAT, 'UTF-8', false); - $site_realm = htmlspecialchars($arr['realm'], ENT_COMPAT, 'UTF-8', false); - $site_project = htmlspecialchars($arr['project'], ENT_COMPAT, 'UTF-8', false); - $site_crypto = ((array_key_exists('encryption', $arr) && is_array($arr['encryption'])) ? htmlspecialchars(implode(',', $arr['encryption']), ENT_COMPAT, 'UTF-8', false) : ''); - $site_version = ((array_key_exists('version', $arr)) ? htmlspecialchars($arr['version'], ENT_COMPAT, 'UTF-8', false) : ''); + $directory_url = htmlspecialchars((string)$arr['directory_url'], ENT_COMPAT, 'UTF-8', false); + $url = htmlspecialchars((string)strtolower($arr['url']), ENT_COMPAT, 'UTF-8', false); + $sellpage = htmlspecialchars((string)$arr['sellpage'], ENT_COMPAT, 'UTF-8', false); + $site_location = htmlspecialchars((string)$arr['location'], ENT_COMPAT, 'UTF-8', false); + $site_realm = htmlspecialchars((string)$arr['realm'], ENT_COMPAT, 'UTF-8', false); + $site_project = htmlspecialchars((string)$arr['project'], ENT_COMPAT, 'UTF-8', false); + $site_crypto = ((array_key_exists('encryption', $arr) && is_array($arr['encryption'])) ? htmlspecialchars((string)implode(',', $arr['encryption']), ENT_COMPAT, 'UTF-8', false) : ''); + $site_version = ((array_key_exists('version', $arr)) ? htmlspecialchars((string)$arr['version'], ENT_COMPAT, 'UTF-8', false) : ''); // You can have one and only one primary directory per realm. // Downgrade any others claiming to be primary. As they have @@ -3172,7 +3172,7 @@ class Libzot { } static function update_cached_hubloc($hubloc) { - if ($hubloc['hubloc_updated'] > datetime_convert('UTC','UTC','now - 1 week') || $hubloc['hubloc_url'] === z_root()) { + if ($hubloc['hubloc_updated'] > datetime_convert('UTC','UTC','now - 3 days') || $hubloc['hubloc_url'] === z_root()) { return; } self::refresh( [ 'hubloc_id_url' => $hubloc['hubloc_id_url'] ] ); diff --git a/Zotlabs/Lib/Zotfinger.php b/Zotlabs/Lib/Zotfinger.php index 840d91403..fa57ab56d 100644 --- a/Zotlabs/Lib/Zotfinger.php +++ b/Zotlabs/Lib/Zotfinger.php @@ -6,7 +6,7 @@ use Zotlabs\Web\HTTPSig; class Zotfinger { - static function exec($resource,$channel = null, $verify = true) { + static function exec($resource, $channel = null, $verify = true, $recurse = true) { if(! $resource) { return false; @@ -39,6 +39,30 @@ class Zotfinger { logger('fetch: ' . print_r($x,true)); + if (in_array(intval($x['return_code']), [ 404, 410 ]) && $recurse) { + + // The resource has been deleted or doesn't exist at this location. + // Try to find another nomadic resource for this channel and return that. + + // First, see if there's a hubloc for this site. Fetch that record to + // obtain the nomadic identity hash. Then use that to find any additional + // nomadic locations. + + $h = Activity::get_actor_hublocs($resource, 'zot6'); + if ($h) { + // mark this location deleted + hubloc_delete($h[0]); + $hubs = Activity::get_actor_hublocs($h[0]['hubloc_hash']); + if ($hubs) { + foreach ($hubs as $hub) { + if ($hub['hubloc_id_url'] !== $resource && !$hub['hubloc_deleted']) { + return self::exec($hub['hubloc_id_url'], $channel, $verify); + } + } + } + } + } + if($x['success']) { if ($verify) { $result['signature'] = HTTPSig::verify($x, EMPTY_STR, 'zot6'); diff --git a/Zotlabs/Module/Admin/Queue.php b/Zotlabs/Module/Admin/Queue.php index baa50591f..8a843083b 100644 --- a/Zotlabs/Module/Admin/Queue.php +++ b/Zotlabs/Module/Admin/Queue.php @@ -23,7 +23,18 @@ class Queue { LibQueue::remove_by_posturl($_REQUEST['emptyhub']); } - $r = q("select count(outq_posturl) as total, max(outq_priority) as priority, outq_posturl from outq + if($_REQUEST['deliverhub']) { + + $hubq = q("SELECT * FROM outq WHERE outq_posturl = '%s'", + dbesc($_REQUEST['deliverhub']) + ); + + foreach ($hubq as $q) { + LibQueue::deliver($q, true); + } + } + + $r = dbq("select count(outq_posturl) as total, max(outq_priority) as priority, outq_posturl from outq where outq_delivered = 0 group by outq_posturl order by total desc"); for($x = 0; $x < count($r); $x ++) { @@ -37,6 +48,7 @@ class Queue { '$priority' => t('Priority'), '$desturl' => t('Destination URL'), '$nukehub' => t('Mark hub permanently offline'), + '$deliverhub' => t('Retry delivery to this hub'), '$empty' => t('Empty queue for this hub'), '$lastconn' => t('Last known contact'), '$hasentries' => ((count($r)) ? true : false), diff --git a/Zotlabs/Module/Dreport.php b/Zotlabs/Module/Dreport.php index 42f337b76..d6f4e5979 100644 --- a/Zotlabs/Module/Dreport.php +++ b/Zotlabs/Module/Dreport.php @@ -56,9 +56,10 @@ class Dreport extends \Zotlabs\Web\Controller { return; } - $r = q("select * from dreport where dreport_xchan = '%s' and dreport_mid = '%s'", + $r = q("select * from dreport where dreport_xchan = '%s' and (dreport_mid = '%s' or dreport_mid = '%s')", dbesc($channel['channel_hash']), - dbesc($mid) + dbesc($mid), + dbesc(str_replace('/item/', '/activity/', $mid)) ); if(! $r) { diff --git a/Zotlabs/Module/Hcard.php b/Zotlabs/Module/Hcard.php index 912c84fd2..1cc26c199 100644 --- a/Zotlabs/Module/Hcard.php +++ b/Zotlabs/Module/Hcard.php @@ -5,7 +5,7 @@ namespace Zotlabs\Module; class Hcard extends \Zotlabs\Web\Controller { function init() { - + if(argc() > 1) $which = argv(1); else { @@ -13,12 +13,12 @@ class Hcard extends \Zotlabs\Web\Controller { \App::$error = 404; return; } - + logger('hcard_request: ' . $which, LOGGER_DEBUG); $profile = ''; $channel = \App::get_channel(); - + if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) { $which = $channel['channel_address']; $profile = argv(1); @@ -30,22 +30,22 @@ class Hcard extends \Zotlabs\Web\Controller { $profile = ''; $profile = $r[0]['profile_guid']; } - - head_add_link( [ - 'rel' => 'alternate', + + head_add_link( [ + 'rel' => 'alternate', 'type' => 'application/atom+xml', 'title' => t('Posts and comments'), 'href' => z_root() . '/feed/' . $which ]); - head_add_link( [ - 'rel' => 'alternate', + head_add_link( [ + 'rel' => 'alternate', 'type' => 'application/atom+xml', 'title' => t('Only posts'), 'href' => z_root() . '/feed/' . $which . '?f=&top=1' ]); - + if(! $profile) { $x = q("select channel_id as profile_uid from channel where channel_address = '%s' limit 1", dbesc(argv(1)) @@ -54,20 +54,20 @@ class Hcard extends \Zotlabs\Web\Controller { \App::$profile = $x[0]; } } - + profile_load($which,$profile); - - + + } - - + + function get() { - $x = new \Zotlabs\Widget\Profile(); + $x = new \Zotlabs\Widget\Fullprofile(); return $x->widget(array()); - + } - - - + + + } diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 41979006e..41a4e120d 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -358,7 +358,7 @@ class Item extends Controller { $consensus = intval($_REQUEST['consensus']); $nocomment = intval($_REQUEST['nocomment']); - $is_poll = ((trim($_REQUEST['poll_answers'][0]) != '' && trim($_REQUEST['poll_answers'][1]) != '') ? true : false); + $is_poll = ((trim((string)$_REQUEST['poll_answers'][0]) != '' && trim((string)$_REQUEST['poll_answers'][1]) != '') ? true : false); // 'origin' (if non-zero) indicates that this network is where the message originated, // for the purpose of relaying comments to other conversation members. @@ -719,13 +719,13 @@ class Item extends Controller { } - $location = notags(trim($_REQUEST['location'])); - $coord = notags(trim($_REQUEST['coord'])); - $verb = notags(trim($_REQUEST['verb'])); - $title = escape_tags(trim($_REQUEST['title'])); - $summary = trim($_REQUEST['summary']); - $body = trim($_REQUEST['body']); - $body .= trim($_REQUEST['attachment']); + $location = notags(trim((string)$_REQUEST['location'])); + $coord = notags(trim((string)$_REQUEST['coord'])); + $verb = notags(trim((string)$_REQUEST['verb'])); + $title = escape_tags(trim((string)$_REQUEST['title'])); + $summary = trim((string)$_REQUEST['summary']); + $body = trim((string)$_REQUEST['body']); + $body .= trim((string)$_REQUEST['attachment']); $postopts = ''; $allow_empty = ((array_key_exists('allow_empty', $_REQUEST)) ? intval($_REQUEST['allow_empty']) : 0); @@ -764,7 +764,7 @@ class Item extends Controller { } - $mimetype = notags(trim($_REQUEST['mimetype'])); + $mimetype = notags(trim((string)$_REQUEST['mimetype'])); if (!$mimetype) $mimetype = 'text/bbcode'; diff --git a/Zotlabs/Module/Search.php b/Zotlabs/Module/Search.php index 5db0ce423..fdc251b07 100644 --- a/Zotlabs/Module/Search.php +++ b/Zotlabs/Module/Search.php @@ -7,6 +7,7 @@ use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Activity; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Web\Controller; +use Zotlabs\Lib\Zotfinger; class Search extends Controller { @@ -58,10 +59,26 @@ class Search extends Controller { $o .= search($search, 'search-box', '/search', ((local_channel()) ? true : false)); if (local_channel() && strpos($search, 'https://') === 0 && !$update && !$load) { + if (strpos($search, 'b64.') !== false) { + if (strpos($search, '?') !== false) { + $search = strtok($search, '?'); + } + + $search = unpack_link_id(basename($search)); + } + $f = Libzot::fetch_conversation(App::get_channel(), punify($search), true); if ($f) { - goaway(z_root() . '/hq/' . gen_link_id($f['message_id'])); + $mid = $f[0]['message_id']; + foreach ($f as $m) { + if (strpos($search, $m['message_id']) === 0) { + $mid = $m['message_id']; + break; + } + } + + goaway(z_root() . '/hq/' . gen_link_id($mid)); } else { // try other fetch providers (e.g. diaspora, pubcrawl) diff --git a/Zotlabs/Render/Comanche.php b/Zotlabs/Render/Comanche.php index 5ce05243b..230c2455e 100644 --- a/Zotlabs/Render/Comanche.php +++ b/Zotlabs/Render/Comanche.php @@ -572,7 +572,7 @@ class Comanche { require_once('widget/' . trim($name) . '.php'); elseif(file_exists('widget/' . trim($name) . '/' . trim($name) . '.php')) require_once('widget/' . trim($name) . '/' . trim($name) . '.php'); - + if(! function_exists($func)) { $theme_widget = $func . '.php'; if(theme_include($theme_widget)) { @@ -640,7 +640,8 @@ class Comanche { $cnt = preg_match_all("/\[widget=(.*?)\](.*?)\[\/widget\]/ism", $s, $matches, PREG_SET_ORDER); if($cnt) { foreach($matches as $mtch) { - $s = str_replace($mtch[0],$this->widget(trim($mtch[1]),$mtch[2]),$s); + + $s = str_replace((string)$mtch[0], (string)$this->widget(trim((string)$mtch[1]), (string)$mtch[2]), $s); } } diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index 7da9acabf..a7a59b8cf 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -156,6 +156,7 @@ class HTTPSig { $cached_key = self::get_key($key, $keytype, $result['signer']); + if (!($cached_key && $cached_key['public_key'])) { return $result; } @@ -243,7 +244,12 @@ class HTTPSig { } } - $key = self::get_activitystreams_key($id, $force); + $delete = false; + if ($keytype === 'delete') { + $delete = true; + } + + $key = self::get_activitystreams_key($id, $force, $delete); return $key; @@ -274,7 +280,7 @@ class HTTPSig { * false if no pub key found, otherwise return an array with the pub key */ - static function get_activitystreams_key($id, $force = false) { + static function get_activitystreams_key($id, $force = false, $delete = false) { // Check the local cache first, but remove any fragments like #main-key since these won't be present in our cached data $url = ((strpos($id, '#')) ? substr($id, 0, strpos($id, '#')) : $id); @@ -294,6 +300,12 @@ class HTTPSig { } } + if ($delete) { + // If we received a delete and we do not have the record cached, + // we probably never saw this actor. Do not try to fetch it now. + return false; + } + // The record wasn't in cache. Fetch it now. $r = ActivityStreams::fetch($id); diff --git a/Zotlabs/Web/SessionHandler.php b/Zotlabs/Web/SessionHandler.php index 4292fdc28..392cab1ae 100644 --- a/Zotlabs/Web/SessionHandler.php +++ b/Zotlabs/Web/SessionHandler.php @@ -6,7 +6,7 @@ namespace Zotlabs\Web; class SessionHandler implements \SessionHandlerInterface { - function open ($s, $n) { + function open ($s, $n) : bool { return true; } @@ -15,7 +15,7 @@ class SessionHandler implements \SessionHandlerInterface { // some which call read explicitly and some that do not. So we call it explicitly // just after sid regeneration to force a record to exist. - function read ($id) { + function read ($id) : string|false { if($id) { $r = q("SELECT sess_data FROM session WHERE sid= '%s'", dbesc($id)); @@ -36,7 +36,7 @@ class SessionHandler implements \SessionHandlerInterface { } - function write ($id, $data) { + function write ($id, $data) : bool { // Pretend everything is hunky-dory, even though it isn't. // There probably isn't anything we can do about it in any event. @@ -49,9 +49,9 @@ class SessionHandler implements \SessionHandlerInterface { // Unless we authenticate somehow, only keep a session for 5 minutes // The viewer can extend this by performing any web action using the - // original cookie, but this allows us to cleanup the hundreds or + // original cookie, but this allows us to cleanup the hundreds or // thousands of empty sessions left around from web crawlers which are - // assigned cookies on each page that they never use. + // assigned cookies on each page that they never use. $expire = time() + 300; @@ -74,19 +74,19 @@ class SessionHandler implements \SessionHandlerInterface { return true; } - - function close() { + + function close() : bool { return true; } - function destroy ($id) { + function destroy ($id) : bool { q("DELETE FROM session WHERE sid = '%s'", dbesc($id)); return true; } - function gc($expire) { + function gc($expire) : int|false { q("DELETE FROM session WHERE expire < %d", dbesc(time())); return true; } @@ -60,7 +60,7 @@ require_once('include/bbcode.php'); require_once('include/items.php'); define('PLATFORM_NAME', 'hubzilla'); -define('STD_VERSION', '7.0.1'); +define('STD_VERSION', '7.1.2'); define('ZOT_REVISION', '6.0'); define('DB_UPDATE_VERSION', 1252); diff --git a/include/contact_widgets.php b/include/contact_widgets.php index 875a519a5..1ae8b17c5 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -100,6 +100,10 @@ function categories_widget($baseurl,$selected = '') { \Zotlabs\Daemon\Master::Summon([ 'Cache_query', $key, base64_encode(json_encode($arr)) ]); } + if (!$content) { + return EMPTY_STR; + } + $r = unserialize($content); $terms = []; diff --git a/include/conversation.php b/include/conversation.php index 17367856c..c0238b8ae 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -558,7 +558,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa $page_writeable = ($profile_owner == local_channel()); if (!$update) { - $tab = notags(trim($_GET['tab'])); + $tab = notags(trim((string)$_GET['tab'])); if ($tab === 'posts') { // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, // because browser prefetching might change it on us. We have to deliver it with the page. @@ -974,7 +974,7 @@ function best_link_url($item) { } } if(! $best_url) { - if(strlen($item['author-link'])) + if($item['author-link']) $best_url = $item['author-link']; else $best_url = $item['url']; diff --git a/include/dba/dba_pdo.php b/include/dba/dba_pdo.php index 49f741601..2137ca0cc 100644 --- a/include/dba/dba_pdo.php +++ b/include/dba/dba_pdo.php @@ -27,6 +27,13 @@ class dba_pdo extends dba_driver { $dsn .= ';dbname=' . $db; + if ($this->driver_dbtype === 'mysql') { + $dsn .= ';charset=utf8mb4'; + } + else { + $dsn .= ";options='--client_encoding=UTF8'"; + } + try { $this->db = new PDO($dsn,$user,$pass); $this->db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); diff --git a/include/feedutils.php b/include/feedutils.php index d31836983..1c653325d 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -1180,7 +1180,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { else { $name = $author['author_name']; } - $x = import_author_unknown(array('name' => $name,'url' => $author['author_link'],'photo' => array('src' => $author['author_photo']))); + $x = import_author_rss(array('name' => $name,'url' => $author['author_link'],'photo' => array('src' => $author['author_photo']))); if($x) $datarray['author_xchan'] = $x; } @@ -1440,7 +1440,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { else { $name = $author['author_name']; } - $x = import_author_unknown(array('name' => $name,'url' => $author['author_link'],'photo' => array('src' => $author['author_photo']))); + $x = import_author_rss(array('name' => $name,'url' => $author['author_link'],'photo' => array('src' => $author['author_photo']))); if($x) $datarray['author_xchan'] = $x; } diff --git a/include/hubloc.php b/include/hubloc.php index 2cce7a725..6401d1f0d 100644 --- a/include/hubloc.php +++ b/include/hubloc.php @@ -16,6 +16,8 @@ use Zotlabs\Daemon\Master; */ function hubloc_store_lowlevel($arr) { + $update = ((array_key_exists('hubloc_id',$arr) && $arr['hubloc_id']) ? 'hubloc_id = ' . intval($arr['hubloc_id']) : false); + $store = [ 'hubloc_guid' => ((array_key_exists('hubloc_guid',$arr)) ? $arr['hubloc_guid'] : ''), 'hubloc_guid_sig' => ((array_key_exists('hubloc_guid_sig',$arr)) ? $arr['hubloc_guid_sig'] : ''), @@ -40,7 +42,7 @@ function hubloc_store_lowlevel($arr) { 'hubloc_deleted' => ((array_key_exists('hubloc_deleted',$arr)) ? $arr['hubloc_deleted'] : 0) ]; - return create_table_from_array('hubloc', $store); + return (($update) ? update_table_from_array('hubloc', $store, $update) : create_table_from_array('hubloc', $store)); } function site_store_lowlevel($arr) { @@ -283,6 +285,13 @@ function hubloc_change_primary($hubloc) { return true; } +function hubloc_delete($hubloc) { + if (is_array($hubloc) && array_key_exists('hubloc_id', $hubloc)) { + q("UPDATE hubloc SET hubloc_deleted = 1 WHERE hubloc_id = %d", + intval($hubloc['hubloc_id']) + ); + } +} /** * @brief Mark a hubloc as down. diff --git a/include/items.php b/include/items.php index 822e2d88b..0b33d876d 100644 --- a/include/items.php +++ b/include/items.php @@ -1446,7 +1446,7 @@ function activity_sanitise($arr) { if(is_array($x)) $ret[$k] = activity_sanitise($x); else - $ret[$k] = htmlspecialchars($x, ENT_COMPAT, 'UTF-8', false); + $ret[$k] = htmlspecialchars((string)$x, ENT_COMPAT, 'UTF-8', false); } return $ret; } @@ -1625,9 +1625,9 @@ function item_store($arr, $allow_exec = false, $deliver = true) { return $ret; } - $arr['title'] = ((array_key_exists('title',$arr) && strlen($arr['title'])) ? trim($arr['title']) : ''); - $arr['summary'] = ((array_key_exists('summary',$arr) && strlen($arr['summary'])) ? trim($arr['summary']) : ''); - $arr['body'] = ((array_key_exists('body',$arr) && strlen($arr['body'])) ? trim($arr['body']) : ''); + $arr['title'] = ((array_key_exists('title',$arr) && $arr['title']) ? trim($arr['title']) : ''); + $arr['summary'] = ((array_key_exists('summary',$arr) && $arr['summary']) ? trim($arr['summary']) : ''); + $arr['body'] = ((array_key_exists('body',$arr) && $arr['body']) ? trim($arr['body']) : ''); $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : ''); $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : ''); @@ -4438,7 +4438,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C ); } - if(strlen($arr['file'])) { + if($arr['file']) { $sql_extra .= term_query('item',$arr['files'],TERM_FILE); } diff --git a/include/text.php b/include/text.php index aea8790fc..29a2ab3b1 100644 --- a/include/text.php +++ b/include/text.php @@ -417,6 +417,9 @@ function xmlify($str) { //$buffer = ''; + if (!$str) + return EMPTY_STR; + if(is_array($str)) { // allow to fall through so we ge a PHP error, as the log statement will @@ -482,6 +485,9 @@ function unxmlify($s) { return $ret; */ + if (!$s) + return EMPTY_STR; + if(is_array($s)) { // allow to fall through so we ge a PHP error, as the log statement will @@ -840,7 +846,7 @@ function activity_match($haystack,$needle) { if($needle) { foreach($needle as $n) { - if(($haystack === $n) || (strtolower(basename($n)) === strtolower(basename($haystack)))) { + if(($haystack === $n) || (strtolower(basename((string)$n)) === strtolower(basename((string)$haystack)))) { return true; } } @@ -1492,6 +1498,10 @@ function day_translate($s) { * @return string */ function normalise_link($url) { + if (!$url) { + return EMPTY_STR; + } + $ret = str_replace(array('https:', '//www.'), array('http:', '//'), $url); return(rtrim($ret, '/')); @@ -3588,6 +3598,45 @@ function create_table_from_array($table, $arr, $binary_fields = []) { return $r; } + +function update_table_from_array($table, $arr, $where, $binary_fields = []) { + + if (! ($arr && $table)) { + return false; + } + + $columns = db_columns($table); + + $clean = []; + foreach ($arr as $k => $v) { + if (! in_array($k, $columns)) { + continue; + } + + $matches = false; + if (preg_match('/([^a-zA-Z0-9\-\_\.])/', $k, $matches)) { + return false; + } + if (in_array($k, $binary_fields)) { + $clean[$k] = dbescbin($v); + } else { + $clean[$k] = dbesc($v); + } + } + + $sql = "UPDATE " . TQUOT . $table . TQUOT . " SET "; + + foreach ($clean as $k => $v) { + $sql .= TQUOT . $k . TQUOT . ' = "' . $v . '",'; + } + + $sql = rtrim($sql,','); + + $r = dbq($sql . " WHERE " . $where); + + return $r; +} + function share_shield($m) { return str_replace($m[1],'!=+=+=!' . base64url_encode($m[1]) . '=+!=+!=',$m[0]); } diff --git a/include/zid.php b/include/zid.php index e462f8357..ae7d9e252 100644 --- a/include/zid.php +++ b/include/zid.php @@ -37,7 +37,7 @@ function is_matrix_url($url) { * @return string */ function zid($s, $address = '') { - if (! strlen($s) || strpos($s,'zid=')) + if (!$s || strpos($s,'zid=')) return $s; $m = parse_url($s); diff --git a/view/fr/cert_bad_eml.tpl b/view/fr/cert_bad_eml.tpl new file mode 100644 index 000000000..c99c515ac --- /dev/null +++ b/view/fr/cert_bad_eml.tpl @@ -0,0 +1,9 @@ +Il s'agit du serveur web de {{$sitename}} ; + +Une vérification de routine indique que le certificat SSL de ce site web n'est pas valide. + +Votre site web ne peut pas fonctionner correctement avec Hubzilla tant que ce problème n'est pas résolu. Veuillez vérifier votre certificat et avec votre fournisseur de certificat ou votre fournisseur de service pour vous assurer qu'il est "valide pour le navigateur" et installé correctement. Les certificats auto-signés ne sont PAS SUPPORTÉS et NE SONT PAS AUTORISÉS dans Hubzilla. La vérification est effectuée en récupérant une URL de votre site web avec une vérification SSL stricte activée, et si cela échoue, une nouvelle vérification est effectuée avec des vérifications SSL désactivées. Il est possible qu'une erreur transitoire produise ce message, mais si des changements récents de configuration ont été effectués, ou si vous recevez ce message plus d'une fois, veuillez vérifier votre certificat. Le message d'erreur est "{{$error}}". + +Veuillez nous excuser pour ce désagrément, + +Votre serveur web à {{$siteurl}} diff --git a/view/fr/cron_bad_eml.tpl b/view/fr/cron_bad_eml.tpl new file mode 100644 index 000000000..64b5f9087 --- /dev/null +++ b/view/fr/cron_bad_eml.tpl @@ -0,0 +1,13 @@ +Ceci est le serveur web de {{$sitename}} ; + +Une vérification de routine indique que les tâches de maintenance programmées sur ce site ne sont pas exécutées. + +Veuillez vérifier vos tâches "cron" ou le mécanisme équivalent sur votre serveur et assurez-vous qu'elles sont exécutées. Consultez les instructions d'installation si vous voyez ce message pour la première fois. Si ces tâches de maintenance se sont déroulées normalement jusqu'à présent, vérifiez que rien ne s'est produit pour expliquer leur arrêt. Cette vérification est effectuée tous les trois jours environ. + +Le message d'erreur est "{{$error}}". + +La dernière exécution réussie remonte à "{{$lastdate}}". + +Veuillez nous excuser pour ce désagrément, + +votre serveur web à {{$siteurl}} diff --git a/view/fr/invite.casual.subject.tpl b/view/fr/invite.casual.subject.tpl new file mode 100644 index 000000000..b6f05bb7e --- /dev/null +++ b/view/fr/invite.casual.subject.tpl @@ -0,0 +1 @@ +Rejoignez-nous sur {{$projectname}} {{$invite_loc}} diff --git a/view/fr/invite.casual.tpl b/view/fr/invite.casual.tpl new file mode 100644 index 000000000..ff871e945 --- /dev/null +++ b/view/fr/invite.casual.tpl @@ -0,0 +1,18 @@ +{{* tpl FR, pour inviter les personnes qui me connaissant *}} + +Rejoingnez moi sur {{$projectname}}. +{{$linktxt}} {{$invite_where}} + +Vous devrez fournir ce code d'invitation : +{{$invite_code}} + +Ou bien : + +1) Enregistrez-vous à n'importe quel endroit de {{$projectname}} (ils sont tous interconnectés). +2) Saisissez mon adresse de réseau {{$projectname}} dans la barre de recherche du site. + +{{$invite_whereami}} + +ou visitez {{$invite_whoami}}. + +3) Cliquez sur [Connecter]. diff --git a/view/fr/invite.formal.subject.tpl b/view/fr/invite.formal.subject.tpl new file mode 100644 index 000000000..0fcab5ee7 --- /dev/null +++ b/view/fr/invite.formal.subject.tpl @@ -0,0 +1 @@ +Invitation pour votre accès au réseau {{$invite_loc}} diff --git a/view/fr/invite.formal.tpl b/view/fr/invite.formal.tpl new file mode 100644 index 000000000..404059af6 --- /dev/null +++ b/view/fr/invite.formal.tpl @@ -0,0 +1,19 @@ +{{* tpl FR formel, pour inviter de manière plus polie des personnes comme des membres d'entreprise ou des partenaires professionnels *}}. + +Veuillez rejoindre le réseau social {{$nom du projet}}. Ce message contient les données essentielles pour la première connexion. + +Le site est à rejoindre se trouve ici : +{{$invite_whereami}} + +Votre accès est juste préparé avec un code d'invitation : +{{$invite_code}} + +que vous devrez saisir dans le champ du formulaire d'inscription, qui s'affiche lorsque vous cliquez sur le lien "J'ai un code d'invitation". Veuillez également saisir votre adresse e-mail dans le champ suivant. Nous tenons à vous informer que le code d'invitation est lié à votre adresse électronique et n'est pas transférable. + +Pour des raisons de sécurité, vous devez fournir un mot de passe de compte qui n'est et ne doit rester connu que de vous. Le mot de passe non visible doit être tapé deux fois pour éviter toute erreur de frappe. Le mot de passe est toujours demandé ultérieurement lorsque vous voulez vous connecter au site. + +Selon la configuration du site, vous pouvez recevoir un autre courriel à votre adresse avec un code de validation, à saisir dans le formulaire vers lequel ce courriel vous dirigera. Ce genre de mesure contribue à renforcer la sécurité du site. + +Selon la configuration du site, un administrateur devra valider votre accès. Merci de patienter car cela ne se fera pas instantanément. + +Cordialement. diff --git a/view/fr/register_open_eml.tpl b/view/fr/register_open_eml.tpl index 46390bc9d..3656ab3bf 100644 --- a/view/fr/register_open_eml.tpl +++ b/view/fr/register_open_eml.tpl @@ -1,18 +1,14 @@ - -Un compte a été créé sur {{$sitename}} avec cette adresse de courriel. -Voici les détails de connexion: - -Emplacement du site:⇥{{$siteurl}} -Utilisateur:⇥{{$email}} -Mot de passe: (le mot de passe qui a été spécifié lors de l'enregistrement) - -Si ce compte a été créé sans votre accord, vous pouvez visiter ce site et réinitialiser le mot de passe. -Ceci vous permettra de supprimer le compte à partir de la page des réglages du profil. -Veuillez accepter nos excuses pour tous les désagréments engendrés. - -Merci et bienvenue sur {{$sitename}}. - -Sincèrement, - L'administrateur {{$sitename}} - -
\ No newline at end of file +Un compte a été créé sur {{$sitename}} avec cette adresse de courriel.
+Voici les détails de connexion:
+
+Adresse du site: {{$siteurl}}
+Utilisateur: {{$email}}
+Mot de passe: (le mot de passe qui a été spécifié lors de l'enregistrement)
+
+Si ce compte a été créé à votre insu et que vous n'en voulez pas, vous pouvez vous rendre sur ce site et réinitialiser le mot de passe. Cela vous permettra de supprimer le compte à partir des Paramètres / Paramètres du compte. Nous vous prions de nous excuser pour ce désagrément.
+
+Merci et bienvenue sur {{$sitename}}.
+
+L'administrateur {{$sitename}}
+
+
diff --git a/view/fr/register_verify_eml.tpl b/view/fr/register_verify_eml.tpl index 9d2be027c..6510515fe 100644 --- a/view/fr/register_verify_eml.tpl +++ b/view/fr/register_verify_eml.tpl @@ -1,24 +1,20 @@ - -Une demande d'enregistrement pour un nouvel utilisateur a été reçue par {{$sitename}} qui requiert -votre accord. - - -Voici les détails de connexion: - -Emplacement du site:⇥{{$siteurl}} -Utilisateur:⇥{{$email}} -Adresse IP: {{$details}} - -Pour confirmer votre accord, veuillez suivre le lien suivant: - - -{{$siteurl}}/regmod/allow/{{$hash}} - - -Pour supprimer ce compte, veuillez visiter: - - -{{$siteurl}}/regmod/deny/{{$hash}} - - -Merci. +Une demande d'enregistrement pour un nouvel utilisateur a été reçue par {{$sitename}} qui requiert
+votre accord.
+
+
+Voici les détails de connexion :
+
+Emplacement du site: {{$siteurl}}
+Utilisateur : {{$email}}
+Adresse IP : {{$details}}
+
+Pour confirmer votre demande, veuillez cliquer sur le lien suivant:
+
+{{$siteurl}}/regmod/allow/{{$hash}}
+
+Pour supprimer ce compte, veuillez visiter:
+
+{{$siteurl}}/regmod/deny/{{$hash}}
+
+
+Merci.
diff --git a/view/fr/register_verify_member.tpl b/view/fr/register_verify_member.tpl index 3461c56a0..9d18ce204 100644 --- a/view/fr/register_verify_member.tpl +++ b/view/fr/register_verify_member.tpl @@ -1,24 +1,29 @@ - -Merci de vous être enregistré sur {{$sitename}}. - -Voici les détails de connexion: - -Emplacement du site:⇥{{$siteurl}} -Utilisateur:⇥{{$email}} - -Connectez-vous avec le mot de passe que vous avez choisi au moment de l'enregistrement. - -Nous avons besoin de vérifier votre adresse de courriel avant d’autoriser votre accès au réseau. - -Si vous avez enregistré ce compte, suivre ce lien: - -{{$siteurl}}/regver/allow/{{$hash}} - - -Pour supprimer ce compte, veuillez visiter: - - -{{$siteurl}}/regver/deny/{{$hash}} - - -Merci. +Merci de vous être enregistré sur {{$sitename}}.
+
+Voici les détails de connexion :
+
+Adresse du site: {{$siteurl}}
+Utilisateur: {{$email}}
+
+Connectez-vous avec le mot de passe que vous avez choisi au moment de l'enregistrement.
+
+Nous devons vérifier votre adresse électronique afin de vous donner un accès complet au réseau.
+
+Votre code de vérification est :
+
+{{$hash}}
+
+{{if $timeframe}}
+Ce code est valable de {{$timeframe.0}} UTC jusqu'à {{$timeframe.1}}
+
+{{/if}}
+
+Si vous avez enregistré ce compte, veuillez entrer le code de vérification lorsque cela vous est demandé ou cliquez sur le lien suivant :
+
+
+{{$siteurl}}/regate/{{$mail}}
+
+Pour refuser la demande et supprimer le compte, merci de vous rendre à cette adresse :
+{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
+
+Merci.
diff --git a/view/tpl/admin_queue.tpl b/view/tpl/admin_queue.tpl index 9d3d848c9..1587bff47 100644 --- a/view/tpl/admin_queue.tpl +++ b/view/tpl/admin_queue.tpl @@ -7,7 +7,7 @@ {{foreach $entries as $e}} -<tr><td>{{$e.total}}</td><td>{{$e.outq_posturl}}</td><td>{{$e.priority}}</td>{{if $expert}}<td><a href="admin/queue?f=&drophub={{$e.eurl}}" title="{{$nukehub}}" class="btn btn-outline-secondary"><i class="fa fa-times"></i><a></td><td><a href="admin/queue?f=&emptyhub={{$e.eurl}}" title="{{$empty}}" class="btn btn-outline-secondary"><i class="fa fa-trash-o"></i></a></td>{{/if}}</tr> +<tr><td>{{$e.total}}</td><td>{{$e.outq_posturl}}</td><td>{{$e.priority}}</td>{{if $expert}}<td><a href="admin/queue?f=&drophub={{$e.eurl}}" title="{{$nukehub}}" class="btn btn-outline-secondary"><i class="fa fa-times"></i><a></td><td><a href="admin/queue?f=&emptyhub={{$e.eurl}}" title="{{$empty}}" class="btn btn-outline-secondary"><i class="fa fa-trash-o"></i></a></td><td><a href="admin/queue?f=&deliverhub={{$e.eurl}}" title="{{$deliverhub}}" class="btn btn-outline-secondary"><i class="fa fa-refresh"></i><a></td>{{/if}}</tr> {{/foreach}} </table> |