Merge branch 'dev' into release

This commit is contained in:
nobody 2022-05-05 15:15:35 -07:00
commit 1ccf74910f
29 changed files with 2473 additions and 2351 deletions

View file

@ -1859,7 +1859,7 @@ class Activity
}
public static function activity_obj_mapper($obj)
public static function activity_obj_mapper($obj, $sync = false)
{
@ -1884,6 +1884,9 @@ class Activity
Hook::call('activity_obj_mapper', $objs);
if ($obj === 'Answer') {
if ($sync) {
return $obj;
}
return 'Note';
}
@ -2761,15 +2764,16 @@ class Activity
$s = [];
if (is_array($act->obj)) {
$binary = false;
$markdown = false;
if (array_key_exists('mediaType', $act->obj) && $act->obj['mediaType'] !== 'text/html') {
if ($act->obj['mediaType'] === 'text/markdown') {
$mediatype = $act->objprop('mediaType','');
if ($mediatype && $mediatype !== 'text/html') {
if ($mediatype === 'text/markdown') {
$markdown = true;
} else {
$s['mimetype'] = escape_tags($act->obj['mediaType']);
$s['mimetype'] = escape_tags($mediatype);
$binary = true;
}
}
@ -2797,8 +2801,8 @@ class Activity
// These activities should have been handled separately in the Inbox module and should not be turned into posts
if (
in_array($act->type, ['Follow', 'Accept', 'Reject', 'Create', 'Update']) && is_array($act->obj) && array_key_exists('type', $act->obj)
&& ($act->obj['type'] === 'Follow' || ActivityStreams::is_an_actor($act->obj['type']))
in_array($act->type, ['Follow', 'Accept', 'Reject', 'Create', 'Update'])
&& ($act->objprop('type') === 'Follow' || ActivityStreams::is_an_actor($act->objprop('type')))
) {
return false;
}
@ -2824,7 +2828,7 @@ class Activity
// ensure we store the original actor
self::actor_store($act->actor['id'], $act->actor);
$s['mid'] = ((is_array($act->obj) && isset($act->obj['id'])) ? $act->obj['id'] : $act->obj);
$s['mid'] = ($act->objprop('id')) ? $act->objprop('id') : $act->obj;
if (!$s['mid']) {
return false;
@ -2834,21 +2838,21 @@ class Activity
if (array_key_exists('published', $act->data) && $act->data['published']) {
$s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']);
} elseif (is_array($act->obj) && array_key_exists('published', $act->obj) && $act->obj['published']) {
} elseif ($act->objprop('published')) {
$s['created'] = datetime_convert('UTC', 'UTC', $act->obj['published']);
}
if (array_key_exists('updated', $act->data) && $act->data['updated']) {
$s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']);
} elseif (is_array($act->obj) && array_key_exists('updated', $act->obj) && $act->obj['updated']) {
} elseif ($act->objprop('updated')) {
$s['edited'] = datetime_convert('UTC', 'UTC', $act->obj['updated']);
}
if (array_key_exists('expires', $act->data) && $act->data['expires']) {
$s['expires'] = datetime_convert('UTC', 'UTC', $act->data['expires']);
} elseif (is_array($act->obj) && array_key_exists('expires', $act->obj) && $act->obj['expires']) {
} elseif ($act->objprop('expires')) {
$s['expires'] = datetime_convert('UTC', 'UTC', $act->obj['expires']);
}
if ($act->type === 'Invite' && is_array($act->obj) && array_key_exists('type', $act->obj) && $act->obj['type'] === 'Event') {
if ($act->type === 'Invite' && $act->objprop('type') === 'Event') {
$s['mid'] = $s['parent_mid'] = $act->id;
}
@ -2864,7 +2868,7 @@ class Activity
$response_activity = true;
$s['mid'] = $act->id;
$s['parent_mid'] = ((is_array($act->obj) && isset($act->obj['id'])) ? $act->obj['id'] : $act->obj);
$s['parent_mid'] = ($act->objprop('id')) ? $act->objprop('id') : $act->obj;
// over-ride the object timestamp with the activity
@ -2877,10 +2881,10 @@ class Activity
$s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']);
}
$obj_actor = ((isset($act->obj['actor'])) ? $act->obj['actor'] : $act->get_actor('attributedTo', $act->obj));
$obj_actor = ($act->objprop('actor')) ? $act->obj['actor'] : $act->get_actor('attributedTo', $act->obj);
// Actor records themselves do not have an actor or attributedTo
if ((!$obj_actor) && isset($act->obj['type']) && Activitystreams::is_an_actor($act->obj['type'])) {
if ((!$obj_actor) && $act->objprop('type') && Activitystreams::is_an_actor($act->obj['type'])) {
$obj_actor = $act->obj;
}
@ -2901,11 +2905,10 @@ class Activity
// if the object is an actor, it is not really a response activity, so reset it to a top level post
if (is_array($act->obj) && ActivityStreams::is_an_actor($act->obj['type'])) {
if ($act->objprop('type') && ActivityStreams::is_an_actor($act->obj['type'])) {
$s['parent_mid'] = $s['mid'];
}
// ensure we store the original actor of the associated (parent) object
self::actor_store($obj_actor['id'], $obj_actor);
@ -2913,15 +2916,20 @@ class Activity
$quoted_content = '[quote]' . $content['content'] . '[/quote]';
$object_type = $act->objprop('type', t('Activity'));
if (ActivityStreams::is_an_actor($object_type)) {
$object_type = t('Profile');
}
if ($act->type === 'Like') {
$content['content'] = sprintf(t('Likes %1$s\'s %2$s'), $mention, ((ActivityStreams::is_an_actor($act->obj['type'])) ? t('Profile') : $act->obj['type'])) . EOL . EOL . $quoted_content;
$content['content'] = sprintf(t('Likes %1$s\'s %2$s'), $mention, $object_type) . EOL . EOL . $quoted_content;
}
if ($act->type === 'Dislike') {
$content['content'] = sprintf(t('Doesn\'t like %1$s\'s %2$s'), $mention, ((ActivityStreams::is_an_actor($act->obj['type'])) ? t('Profile') : $act->obj['type'])) . EOL . EOL . $quoted_content;
$content['content'] = sprintf(t('Doesn\'t like %1$s\'s %2$s'), $mention, $object_type) . EOL . EOL . $quoted_content;
}
// handle event RSVPs
if (($act->obj['type'] === 'Event') || ($act->obj['type'] === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event')) {
if (($object_type === 'Event') || ($object_type === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event')) {
if ($act->type === 'Accept') {
$content['content'] = sprintf(t('Will attend %s\'s event'), $mention) . EOL . EOL . $quoted_content;
}
@ -2937,7 +2945,7 @@ class Activity
}
if ($act->type === 'Announce') {
$content['content'] = sprintf(t('🔁 Repeated %1$s\'s %2$s'), $mention, ((ActivityStreams::is_an_actor($act->obj['type'])) ? t('Profile') : $act->obj['type']));
$content['content'] = sprintf(t('🔁 Repeated %1$s\'s %2$s'), $mention, $object_type);
}
if ($act->type === 'emojiReaction') {
@ -2985,7 +2993,7 @@ class Activity
if ($s['mid'] === $s['parent_mid']) {
// it is a parent node - decode the comment policy info if present
if (isset($act->obj['commentPolicy'])) {
if ($act->objprop('commentPolicy')) {
$until = strpos($act->obj['commentPolicy'], 'until=');
if ($until !== false) {
$s['comments_closed'] = datetime_convert('UTC', 'UTC', substr($act->obj['commentPolicy'], $until + 6));
@ -3021,7 +3029,7 @@ class Activity
if ($quote_url) {
$s = self::get_quote($quote_url,$s);
}
elseif (isset($act->obj['quoteUrl'])) {
elseif ($act->objprop('quoteUrl')) {
$s = self::get_quote($act->obj['quoteUrl'],$s);
}
@ -3035,23 +3043,25 @@ class Activity
$s['item_deleted'] = 1;
}
if ($act->obj && array_key_exists('sensitive', $act->obj) && boolval($act->obj['sensitive'])) {
if ($act->objprop('sensitive')) {
$s['item_nsfw'] = 1;
}
$s['verb'] = self::activity_mapper($act->type);
// Mastodon does not provide update timestamps when updating poll tallies which means race conditions may occur here.
if (in_array($act->type,['Create','Update']) && $act->obj['type'] === 'Question' && $s['edited'] === $s['created']) {
if (isset($act->obj['votersCount']) && intval($act->obj['votersCount'])) {
if (in_array($act->type,['Create','Update']) && $act->objprop('type') === 'Question' && $s['edited'] === $s['created']) {
if (intval($act->objprop('votersCount'))) {
$s['edited'] = datetime_convert();
}
}
$s['obj_type'] = self::activity_obj_mapper($act->obj['type']);
if ($act->objprop('type')) {
$s['obj_type'] = self::activity_obj_mapper($act->obj['type']);
}
$s['obj'] = $act->obj;
if (is_array($s['obj']) && array_path_exists('actor/id', $s['obj'])) {
if (array_path_exists('actor/id', $s['obj'])) {
$s['obj']['actor'] = $s['obj']['actor']['id'];
}
@ -3091,7 +3101,7 @@ class Activity
}
}
if (!$response_activity) {
if (is_array($act->obj) && !$response_activity) {
$a = self::decode_taxonomy($act->obj);
if ($a) {
$s['term'] = $a;
@ -3122,27 +3132,27 @@ class Activity
// Objects that might have media attachments which aren't already provided in the content element.
// We'll check specific media objects separately.
if (in_array($act->obj['type'], ['Article', 'Document', 'Event', 'Note', 'Page', 'Place', 'Question']) && isset($s['attach']) && $s['attach']) {
if (in_array($act->objprop('type',''), ['Article', 'Document', 'Event', 'Note', 'Page', 'Place', 'Question'])
&& isset($s['attach']) && $s['attach']) {
$s = self::bb_attach($s);
}
if ($act->obj['type'] === 'Question' && in_array($act->type, ['Create', 'Update'])) {
if ($act->obj['endTime']) {
if ($act->objprop('type') === 'Question' && in_array($act->type, ['Create', 'Update'])) {
if ($act->objprop['endTime']) {
$s['comments_closed'] = datetime_convert('UTC', 'UTC', $act->obj['endTime']);
}
}
if (array_key_exists('closed', $act->obj) && $act->obj['closed']) {
if ($act->objprop('closed')) {
$s['comments_closed'] = datetime_convert('UTC', 'UTC', $act->obj['closed']);
}
// we will need a hook here to extract magnet links e.g. peertube
// right now just link to the largest mp4 we find that will fit in our
// standard content region
if (!$response_activity) {
if ($act->obj['type'] === 'Video') {
if ($act->objprop('type') === 'Video') {
$vtypes = [
'video/mp4',
'video/ogg',
@ -3155,7 +3165,7 @@ class Activity
// try to find a poster to display on the video element
if (array_key_exists('icon', $act->obj)) {
if ($act->objprop('icon')) {
if (is_array($act->obj['icon'])) {
if (array_key_exists(0, $act->obj['icon'])) {
$ptr = $act->obj['icon'];
@ -3175,7 +3185,7 @@ class Activity
$tag = (($poster) ? '[video poster="' . $poster . '"]' : '[video]');
$ptr = null;
if (array_key_exists('url', $act->obj)) {
if ($act->objprop('url')) {
if (is_array($act->obj['url'])) {
if (array_key_exists(0, $act->obj['url'])) {
$ptr = $act->obj['url'];
@ -3217,7 +3227,7 @@ class Activity
}
}
if ($act->obj['type'] === 'Audio') {
if ($act->objprop('type') === 'Audio') {
$atypes = [
'audio/mpeg',
'audio/ogg',
@ -3251,7 +3261,7 @@ class Activity
}
}
if ($act->obj['type'] === 'Image' && strpos($s['body'], 'zrl=') === false) {
if ($act->objprop('type') === 'Image' && strpos($s['body'], 'zrl=') === false) {
$ptr = null;
if (array_key_exists('url', $act->obj)) {
@ -3276,7 +3286,7 @@ class Activity
}
if ($act->obj['type'] === 'Page' && !$s['body']) {
if ($act->objprop('type') === 'Page' && !$s['body']) {
$ptr = null;
$purl = EMPTY_STR;
@ -3312,7 +3322,7 @@ class Activity
}
if (in_array($act->obj['type'], ['Note', 'Article', 'Page'])) {
if (in_array($act->objprop('type'), ['Note', 'Article', 'Page'])) {
$ptr = null;
if (array_key_exists('url', $act->obj)) {
@ -3346,10 +3356,8 @@ class Activity
$s['item_private'] = 0;
}
if (is_array($act->obj)) {
if (array_key_exists('directMessage', $act->obj) && intval($act->obj['directMessage'])) {
$s['item_private'] = 2;
}
if ($act->objprop('directMessage')) {
$s['item_private'] = 2;
}
set_iconfig($s, 'activitypub', 'recips', $act->raw_recips);

View file

@ -279,6 +279,19 @@ class ActivityStreams
return $key;
}
/**
* @brief get single property from Activity object
*
* @param string $property
* @param mixed return value if property or object not set
* or object is a string id which could not be fetched.
* @return mixed
*/
public function objprop($property, $default = false) {
$x = $this->get_property_obj($property,$this->obj);
return (isset($x)) ? $x : $default;
}
/**
* @brief
*
@ -298,12 +311,7 @@ class ActivityStreams
$base = (($base) ? $base : $this->data);
$propname = (($prefix) ? $prefix . ':' : '') . $property;
if (!is_array($base)) {
btlogger('not an array: ' . print_r($base, true));
return null;
}
return ((array_key_exists($propname, $base)) ? $base[$propname] : null);
return ((is_array($base) && array_key_exists($propname, $base)) ? $base[$propname] : null);
}

View file

@ -362,6 +362,7 @@ class Apps
'Connections' => t('Connections'),
'Content Filter' => t('Content Filter'),
'Content Import' => t('Content Import'),
'Custom SQL' => t('Custom SQL'),
'Directory' => t('Directory'),
'Drafts' => t('Drafts'),
'Events' => t('Events'),

View file

@ -506,7 +506,9 @@ class Libsync
$abconfig = null;
if (array_key_exists('abconfig', $abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) {
$abconfig = $abook['abconfig'];
}
$clean = [];
@ -647,7 +649,16 @@ class Libsync
if ($abconfig) {
/// @fixme does not handle sync of del_abconfig
foreach ($abconfig as $abc) {
set_abconfig($channel['channel_id'], $abc['xchan'], $abc['cat'], $abc['k'], $abc['v']);
if ($abc['cat'] === 'system' && $abc['k'] === 'my_perms') {
$x = explode(',', $abc['v']);
if (in_array('view_stream',$x) && ! in_array('deliver_stream',$x)) {
$x[] = 'deliver_stream';
}
set_abconfig($channel['channel_id'], $abc['xchan'], $abc['cat'], $abc['k'], implode(',', $x));
}
else {
set_abconfig($channel['channel_id'], $abc['xchan'], $abc['cat'], $abc['k'], $abc['v']);
}
}
}
if ($reconnect) {

View file

@ -1922,13 +1922,13 @@ class Libzot
$prnt = ((strpos($arr['parent_mid'], 'token=') !== false) ? substr($arr['parent_mid'], 0, strpos($arr['parent_mid'], '?')) : '');
$r = q(
"select route, id, parent_mid, mid, owner_xchan, item_private, obj_type from item where mid = '%s' and uid = %d limit 1",
"select id, parent_mid, mid, owner_xchan, item_private, obj_type from item where mid = '%s' and uid = %d limit 1",
dbesc($arr['parent_mid']),
intval($channel['channel_id'])
);
if (!$r) {
$r = q(
"select route, id, parent_mid, mid, owner_xchan, item_private, obj_type from item where mid = '%s' and uid = %d limit 1",
"select id, parent_mid, mid, owner_xchan, item_private, obj_type from item where mid = '%s' and uid = %d limit 1",
dbesc($prnt),
intval($channel['channel_id'])
);
@ -1949,8 +1949,6 @@ class Libzot
}
if ($r[0]['obj_type'] === 'Question') {
// route checking doesn't work correctly here because we've changed the privacy
$r[0]['route'] = EMPTY_STR;
// If this is a poll response, convert the obj_type to our (internal-only) "Answer" type
if ($arr['obj_type'] === 'Note' && $arr['title'] && (!$arr['content'])) {
$arr['obj_type'] = 'Answer';
@ -2000,53 +1998,6 @@ class Libzot
continue;
}
if ($relay || $friendofriend || (intval($r[0]['item_private']) === 0 && intval($arr['item_private']) === 0)) {
// reset the route in case it travelled a great distance upstream
// use our parent's route so when we go back downstream we'll match
// with whatever route our parent has.
// Also friend-of-friend conversations may have been imported without a route,
// but we are now getting comments via listener delivery
// and if there is no privacy on this or the parent, we don't care about the route,
// so just set the owner and route accordingly.
$arr['route'] = $r[0]['route'];
$arr['owner_xchan'] = $r[0]['owner_xchan'];
} else {
// going downstream check that we have the same upstream provider that
// sent it to us originally. Ignore it if it came from another source
// (with potentially different permissions).
// only compare the last hop since it could have arrived at the last location any number of ways.
// Always accept empty routes and firehose items (route contains 'undefined') .
$existing_route = explode(',', $r[0]['route']);
$routes = count($existing_route);
if ($routes) {
$last_hop = array_pop($existing_route);
$last_prior_route = implode(',', $existing_route);
} else {
$last_hop = '';
$last_prior_route = '';
}
if (in_array('undefined', $existing_route) || $last_hop == 'undefined' || $sender == 'undefined') {
$last_hop = '';
}
$current_route = ((isset($arr['route']) && $arr['route']) ? $arr['route'] . ',' : '') . $sender;
if ($last_hop && $last_hop != $sender) {
logger('comment route mismatch: parent route = ' . $r[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG);
logger('comment route mismatch: parent msg = ' . $r[0]['id'], LOGGER_DEBUG);
$DR->update('comment route mismatch');
$result[] = $DR->get();
continue;
}
// we'll add sender onto this when we deliver it. $last_prior_route now has the previously stored route
// *except* for the sender which would've been the last hop before it got to us.
$arr['route'] = $last_prior_route;
}
}
// This is used to fetch allow/deny rules if either the sender
@ -2106,9 +2057,6 @@ class Libzot
$item_result = self::update_imported_item($sender, $arr, $r[0], $channel['channel_id'], $tag_delivery);
$DR->update('updated');
$result[] = $DR->get();
if (!$relay) {
add_source_route($item_id, $sender);
}
} else {
$DR->update('update ignored');
$result[] = $DR->get();
@ -2180,10 +2128,6 @@ class Libzot
* * \e array \b channel
*/
Hook::call('activity_received', $parr);
// don't add a source route if it's a relay or later recipients will get a route mismatch
if (!$relay) {
add_source_route($item_id, $sender);
}
}
$DR->update(($item_id) ? 'posted' : 'storage failed: ' . $item_result['message']);
$result[] = $DR->get();
@ -2305,7 +2249,7 @@ class Libzot
}
}
if ($AS->obj['actor'] && $AS->obj['actor']['id'] && $AS->obj['actor']['id'] !== $AS->actor['id']) {
if (array_path_exists('actor/id', $AS->obj) && $AS->obj['actor']['id'] !== $AS->actor['id']) {
$y = import_author_xchan(['url' => $AS->obj['actor']['id']]);
if (!$y) {
logger('FOF Activity: no object actor');
@ -2797,7 +2741,7 @@ class Libzot
public static function import_site($arr)
{
if ((!is_array($arr)) || (!$arr['url']) || (!$arr['site_sig'])) {
if (!(is_array($arr) && isset($arr['url']) && isset($arr['site_sig']))) {
return false;
}
@ -3359,79 +3303,59 @@ class Libzot
$ret['site']['authRedirect'] = z_root() . '/magic';
$ret['site']['sitekey'] = get_config('system', 'pubkey');
$dirmode = get_config('system', 'directory_mode');
if (($dirmode === false) || ($dirmode == DIRECTORY_MODE_NORMAL)) {
$ret['site']['directory_mode'] = 'normal';
}
if ($dirmode == DIRECTORY_MODE_PRIMARY) {
$ret['site']['directory_mode'] = 'primary';
} elseif ($dirmode == DIRECTORY_MODE_SECONDARY) {
$ret['site']['directory_mode'] = 'secondary';
} elseif ($dirmode == DIRECTORY_MODE_STANDALONE) {
$ret['site']['directory_mode'] = 'standalone';
}
if ($dirmode != DIRECTORY_MODE_NORMAL) {
$ret['site']['directory_url'] = z_root() . '/dirsearch';
}
$ret['site']['encryption'] = Crypto::methods();
$ret['signature_algorithm'] = $sig_method;
$ret['site']['zot'] = System::get_zot_revision();
// hide detailed site information if you're off the grid
if ($dirmode != DIRECTORY_MODE_STANDALONE || $force) {
$register_policy = intval(get_config('system', 'register_policy'));
if ($register_policy == REGISTER_CLOSED) {
$ret['site']['register_policy'] = 'closed';
}
if ($register_policy == REGISTER_APPROVE) {
$ret['site']['register_policy'] = 'approve';
}
if ($register_policy == REGISTER_OPEN) {
$ret['site']['register_policy'] = 'open';
}
$access_policy = intval(get_config('system', 'access_policy'));
if ($access_policy == ACCESS_PRIVATE) {
$ret['site']['access_policy'] = 'private';
}
if ($access_policy == ACCESS_PAID) {
$ret['site']['access_policy'] = 'paid';
}
if ($access_policy == ACCESS_FREE) {
$ret['site']['access_policy'] = 'free';
}
if ($access_policy == ACCESS_TIERED) {
$ret['site']['access_policy'] = 'tiered';
}
$ret['site']['admin'] = get_config('system', 'admin_email');
$visible_plugins = [];
$r = q("select * from addon where hidden = 0");
if ($r) {
foreach ($r as $rr) {
$visible_plugins[] = $rr['aname'];
}
}
$ret['site']['about'] = bbcode(get_config('system', 'siteinfo'), ['export' => true]);
$ret['site']['plugins'] = $visible_plugins;
$ret['site']['sitehash'] = get_config('system', 'location_hash');
$ret['site']['sellpage'] = get_config('system', 'sellpage');
$ret['site']['location'] = get_config('system', 'site_location');
$ret['site']['sitename'] = System::get_site_name();
$ret['site']['logo'] = System::get_site_icon();
$ret['site']['project'] = System::get_project_name();
$ret['site']['version'] = System::get_project_version();
$register_policy = intval(get_config('system', 'register_policy'));
if ($register_policy == REGISTER_CLOSED) {
$ret['site']['register_policy'] = 'closed';
}
if ($register_policy == REGISTER_APPROVE) {
$ret['site']['register_policy'] = 'approve';
}
if ($register_policy == REGISTER_OPEN) {
$ret['site']['register_policy'] = 'open';
}
$access_policy = intval(get_config('system', 'access_policy'));
if ($access_policy == ACCESS_PRIVATE) {
$ret['site']['access_policy'] = 'private';
}
if ($access_policy == ACCESS_PAID) {
$ret['site']['access_policy'] = 'paid';
}
if ($access_policy == ACCESS_FREE) {
$ret['site']['access_policy'] = 'free';
}
if ($access_policy == ACCESS_TIERED) {
$ret['site']['access_policy'] = 'tiered';
}
$ret['site']['admin'] = get_config('system', 'admin_email');
$visible_plugins = [];
$r = q("select * from addon where hidden = 0");
if ($r) {
foreach ($r as $rr) {
$visible_plugins[] = $rr['aname'];
}
}
$ret['site']['about'] = bbcode(get_config('system', 'siteinfo'), ['export' => true]);
$ret['site']['plugins'] = $visible_plugins;
$ret['site']['sitehash'] = get_config('system', 'location_hash');
$ret['site']['sellpage'] = get_config('system', 'sellpage');
$ret['site']['location'] = get_config('system', 'site_location');
$ret['site']['sitename'] = System::get_site_name();
$ret['site']['logo'] = System::get_site_icon();
$ret['site']['project'] = System::get_project_name();
$ret['site']['version'] = System::get_project_version();
return $ret['site'];
}

View file

@ -0,0 +1,61 @@
<?php
namespace Code\Module\Admin;
use Code\Render\Theme;
use Code\Lib\Apps;
class Customsql
{
public function get()
{
$o = '';
if (!is_site_admin()) {
logger('admin denied.');
return;
}
if (!Apps::system_app_installed(local_channel(), 'Custom SQL')) {
$text = t('Once installed, this app provides an admin-only interface to execute arbitrary SQL statements.');
return '<div class="section-content-info-wrapper">' . $text . '</div>';
}
$query = [ 'query', 'Enter an SQL query', '', '' ];
$ok = [ 'ok', 'OK to show all results?', '', '' ];
$o = replace_macros(Theme::get_template('customsql.tpl'), [
'$title' => t('Custom SQL query'),
'$warn' => t('If you do not know exactly what you are doing, please leave this page now.'),
'$query' => $query,
'$form_security_token' => get_form_security_token('admin_customsql'),
'$ok' => $ok,
'$submit' => t('Submit')
]);
if (isset($_REQUEST['query']) && $_REQUEST['query']) {
check_form_security_token_redirectOnErr('/admin/customsql', 'admin_customsql');
$o .= EOL . EOL;
$r = q($_REQUEST['query']);
if (is_array($r) && count($r) > 500 && !isset($_REQUEST['ok'])) {
notice ( t('Too many results.') . EOL);
}
elseif (is_array($r)) {
$o .= '"' . $_REQUEST['query'] . '" ' . sprintf( t('returned %d results'), count($r));
$o .= EOL. EOL;
$o .= '<pre>' . print_array($r) . '</pre>';
}
else {
$o .= t('Query returned: ') . (($r) ? t('true') : t('false')) ;
}
}
return $o;
}
}

89
Code/Module/Dev.php Normal file
View file

@ -0,0 +1,89 @@
<?php
/**
* @file Code/Module/Dev.php
* @brief dev controller.
*
* Controller for the /dev/ area.
*/
namespace Code\Module;
use App;
use Code\Web\Controller;
use Code\Web\SubModule;
use Code\Lib\Config;
use Code\Lib\Channel;
use Code\Lib\Navbar;
use Code\Lib\Addon;
use Code\Render\Theme;
/**
* @brief dev area.
*
*/
class Dev extends Controller
{
private $sm = null;
public function __construct()
{
$this->sm = new SubModule();
}
public function init()
{
logger('dev_init', LOGGER_DEBUG);
if (argc() > 1) {
$this->sm->call('init');
}
}
public function post()
{
logger('dev_post', LOGGER_DEBUG);
if (argc() > 1) {
$this->sm->call('post');
}
}
/**
* @return string
*/
public function get()
{
logger('dev_content', LOGGER_DEBUG);
/*
* Page content
*/
Navbar::set_selected('Dev');
$o = '';
if (argc() > 1) {
$o = $this->sm->call('get');
if ($o === false) {
notice(t('Item not found.'));
}
}
if (is_ajax()) {
echo $o;
killme();
} else {
return $o;
}
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Code\Module;
namespace Code\Module\Dev;
use App;
use Code\Web\Controller;

View file

@ -1,6 +1,6 @@
<?php
namespace Code\Module;
namespace Code\Module\Dev;
use Code\Web\Controller;
@ -12,7 +12,7 @@ class Xchan extends Controller
$o = '<h3>' . t('Xchan Lookup') . '</h3>';
$o .= '<form action="xchan" method="get">';
$o .= '<form action="dev/xchan" method="get">';
$o .= t('Lookup xchan beginning with (or webbie): ');
$o .= '<input type="text" style="width:250px;" name="addr" value="' . $_GET['addr'] . '">';
$o .= '<input type="submit" name="submit" value="' . t('Submit') . '"></form>';

View file

@ -1,6 +1,6 @@
<?php
namespace Code\Module;
namespace Code\Module\Dev;
use App;
use Code\Lib\ZotURL;

View file

@ -1,6 +1,6 @@
<?php
namespace Code\Module;
namespace Code\Module\Dev;
/*
* Zotfinger

View file

@ -510,7 +510,6 @@ class Item extends Controller
$item_flags = $item_restrict = 0;
$expires = NULL_DATE;
$route = '';
$parent_item = null;
$parent_contact = null;
$thr_parent = '';
@ -590,7 +589,6 @@ class Item extends Controller
$thr_parent = $parent_mid;
$route = $parent_item['route'];
}
if ($parent_item && isset($parent_item['replyto']) && $parent_item['replyto']) {
@ -1462,7 +1460,6 @@ class Item extends Controller
$datarray['comment_policy'] = ((is_numeric($comment_policy)) ? map_scope($comment_policy) : $comment_policy); // only map scope if it is numeric, otherwise use what we have
$datarray['term'] = $post_tags;
$datarray['plink'] = $plink;
$datarray['route'] = $route;
$datarray['replyto'] = $replyto;
// A specific ACL over-rides public_policy completely

View file

@ -735,8 +735,10 @@ class Photos extends Controller
'$nickname' => App::$data['channel']['channel_address'],
'$newalbum_label' => t('Enter an album name'),
'$newalbum_placeholder' => t('or select an existing album (doubleclick)'),
'$caption' => array('description', t('Please briefly describe this photo for vision-impaired viewers')),
'title' => ['title', t('Title (optional)')],
'$caption' => [ 'description', t('Please briefly describe this photo for vision-impaired viewers'), '',
t('If uploading multiple photos this description will be added to every photo.') ],
'title' => ['title', t('Title (optional)'), '',
t('If uploading multiple photos this title/caption will be added to every photo.') ],
'$albums' => $albums['albums'],
'$selname' => $selname,
'$permissions' => t('Permissions'),

30
Code/Update/_1257.php Normal file
View file

@ -0,0 +1,30 @@
<?php
namespace Code\Update;
use Code\Lib\AbConfig;
class _1257
{
public function run()
{
$r = q("SELECT * from abook where abook_self = 0");
if ($r) {
foreach ($r as $rv) {
$perms = AbConfig::Get($rv['abook_channel'], $rv['abook_xchan'], 'system', 'my_perms', [] );
$s = explode(',', $perms);
if (in_array('view_stream', $s) && (! in_array('deliver_stream', $s))) {
$s[] = 'deliver_stream';
}
AbConfig::Set($rv['abook_channel'], $rv['abook_xchan'], 'system', 'my_perms', implode(',', $s));
}
}
return UPDATE_SUCCESS;
}
public function verify()
{
return true;
}
}

View file

@ -510,6 +510,7 @@ class HTTPSig
$return_headers = [];
if ($alg === 'sha256') {
$algorithm = 'rsa-sha256';
}
@ -517,9 +518,15 @@ class HTTPSig
$algorithm = 'rsa-sha512';
}
// Use hs2019 by default.
if (get_config('system','use_hs2019',true) && $algorithm === 'rsa-sha256') {
$algorithm = 'hs2019';
}
$x = self::sign($head, $prvkey, $alg);
$headerval = 'keyId="' . $keyid . '",algorithm="' . (($algorithm === 'rsa-sha256') ? 'hs2019' : $algorithm) . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"';
$headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"';
if ($encryption) {
$x = Crypto::encapsulate($headerval, $encryption['key'], $encryption['algorithm']);

View file

@ -60,12 +60,12 @@ class Admin
Hook::call('admin_aside', $arr);
$o .= replace_macros(Theme::get_template('admin_aside.tpl'), array(
'$admin' => $aside,
'$admin' => $arr['links'],
'$admtxt' => t('Admin'),
'$plugadmtxt' => t('Addon Features'),
'$plugins' => $plugins,
'$plugins' => $arr['plugins'],
'$logtxt' => t('Logs'),
'$logs' => $logs,
'$logs' => $arr['logs'],
'$h_pending' => t('Member registrations waiting for confirmation'),
'$admurl' => z_root() . '/admin/'
));

6
app/customsql.apd Normal file
View file

@ -0,0 +1,6 @@
version: 1
url: $baseurl/admin/customsql
requires: admin
name: Custom SQL
photo: icon:mysql
categories: System,nav_featured_app

View file

@ -27,7 +27,7 @@ require_once('version.php');
define ( 'PLATFORM_NAME', 'streams' );
define ( 'DB_UPDATE_VERSION', 1256 );
define ( 'DB_UPDATE_VERSION', 1257 );
define ( 'ZOT_REVISION', '11.0' );
define ( 'PLATFORM_ARCHITECTURE', 'zap' );

View file

@ -86,7 +86,7 @@ class dba_pdo extends dba_driver
$result = null;
$this->error = '';
$select = ((stripos($sql, 'select') === 0) ? true : false);
$select = ((stripos($sql, 'select') === 0 || stripos($sql, 'show') === 0 || stripos($sql, 'explain') === 0) ? true : false);
try {
$result = $this->db->query($sql, PDO::FETCH_ASSOC);

View file

@ -352,11 +352,13 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $
$enclosures = json_decode($item['attach'], true);
if ($enclosures) {
foreach ($enclosures as $enc) {
$o .= '<link rel="enclosure" '
. (($enc['href']) ? 'href="' . $enc['href'] . '" ' : '')
. (($enc['length']) ? 'length="' . $enc['length'] . '" ' : '')
. (($enc['type']) ? 'type="' . $enc['type'] . '" ' : '')
. ' />' . "\r\n";
if (is_array($enc)) {
$o .= '<link rel="enclosure" '
. (isset($enc['href']) ? 'href="' . $enc['href'] . '" ' : '')
. (isset($enc['length']) ? 'length="' . $enc['length'] . '" ' : '')
. (isset($enc['type']) ? 'type="' . $enc['type'] . '" ' : '')
. ' />' . "\r\n";
}
}
}
}

View file

@ -95,8 +95,7 @@ function collect_recipients($item, &$private_envelope,$include_groups = true) {
// a different clone. We need to get the post to that hub.
// The post may be private by virtue of not being visible to anybody on the internet,
// but there are no envelope recipients, so set this to false. Delivery is controlled
// by the directives in $item['public_policy'].
// but there are no envelope recipients, so set this to false.
$private_envelope = false;
@ -165,18 +164,6 @@ function collect_recipients($item, &$private_envelope,$include_groups = true) {
$recipients = check_list_permissions($item['uid'],$recipients,'deliver_stream');
// remove any upstream recipients from our list.
// If it is ourself we'll add it back in a second.
// This should prevent complex delivery chains from getting overly complex by not
// sending to anybody who is on our list of those who sent it to us.
if ($item['route']) {
$route = explode(',',$item['route']);
if (count($route)) {
$route = array_unique($route);
$recipients = array_diff($recipients,$route);
}
}
// add ourself just in case we have nomadic clones that need to get a copy.
@ -386,43 +373,6 @@ function absolutely_no_comments($item) {
return false;
}
/**
* @brief Adds $hash to the item source route specified by $iid.
*
* $item['route'] contains a comma-separated list of xchans that sent the current message,
* somewhat analogous to the * Received: header line in email. We can use this to perform
* loop detection and to avoid sending a particular item to any "upstream" sender (they
* already have a copy because they sent it to us).
*
* Modifies item in the database pointed to by $iid.
*
* @param integer $iid
* item['id'] of target item
* @param string $hash
* xchan_hash of the channel that sent the item
*/
function add_source_route($iid, $hash) {
// logger('add_source_route ' . $iid . ' ' . $hash, LOGGER_DEBUG);
if ((! $iid) || (! $hash)) {
return;
}
$r = q("select route from item where id = %d limit 1",
intval($iid)
);
if ($r) {
$new_route = (($r[0]['route']) ? $r[0]['route'] . ',' : '') . $hash;
q("update item set route = '%s' where id = %d",
dbesc($new_route),
intval($iid)
);
}
}
/**
* @brief Post an activity.
*
@ -652,6 +602,7 @@ function get_item_elements($x,$allow_code = false) {
$arr['body'] = $x['body'];
$arr['summary'] = $x['summary'];
$mirror = array_key_exists('revision', $x);
$maxlen = get_max_import_size();
if($maxlen && mb_strlen($arr['body']) > $maxlen) {
@ -685,7 +636,6 @@ function get_item_elements($x,$allow_code = false) {
$arr['uuid'] = (($x['uuid']) ? htmlspecialchars($x['uuid'], ENT_COMPAT,'UTF-8',false) : '');
$arr['app'] = (($x['app']) ? htmlspecialchars($x['app'], ENT_COMPAT,'UTF-8',false) : '');
$arr['route'] = (($x['route']) ? htmlspecialchars($x['route'], ENT_COMPAT,'UTF-8',false) : '');
$arr['mid'] = (($x['message_id']) ? htmlspecialchars($x['message_id'], ENT_COMPAT,'UTF-8',false) : '');
$arr['parent_mid'] = (($x['message_top']) ? htmlspecialchars($x['message_top'], ENT_COMPAT,'UTF-8',false) : '');
$arr['thr_parent'] = (($x['message_parent']) ? htmlspecialchars($x['message_parent'], ENT_COMPAT,'UTF-8',false) : '');
@ -702,8 +652,9 @@ function get_item_elements($x,$allow_code = false) {
// convert AS1 namespaced elements to AS-JSONLD
$arr['verb'] = Activity::activity_mapper($arr['verb']);
$arr['obj_type'] = Activity::activity_obj_mapper($arr['obj_type']);
$arr['tgt_type'] = Activity::activity_obj_mapper($arr['tgt_type']);
$arr['obj_type'] = Activity::activity_obj_mapper($arr['obj_type'], $mirror);
$arr['tgt_type'] = Activity::activity_obj_mapper($arr['tgt_type'], $mirror);
$arr['comment_policy'] = (($x['comment_scope']) ? htmlspecialchars($x['comment_scope'], ENT_COMPAT,'UTF-8',false) : 'contacts');
@ -822,7 +773,7 @@ function get_item_elements($x,$allow_code = false) {
// Strip old-style hubzilla bookmarks
// Do this after signature verification
if(strpos($x['body'],"#^[") !== false) {
if (strpos($x['body'],"#^[") !== false) {
$x['body'] = str_replace("#^[","[",$x['body']);
}
@ -831,12 +782,12 @@ function get_item_elements($x,$allow_code = false) {
// Do this after signature checking as the original signature
// was generated on the escaped content.
if($arr['mimetype'] === 'text/markdown') {
if ($arr['mimetype'] === 'text/markdown') {
$arr['summary'] = MarkdownSoap::unescape($arr['summary']);
$arr['body'] = MarkdownSoap::unescape($arr['body']);
}
if(array_key_exists('revision',$x)) {
if ($mirror) {
// extended export encoding
@ -1115,7 +1066,6 @@ function encode_item($item,$mirror = false) {
$x['location'] = $item['location'];
$x['longlat'] = $item['coord'];
$x['signature'] = $item['sig'];
$x['route'] = $item['route'];
$x['replyto'] = $item['replyto'];
$x['owner'] = encode_item_xchan($item['owner']);
$x['author'] = encode_item_xchan($item['author']);
@ -1612,7 +1562,6 @@ function item_store($arr, $allow_exec = false, $deliver = true, $linkid = true)
$arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
$arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
$arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
$arr['route'] = ((x($arr,'route')) ? trim($arr['route']) : '');
$arr['uuid'] = ((x($arr,'uuid')) ? trim($arr['uuid']) : '');
$arr['item_private'] = ((x($arr,'item_private')) ? intval($arr['item_private']) : 0 );
$arr['item_wall'] = ((x($arr,'item_wall')) ? intval($arr['item_wall']) : 0 );
@ -2225,7 +2174,6 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $linkid =
$arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
$arr['revision'] = ((x($arr,'revision') && $arr['revision'] > 0) ? intval($arr['revision']) : 0);
$arr['route'] = ((array_key_exists('route',$arr)) ? trim($arr['route']) : $orig[0]['route']);
$arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : $orig[0]['location']);
$arr['uuid'] = ((x($arr,'uuid')) ? notags(trim($arr['uuid'])) : $orig[0]['uuid']);

View file

@ -4056,7 +4056,7 @@ function print_val($v, $escape = true)
function array_path_exists($str, $arr)
{
if (! ($arr && is_array($arr))) {
if (! (isset($arr) && is_array($arr))) {
return false;
}

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,2 @@
<?php
define ( 'STD_VERSION', '22.04.28' );
define ( 'STD_VERSION', '22.05.06' );

View file

@ -1,6 +1,6 @@
<h3>{{$page_title}}</h3>
<form action="ap_probe" method="get">
<form action="dev/ap_probe" method="get">
{{include file="field_input.tpl" field=$resource}}
{{include file="field_checkbox.tpl" field=$authf}}
<input type="submit" name="submit" value="{{$submit}}" >

15
view/tpl/customsql.tpl Executable file
View file

@ -0,0 +1,15 @@
<div class="generic-content-wrapper" id='customsql'>
<div class="section-title-wrapper"><h1>{{$title}}</h1></div>
<div class="section-content-wrapper">
<div class="descriptive_text">{{$warn}}</div>
<br><br>
<form action="admin/customsql" method="post">
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
{{include file="field_textarea.tpl" field=$query}}
{{include file="field_checkbox.tpl" field=$ok}}
<div class="settings-submit-wrapper" >
<button type="submit" name="done" value="{{$submit}}" class="btn btn-primary">{{$submit}}</button>
</div>
</form>
</div>
</div>

View file

@ -1,5 +1,5 @@
<div id="photo-upload-form">
<input id="invisible-photos-file-upload" type="file" name="files" style="visibility:hidden;position:absolute;top:-50;left:-50;width:0;height:0;" data-nickname='{{$nickname}}' >
<input id="invisible-photos-file-upload" type="file" name="files" style="visibility:hidden;position:absolute;top:-50;left:-50;width:0;height:0;" data-nickname='{{$nickname}}' multiple >
<div class="section-content-tools-wrapper">
<form action="#" enctype="multipart/form-data" method="post" name="photos-upload-form" id="photos-upload-form" class="acl-form" data-form_id="photos-upload-form" data-allow_cid='{{$allow_cid}}' data-allow_gid='{{$allow_gid}}' data-deny_cid='{{$deny_cid}}' data-deny_gid='{{$deny_gid}}'>
<input type="hidden" id="photos-upload-source" name="source" value="photos" />

View file

@ -1,6 +1,6 @@
<h3>{{$page_title}}</h3>
<form action="zot_probe" method="get">
<form action="dev/zot_probe" method="get">
{{include file="field_input.tpl" field=$resource}}
{{include file="field_checkbox.tpl" field=$authf}}
<input type="submit" name="submit" value="{{$submit}}" >

View file

@ -1,6 +1,6 @@
<h3>{{$page_title}}</h3>
<form action="zotfinger" method="get">
<form action="dev/zotfinger" method="get">
{{include file="field_input.tpl" field=$resource}}
<input type="submit" name="submit" value="{{$submit}}" >
</form>