diff --git a/include/conversation.php b/include/conversation.php index 0ae8dd14a1..43eeb9e41c 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -837,15 +837,15 @@ function item_photo_menu($item) { if (!empty($pcid)) { $contact_url = 'contact/' . $pcid; - $posts_link = 'contact/' . $pcid . '/posts'; - $block_link = 'contact/' . $pcid . '/block'; - $ignore_link = 'contact/' . $pcid . '/ignore'; + $posts_link = $contact_url . '/posts'; + $block_link = $contact_url . '/block'; + $ignore_link = $contact_url . '/ignore'; } if ($cid && !$item['self']) { - $poke_link = 'poke?c=' . $cid; $contact_url = 'contact/' . $cid; - $posts_link = 'contact/' . $cid . '/posts'; + $poke_link = $contact_url . '/poke'; + $posts_link = $contact_url . '/posts'; if (in_array($network, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA])) { $pm_url = 'message/new/' . $cid; diff --git a/mod/poke.php b/mod/poke.php deleted file mode 100644 index a683b11411..0000000000 --- a/mod/poke.php +++ /dev/null @@ -1,191 +0,0 @@ -getPokeVerbs(); - - if (!array_key_exists($verb, $verbs)) { - return; - } - - $activity = Activity::POKE . '#' . urlencode($verbs[$verb][0]); - - $contact_id = intval($_GET['cid']); - if (!$contact_id) { - return; - } - - $parent = (!empty($_GET['parent']) ? intval($_GET['parent']) : 0); - - - Logger::log('poke: verb ' . $verb . ' contact ' . $contact_id, Logger::DEBUG); - - - $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($contact_id), - intval($uid) - ); - - if (!DBA::isResult($r)) { - Logger::log('poke: no contact ' . $contact_id); - return; - } - - $target = $r[0]; - - if ($parent) { - $fields = ['uri', 'private', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']; - $condition = ['id' => $parent, 'parent' => $parent, 'uid' => $uid]; - $item = Item::selectFirst($fields, $condition); - - if (DBA::isResult($item)) { - $parent_uri = $item['uri']; - $private = $item['private']; - $allow_cid = $item['allow_cid']; - $allow_gid = $item['allow_gid']; - $deny_cid = $item['deny_cid']; - $deny_gid = $item['deny_gid']; - } - } else { - $private = (!empty($_GET['private']) ? intval($_GET['private']) : Item::PUBLIC); - - $allow_cid = ($private ? '<' . $target['id']. '>' : $a->user['allow_cid']); - $allow_gid = ($private ? '' : $a->user['allow_gid']); - $deny_cid = ($private ? '' : $a->user['deny_cid']); - $deny_gid = ($private ? '' : $a->user['deny_gid']); - } - - $poster = $a->contact; - - $uri = Item::newURI($uid); - - $arr = []; - - $arr['guid'] = System::createUUID(); - $arr['uid'] = $uid; - $arr['uri'] = $uri; - $arr['parent-uri'] = (!empty($parent_uri) ? $parent_uri : $uri); - $arr['wall'] = 1; - $arr['contact-id'] = $poster['id']; - $arr['owner-name'] = $poster['name']; - $arr['owner-link'] = $poster['url']; - $arr['owner-avatar'] = $poster['thumb']; - $arr['author-name'] = $poster['name']; - $arr['author-link'] = $poster['url']; - $arr['author-avatar'] = $poster['thumb']; - $arr['title'] = ''; - $arr['allow_cid'] = $allow_cid; - $arr['allow_gid'] = $allow_gid; - $arr['deny_cid'] = $deny_cid; - $arr['deny_gid'] = $deny_gid; - $arr['visible'] = 1; - $arr['verb'] = $activity; - $arr['private'] = $private; - $arr['object-type'] = Activity\ObjectType::PERSON; - - $arr['origin'] = 1; - $arr['body'] = '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' . ' ' . DI::l10n()->t($verbs[$verb][0]) . ' ' . '[url=' . $target['url'] . ']' . $target['name'] . '[/url]'; - - $arr['object'] = '' . Activity\ObjectType::PERSON . '' . $target['name'] . '' . $target['url'] . ''; - $arr['object'] .= '' . XML::escape('' . "\n"); - - $arr['object'] .= XML::escape('' . "\n"); - $arr['object'] .= '' . "\n"; - - Item::insert($arr); - - Hook::callAll('post_local_end', $arr); - - return; -} - -function poke_content(App $a) -{ - if (!local_user()) { - notice(DI::l10n()->t('Permission denied.') . EOL); - return; - } - - if (empty($_GET['c'])) { - return; - } - - $contact = DBA::selectFirst('contact', ['id', 'name'], ['id' => $_GET['c'], 'uid' => local_user()]); - if (!DBA::isResult($contact)) { - return; - } - - $name = $contact['name']; - $id = $contact['id']; - - $head_tpl = Renderer::getMarkupTemplate('poke_head.tpl'); - DI::page()['htmlhead'] .= Renderer::replaceMacros($head_tpl,[ - '$baseurl' => DI::baseUrl()->get(true), - ]); - - $parent = (!empty($_GET['parent']) ? intval($_GET['parent']) : '0'); - - - $verbs = DI::l10n()->getPokeVerbs(); - - $shortlist = []; - foreach ($verbs as $k => $v) { - if ($v[1] !== 'NOTRANSLATION') { - $shortlist[] = [$k, $v[1]]; - } - } - - $tpl = Renderer::getMarkupTemplate('poke_content.tpl'); - - $o = Renderer::replaceMacros($tpl,[ - '$title' => DI::l10n()->t('Poke/Prod'), - '$desc' => DI::l10n()->t('poke, prod or do other things to somebody'), - '$clabel' => DI::l10n()->t('Recipient'), - '$choice' => DI::l10n()->t('Choose what you wish to do to recipient'), - '$verbs' => $shortlist, - '$parent' => $parent, - '$prv_desc' => DI::l10n()->t('Make this post private'), - '$submit' => DI::l10n()->t('Submit'), - '$name' => $name, - '$id' => $id - ]); - - return $o; -} diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 2b739278a7..2435b54f9c 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -1235,7 +1235,7 @@ class Contact } if (($contact['network'] == Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) { - $poke_link = DI::baseUrl() . '/poke/?c=' . $contact['id']; + $poke_link = 'contact/' . $contact['id'] . '/poke'; } $contact_url = DI::baseUrl() . '/contact/' . $contact['id']; diff --git a/src/Module/Contact/Poke.php b/src/Module/Contact/Poke.php new file mode 100644 index 0000000000..a1c2c289fe --- /dev/null +++ b/src/Module/Contact/Poke.php @@ -0,0 +1,164 @@ +getPokeVerbs(); + if (!array_key_exists($verb, $verbs)) { + return self::postReturn(false); + } + + $activity = Activity::POKE . '#' . urlencode($verbs[$verb][0]); + + $contact_id = intval($parameters['id']); + if (!$contact_id) { + return self::postReturn(false); + } + + Logger::info('poke: verb ' . $verb . ' contact ' . $contact_id); + + $contact = DBA::selectFirst('contact', ['id', 'name'], ['id' => $parameters['id'], 'uid' => local_user()]); + if (!DBA::isResult($contact)) { + return self::postReturn(false); + } + + $a = DI::app(); + + $private = (!empty($_GET['private']) ? intval($_GET['private']) : Model\Item::PUBLIC); + + $allow_cid = ($private ? '<' . $contact['id']. '>' : $a->user['allow_cid']); + $allow_gid = ($private ? '' : $a->user['allow_gid']); + $deny_cid = ($private ? '' : $a->user['deny_cid']); + $deny_gid = ($private ? '' : $a->user['deny_gid']); + + $actor = $a->contact; + + $uri = Model\Item::newURI($uid); + + $arr = []; + + $arr['guid'] = System::createUUID(); + $arr['uid'] = $uid; + $arr['uri'] = $uri; + $arr['parent-uri'] = $uri; + $arr['wall'] = 1; + $arr['contact-id'] = $actor['id']; + $arr['owner-name'] = $actor['name']; + $arr['owner-link'] = $actor['url']; + $arr['owner-avatar'] = $actor['thumb']; + $arr['author-name'] = $actor['name']; + $arr['author-link'] = $actor['url']; + $arr['author-avatar'] = $actor['thumb']; + $arr['title'] = ''; + $arr['allow_cid'] = $allow_cid; + $arr['allow_gid'] = $allow_gid; + $arr['deny_cid'] = $deny_cid; + $arr['deny_gid'] = $deny_gid; + $arr['visible'] = 1; + $arr['verb'] = $activity; + $arr['private'] = $private; + $arr['object-type'] = Activity\ObjectType::PERSON; + + $arr['origin'] = 1; + $arr['body'] = '[url=' . $actor['url'] . ']' . $actor['name'] . '[/url]' . ' ' . $verbs[$verb][2] . ' ' . '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; + + $arr['object'] = '' . Activity\ObjectType::PERSON . '' . XML::escape($contact['name']) . '' . XML::escape($contact['url']) . ''; + $arr['object'] .= '' . XML::escape('') . "\n"; + + $arr['object'] .= XML::escape('') . "\n"; + $arr['object'] .= '' . "\n"; + + $result = Model\Item::insert($arr); + + Hook::callAll('post_local_end', $arr); + + return self::postReturn($result); + } + + /** + * Since post() is called before rawContent(), we need to be able to return a JSON response in post() directly. + * + * @param bool $success + * @return bool + */ + private static function postReturn(bool $success) + { + if ($success) { + info(DI::l10n()->t('Poke successfully sent.')); + } else { + notice(DI::l10n()->t('Error while sending poke, please retry.')); + } + + if (DI::mode()->isAjax()) { + System::jsonExit(['success' => $success]); + } + + return $success; + } + + public static function content(array $parameters = []) + { + if (!local_user()) { + throw new HTTPException\UnauthorizedException(DI::l10n()->t('You must be logged in to use this module.')); + } + + if (empty($parameters['id'])) { + throw new HTTPException\BadRequestException(); + } + + $contact = DBA::selectFirst('contact', ['id', 'url', 'name'], ['id' => $parameters['id'], 'uid' => local_user()]); + if (!DBA::isResult($contact)) { + throw new HTTPException\NotFoundException(); + } + + Model\Profile::load(DI::app(), '', Model\Contact::getDetailsByURL($contact["url"])); + + $verbs = []; + foreach (DI::l10n()->getPokeVerbs() as $verb => $translations) { + if ($translations[1] !== 'NOTRANSLATION') { + $verbs[$verb] = $translations[1]; + } + } + + $tpl = Renderer::getMarkupTemplate('contact/poke.tpl'); + $o = Renderer::replaceMacros($tpl,[ + '$title' => DI::l10n()->t('Poke/Prod'), + '$desc' => DI::l10n()->t('poke, prod or do other things to somebody'), + '$id' => $contact['id'], + '$verb' => ['verb', DI::l10n()->t('Choose what you wish to do to recipient'), '', '', $verbs], + '$private' => ['private', DI::l10n()->t('Make this post private')], + '$loading' => DI::l10n()->t('Loading...'), + '$submit' => DI::l10n()->t('Submit'), + + ]); + + return $o; + } +} diff --git a/static/routes.config.php b/static/routes.config.php index b6ce9bf629..85ec6d8bb4 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -117,6 +117,7 @@ return [ '/{id:\d+}/conversations' => [Module\Contact::class, [R::GET]], '/{id:\d+}/drop' => [Module\Contact::class, [R::GET]], '/{id:\d+}/ignore' => [Module\Contact::class, [R::GET]], + '/{id:\d+}/poke' => [Module\Contact\Poke::class, [R::GET, R::POST]], '/{id:\d+}/posts' => [Module\Contact::class, [R::GET]], '/{id:\d+}/update' => [Module\Contact::class, [R::GET]], '/{id:\d+}/updateprofile' => [Module\Contact::class, [R::GET]], diff --git a/view/global.css b/view/global.css index 9805a61543..cd0253ac66 100644 --- a/view/global.css +++ b/view/global.css @@ -406,22 +406,6 @@ a { .selected-identity img { border: 2px solid #ff0000; } -/* poke */ -#poke-desc { - margin: 5px 0 10px; -} - -#poke-wrapper { - padding: 10px 0 0px; -} - -#poke-recipient, #poke-action, #poke-privacy-settings { - margin: 10px 0 30px; -} - -#poke-recip-label, #poke-action-label, #prvmail-message-label { - margin: 10px 0 10px; -} .version-match { font-weight: bold; color: #00a700; diff --git a/view/templates/contact/poke.tpl b/view/templates/contact/poke.tpl new file mode 100644 index 0000000000..88e50cf598 --- /dev/null +++ b/view/templates/contact/poke.tpl @@ -0,0 +1,11 @@ +

{{$title}}

+ +

{{$desc nofilter}}

+ +
+ {{include file="field_select.tpl" field=$verb}} + {{include file="field_checkbox.tpl" field=$private}} +

+ +

+
diff --git a/view/templates/poke_content.tpl b/view/templates/poke_content.tpl deleted file mode 100644 index 91aadcdd8c..0000000000 --- a/view/templates/poke_content.tpl +++ /dev/null @@ -1,35 +0,0 @@ - -

{{$title}}

- -
{{$desc nofilter}}
- - -
-
- -
-
{{$clabel}}
- - - -
- -
-
{{$choice}}
- -
- -
-
{{$prv_desc}}
- -
- - - -
-
- diff --git a/view/templates/poke_head.tpl b/view/templates/poke_head.tpl deleted file mode 100644 index cf9ed53351..0000000000 --- a/view/templates/poke_head.tpl +++ /dev/null @@ -1,8 +0,0 @@ - - diff --git a/view/theme/frio/css/style.css b/view/theme/frio/css/style.css index 5e7d452043..ac6b5210c6 100644 --- a/view/theme/frio/css/style.css +++ b/view/theme/frio/css/style.css @@ -2273,7 +2273,7 @@ ul.dropdown-menu li:hover { * PAGES *********/ -.generic-page-wrapper, .videos-content-wrapper, +.generic-page-wrapper, .contact-content-wrapper, .videos-content-wrapper, .suggest-content-wrapper, .common-content-wrapper, .help-content-wrapper, .allfriends-content-wrapper, .match-content-wrapper, .dirfind-content-wrapper, .delegation-content-wrapper, .notes-content-wrapper, @@ -2609,14 +2609,6 @@ ul li:hover .contact-wrapper .contact-action-link:hover { .photo-album-actions .photos-order-link { float: right; } -/* poke */ -#poke-desc { - margin: 5px 0 30px; -} -#poke-wrapper-end { - clear: both; -} - /* Events page */ .fc .fc-month-view .fc-content .fc-title .item-desc:hover { diff --git a/view/theme/frio/js/theme.js b/view/theme/frio/js/theme.js index 335b63f16f..b64c7d0202 100644 --- a/view/theme/frio/js/theme.js +++ b/view/theme/frio/js/theme.js @@ -89,7 +89,7 @@ $(document).ready(function(){ let $body = $('body'); // show bulk deletion button at network page if checkbox is checked - $("body").change("input.item-select", function(){ + $body.change("input.item-select", function(){ var checked = false; // We need to get all checked items, so it would close the delete button @@ -230,7 +230,7 @@ $(document).ready(function(){ // Dropdown menus with the class "dropdown-head" will display the active tab // as button text - $("body").on('click', '.dropdown-head .dropdown-menu li a, .dropdown-head .dropdown-menu li button', function(){ + $body.on('click', '.dropdown-head .dropdown-menu li a, .dropdown-head .dropdown-menu li button', function(){ toggleDropdownText(this); }); @@ -264,7 +264,7 @@ $(document).ready(function(){ // to the input element where the padding value would be at least the width // of the button. Otherwise long user input would be invisible because it is // behind the button. - $("body").on('click', '.form-group-search > input', function() { + $body.on('click', '.form-group-search > input', function() { // Get the width of the button (if the button isn't available // buttonWidth will be null var buttonWidth = $(this).next('.form-button-search').outerWidth(); @@ -351,14 +351,14 @@ $(document).ready(function(){ */ $("aside") .on("shown.bs.offcanvas", function() { - $("body").addClass("aside-out"); + $body.addClass("aside-out"); }) .on("hidden.bs.offcanvas", function() { - $("body").removeClass("aside-out"); + $body.removeClass("aside-out"); }); // Event listener for 'Show & hide event map' button in the network stream. - $("body").on("click", ".event-map-btn", function() { + $body.on("click", ".event-map-btn", function() { showHideEventMap(this); }); @@ -400,6 +400,27 @@ $(document).ready(function(){ .always(function() { $commentSubmit.button('reset'); }); + }); + + $body.on('submit', '.modal-body #poke-wrapper', function(e) { + e.preventDefault(); + + let $form = $(this); + let $pokeSubmit = $form.find('button[type=submit]').button('loading'); + + $.post( + $form.attr('action'), + $form.serialize(), + 'json' + ) + .then(function(data) { + if (data.success) { + $('#modal').modal('hide'); + } + }) + .always(function() { + $pokeSubmit.button('reset'); + }); }) }); diff --git a/view/theme/frio/theme.php b/view/theme/frio/theme.php index 22afbc9a7c..94b29d8603 100644 --- a/view/theme/frio/theme.php +++ b/view/theme/frio/theme.php @@ -117,7 +117,7 @@ function frio_item_photo_links(App $a, &$body_info) function frio_item_photo_menu(App $a, &$arr) { foreach ($arr['menu'] as $k => $v) { - if (strpos($v, 'poke?c=') === 0 || strpos($v, 'message/new/') === 0) { + if (strpos($v, '/poke') === 0 || strpos($v, 'message/new/') === 0) { $v = 'javascript:addToModal(\'' . $v . '\'); return false;'; $arr['menu'][$k] = $v; } @@ -171,7 +171,7 @@ function frio_contact_photo_menu(App $a, &$args) // Add to pm and poke links a new key with the value 'modal'. // Later we can make conditions in the corresponing templates (e.g. // contact_template.tpl) - if (strpos($pokelink, 'poke?c=' . $cid) !== false) { + if (strpos($pokelink, $cid . '/poke') !== false) { $args['menu']['poke'][3] = 'modal'; }