diff --git a/mod/cal.php b/mod/cal.php index ebf78e77f..3f249da0a 100644 --- a/mod/cal.php +++ b/mod/cal.php @@ -28,12 +28,14 @@ use Friendica\Content\Nav; use Friendica\Content\Widget; use Friendica\Core\Renderer; use Friendica\Core\Session; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Event; use Friendica\Model\Item; use Friendica\Model\User; use Friendica\Module\BaseProfile; +use Friendica\Module\Response; use Friendica\Network\HTTPException; use Friendica\Util\DateTimeFormat; use Friendica\Util\Temporal; @@ -216,8 +218,7 @@ function cal_content(App $a) $events = Event::prepareListForTemplate($r); if (!empty(DI::args()->getArgv()[2]) && (DI::args()->getArgv()[2] === 'json')) { - echo json_encode($events); - exit(); + System::jsonExit($events); } // links: array('href', 'text', 'extra css classes', 'title') @@ -253,8 +254,7 @@ function cal_content(App $a) ]); if (!empty($_GET['id'])) { - echo $o; - exit(); + System::httpExit($o); } return $o; @@ -289,10 +289,8 @@ function cal_content(App $a) // If nothing went wrong we can echo the export content if ($evexport["success"]) { - header('Content-type: text/calendar'); header('content-disposition: attachment; filename="' . DI::l10n()->t('calendar') . '-' . $nick . '.' . $evexport["extension"] . '"'); - echo $evexport["content"]; - exit(); + System::httpExit($evexport["content"], Response::TYPE_BLANK, 'text/calendar'); } return; diff --git a/mod/display.php b/mod/display.php index da41c8656..880cfd44b 100644 --- a/mod/display.php +++ b/mod/display.php @@ -25,6 +25,7 @@ use Friendica\Content\Widget; use Friendica\Core\Logger; use Friendica\Core\Renderer; use Friendica\Core\Session; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; @@ -32,6 +33,7 @@ use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Model\User; use Friendica\Module\ActivityPub\Objects; +use Friendica\Module\Response; use Friendica\Network\HTTPException; use Friendica\Protocol\ActivityPub; use Friendica\Protocol\DFRN; @@ -342,7 +344,6 @@ function displayShowFeed(int $uri_id, int $uid, bool $conversation) if ($xml == '') { throw new HTTPException\InternalServerErrorException(DI::l10n()->t('The feed for this item is unavailable.')); } - header("Content-type: application/atom+xml"); - echo $xml; - exit(); + + System::httpExit($xml, Response::TYPE_ATOM); } diff --git a/mod/events.php b/mod/events.php index 4db595ccc..082cdf55d 100644 --- a/mod/events.php +++ b/mod/events.php @@ -27,6 +27,7 @@ use Friendica\Core\ACL; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\Renderer; +use Friendica\Core\System; use Friendica\Core\Theme; use Friendica\Core\Worker; use Friendica\Database\DBA; @@ -122,8 +123,7 @@ function events_post(App $a) if (strcmp($finish, $start) < 0 && !$nofinish) { notice(DI::l10n()->t('Event can not end before it has started.')); if (intval($_REQUEST['preview'])) { - echo DI::l10n()->t('Event can not end before it has started.'); - exit(); + System::httpExit(DI::l10n()->t('Event can not end before it has started.')); } DI::baseUrl()->redirect($onerror_path); } @@ -131,8 +131,7 @@ function events_post(App $a) if (!$summary || ($start === DBA::NULL_DATETIME)) { notice(DI::l10n()->t('Event title and start time are required.')); if (intval($_REQUEST['preview'])) { - echo DI::l10n()->t('Event title and start time are required.'); - exit(); + System::httpExit(DI::l10n()->t('Event title and start time are required.')); } DI::baseUrl()->redirect($onerror_path); } @@ -192,9 +191,7 @@ function events_post(App $a) $datarray['id'] = $event_id; if (intval($_REQUEST['preview'])) { - $html = Event::getHTML($datarray); - echo $html; - exit(); + System::httpExit(Event::getHTML($datarray)); } $event_id = Event::store($datarray); @@ -391,8 +388,7 @@ function events_content(App $a) ]); if (!empty($_GET['id'])) { - echo $o; - exit(); + System::httpExit($o); } return $o; diff --git a/mod/fbrowser.php b/mod/fbrowser.php index d918021cf..1646b19df 100644 --- a/mod/fbrowser.php +++ b/mod/fbrowser.php @@ -24,6 +24,7 @@ use Friendica\App; use Friendica\Core\Renderer; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Photo; @@ -153,7 +154,6 @@ function fbrowser_content(App $a) if (!empty($_GET['mode'])) { return $o; } else { - echo $o; - exit(); + System::httpExit($o); } } diff --git a/mod/msearch.php b/mod/msearch.php index d29a2fc64..00a72ea9f 100644 --- a/mod/msearch.php +++ b/mod/msearch.php @@ -20,6 +20,7 @@ */ use Friendica\App; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\User; @@ -37,8 +38,7 @@ function msearch_post(App $a) if (!strlen($search)) { $output = ['total' => 0, 'items_page' => $perpage, 'page' => $page, 'results' => $results]; - echo json_encode($output); - exit(); + System::jsonExit($output); } $total = 0; @@ -60,7 +60,5 @@ function msearch_post(App $a) $output = ['total' => $total, 'items_page' => $perpage, 'page' => $page, 'results' => $results]; - echo json_encode($output); - - exit(); + System::jsonExit($output); } diff --git a/mod/oexchange.php b/mod/oexchange.php index 31306a608..2060ddd13 100644 --- a/mod/oexchange.php +++ b/mod/oexchange.php @@ -21,7 +21,9 @@ use Friendica\App; use Friendica\Core\Renderer; +use Friendica\Core\System; use Friendica\DI; +use Friendica\Module\Response; use Friendica\Module\Security\Login; function oexchange_init(App $a) { @@ -30,8 +32,7 @@ function oexchange_init(App $a) { $tpl = Renderer::getMarkupTemplate('oexchange_xrd.tpl'); $o = Renderer::replaceMacros($tpl, ['$base' => DI::baseUrl()]); - echo $o; - exit(); + System::httpExit($o, Response::TYPE_XML, 'application/xrd+xml'); } } diff --git a/mod/poco.php b/mod/poco.php index 1ccd74b7f..73179a33f 100644 --- a/mod/poco.php +++ b/mod/poco.php @@ -25,10 +25,11 @@ use Friendica\Content\Text\BBCode; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\Renderer; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; +use Friendica\Module\Response; use Friendica\Util\DateTimeFormat; -use Friendica\Util\Strings; use Friendica\Util\XML; function poco_init(App $a) { @@ -229,14 +230,10 @@ function poco_init(App $a) { Logger::info("End of poco"); if ($format === 'xml') { - header('Content-type: text/xml'); - echo Renderer::replaceMacros(Renderer::getMarkupTemplate('poco_xml.tpl'), XML::arrayEscape(['$response' => $ret])); - exit(); + System::httpExit(Renderer::replaceMacros(Renderer::getMarkupTemplate('poco_xml.tpl'), XML::arrayEscape(['$response' => $ret])), Response::TYPE_XML); } if ($format === 'json') { - header('Content-type: application/json'); - echo json_encode($ret); - exit(); + System::jsonExit($ret); } else { throw new \Friendica\Network\HTTPException\InternalServerErrorException(); } diff --git a/mod/wall_attach.php b/mod/wall_attach.php index 98dc3e1cb..607d0fe7a 100644 --- a/mod/wall_attach.php +++ b/mod/wall_attach.php @@ -21,6 +21,7 @@ use Friendica\App; use Friendica\Core\Session; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Attach; @@ -36,15 +37,13 @@ function wall_attach_post(App $a) { $owner = User::getOwnerDataByNick($nick); if (!DBA::isResult($owner)) { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Invalid request.')]); - exit(); + System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]); } return; } } else { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Invalid request.')]); - exit(); + System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]); } return; @@ -65,8 +64,7 @@ function wall_attach_post(App $a) { if (!$can_post) { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Permission denied.')]); - exit(); + System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]); } notice(DI::l10n()->t('Permission denied.') . EOL ); exit(); @@ -74,7 +72,7 @@ function wall_attach_post(App $a) { if (empty($_FILES['userfile'])) { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Invalid request.')]); + System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]); } exit(); } @@ -93,23 +91,23 @@ function wall_attach_post(App $a) { if ($filesize <= 0) { $msg = DI::l10n()->t('Sorry, maybe your upload is bigger than the PHP configuration allows') . EOL .(DI::l10n()->t('Or - did you try to upload an empty file?')); + @unlink($src); if ($r_json) { - echo json_encode(['error' => $msg]); + System::jsonExit(['error' => $msg]); } else { notice($msg); } - @unlink($src); exit(); } if ($maxfilesize && $filesize > $maxfilesize) { $msg = DI::l10n()->t('File exceeds size limit of %s', Strings::formatBytes($maxfilesize)); + @unlink($src); if ($r_json) { - echo json_encode(['error' => $msg]); + System::jsonExit(['error' => $msg]); } else { echo $msg . EOL; } - @unlink($src); exit(); } @@ -120,7 +118,7 @@ function wall_attach_post(App $a) { if ($newid === false) { $msg = DI::l10n()->t('File upload failed.'); if ($r_json) { - echo json_encode(['error' => $msg]); + System::jsonExit(['error' => $msg]); } else { echo $msg . EOL; } @@ -128,8 +126,7 @@ function wall_attach_post(App $a) { } if ($r_json) { - echo json_encode(['ok' => true, 'id' => $newid]); - exit(); + System::jsonExit(['ok' => true, 'id' => $newid]); } $lf = "\n"; diff --git a/mod/wall_upload.php b/mod/wall_upload.php index a9828e61f..79f1dcc41 100644 --- a/mod/wall_upload.php +++ b/mod/wall_upload.php @@ -28,6 +28,7 @@ use Friendica\App; use Friendica\Core\Logger; use Friendica\Core\Session; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Photo; @@ -50,8 +51,7 @@ function wall_upload_post(App $a, $desktopmode = true) $user = DBA::selectFirst('owner-view', ['id', 'uid', 'nickname', 'page-flags'], ['nickname' => $nick, 'blocked' => false]); if (!DBA::isResult($user)) { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Invalid request.')]); - exit(); + System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]); } return; } @@ -60,8 +60,7 @@ function wall_upload_post(App $a, $desktopmode = true) } } else { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Invalid request.')]); - exit(); + System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]); } return; } @@ -87,8 +86,7 @@ function wall_upload_post(App $a, $desktopmode = true) if (!$can_post) { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Permission denied.')]); - exit(); + System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]); } notice(DI::l10n()->t('Permission denied.')); exit(); @@ -96,7 +94,7 @@ function wall_upload_post(App $a, $desktopmode = true) if (empty($_FILES['userfile']) && empty($_FILES['media'])) { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Invalid request.')]); + System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]); } exit(); } @@ -147,8 +145,7 @@ function wall_upload_post(App $a, $desktopmode = true) if ($src == "") { if ($r_json) { - echo json_encode(['error' => DI::l10n()->t('Invalid request.')]); - exit(); + System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]); } notice(DI::l10n()->t('Invalid request.')); exit(); @@ -164,12 +161,12 @@ function wall_upload_post(App $a, $desktopmode = true) if (!$Image->isValid()) { $msg = DI::l10n()->t('Unable to process image.'); + @unlink($src); if ($r_json) { - echo json_encode(['error' => $msg]); + System::jsonExit(['error' => $msg]); } else { echo $msg. EOL; } - @unlink($src); exit(); } @@ -202,12 +199,12 @@ function wall_upload_post(App $a, $desktopmode = true) if ($filesize > $maximagesize) { Logger::notice('Image size is too big', ['size' => $filesize, 'max' => $maximagesize]); $msg = DI::l10n()->t('Image exceeds size limit of %s', Strings::formatBytes($maximagesize)); + @unlink($src); if ($r_json) { - echo json_encode(['error' => $msg]); + System::jsonExit(['error' => $msg]); } else { echo $msg. EOL; } - @unlink($src); exit(); } } @@ -228,7 +225,7 @@ function wall_upload_post(App $a, $desktopmode = true) if (!$r) { $msg = DI::l10n()->t('Image upload failed.'); if ($r_json) { - echo json_encode(['error' => $msg]); + System::jsonExit(['error' => $msg]); } else { echo $msg. EOL; } @@ -255,8 +252,7 @@ function wall_upload_post(App $a, $desktopmode = true) $photo = Photo::selectFirst(['id', 'datasize', 'width', 'height', 'type'], ['resource-id' => $resource_id], ['order' => ['width']]); if (!$photo) { if ($r_json) { - echo json_encode(['error' => '']); - exit(); + System::jsonExit(['error' => '']); } return false; } @@ -272,8 +268,7 @@ function wall_upload_post(App $a, $desktopmode = true) $picture["preview"] = DI::baseUrl() . "/photo/{$resource_id}-{$smallest}." . $Image->getExt(); if ($r_json) { - echo json_encode(['picture' => $picture]); - exit(); + System::jsonExit(['picture' => $picture]); } Logger::info("upload done"); return $picture; @@ -282,8 +277,7 @@ function wall_upload_post(App $a, $desktopmode = true) Logger::info("upload done"); if ($r_json) { - echo json_encode(['ok' => true]); - exit(); + System::jsonExit(['ok' => true]); } echo "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $page_owner_nick . '/image/' . $resource_id . '][img]' . DI::baseUrl() . "/photo/{$resource_id}-{$smallest}.".$Image->getExt()."[/img][/url]\n\n"; diff --git a/src/App/Page.php b/src/App/Page.php index 0d7b16436..41d50da05 100644 --- a/src/App/Page.php +++ b/src/App/Page.php @@ -31,7 +31,9 @@ use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\Renderer; +use Friendica\Core\System; use Friendica\Core\Theme; +use Friendica\Module\Response; use Friendica\Network\HTTPException; use Friendica\Util\Network; use Friendica\Util\Strings; @@ -503,11 +505,7 @@ class Page implements ArrayAccess } if ($_GET["mode"] == "raw") { - header("Content-type: text/html; charset=utf-8"); - - echo substr($target->saveHTML(), 6, -8); - - exit(); + System::httpExit(substr($target->saveHTML(), 6, -8), Response::TYPE_HTML); } } diff --git a/src/Core/System.php b/src/Core/System.php index 16bc2360e..66b6b25a5 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -306,25 +306,41 @@ class System * @param string $content Response body. Optional. * @throws \Exception */ - public static function httpExit($val, $message = '', $content = '') + public static function httpError($httpCode, $message = '', $content = '') { - if ($val >= 400) { - Logger::debug('Exit with error', ['code' => $val, 'message' => $message, 'callstack' => System::callstack(20), 'method' => DI::args()->getMethod(), 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '']); + if ($httpCode >= 400) { + Logger::debug('Exit with error', ['code' => $httpCode, 'message' => $message, 'callstack' => System::callstack(20), 'method' => DI::args()->getMethod(), 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '']); } - DI::apiResponse()->setStatus($val, $message); + DI::apiResponse()->setStatus($httpCode, $message); DI::apiResponse()->addContent($content); DI::page()->exit(DI::apiResponse()->generate()); exit(); } - public static function jsonError($httpCode, $data, $content_type = 'application/json') + /** + * This function adds the content and a content-teype HTTP header to the output. + * After finishing the process is getting killed. + * + * @param string $content + * @param [type] $responce + * @param string|null $content_type + * @return void + */ + public static function httpExit(string $content, string $responce = Response::TYPE_HTML, ?string $content_type = null) { + DI::apiResponse()->setType($responce, $content_type); + DI::apiResponse()->addContent($content); + DI::page()->exit(DI::apiResponse()->generate()); + exit(); + } + + public static function jsonError($httpCode, $content, $content_type = 'application/json') { if ($httpCode >= 400) { Logger::debug('Exit with error', ['code' => $httpCode, 'content_type' => $content_type, 'callstack' => System::callstack(20), 'method' => DI::args()->getMethod(), 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '']); } DI::apiResponse()->setStatus($httpCode); - self::jsonExit($data, $content_type); + self::jsonExit($content, $content_type); } /** @@ -334,14 +350,14 @@ class System * and adds an application/json HTTP header to the output. * After finishing the process is getting killed. * - * @param mixed $x The input content + * @param mixed $content The input content * @param string $content_type Type of the input (Default: 'application/json') * @param integer $options JSON options * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function jsonExit($x, $content_type = 'application/json', int $options = 0) { + public static function jsonExit($content, $content_type = 'application/json', int $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) { DI::apiResponse()->setType(Response::TYPE_JSON, $content_type); - DI::apiResponse()->addContent(json_encode($x, $options)); + DI::apiResponse()->addContent(json_encode($content, $options)); DI::page()->exit(DI::apiResponse()->generate()); exit(); } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index e23fa3169..acb4bc10e 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -1474,7 +1474,8 @@ class Contact if ($pager->getStart() == 0) { $cdata = Contact::getPublicAndUserContactID($cid, local_user()); if (!empty($cdata['public'])) { - $condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ?)", $cdata['public']]; + $condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", + $cdata['public'], Post\Collection::FEATURED]; $pinned = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params)); $items = array_merge($pinned, $items); } diff --git a/src/Module/AccountManagementControlDocument.php b/src/Module/AccountManagementControlDocument.php index cd17f758b..82997161d 100644 --- a/src/Module/AccountManagementControlDocument.php +++ b/src/Module/AccountManagementControlDocument.php @@ -22,6 +22,7 @@ namespace Friendica\Module; use Friendica\BaseModule; +use Friendica\Core\System; /** * Static definition for the Firefox Account Manager @@ -78,7 +79,6 @@ class AccountManagementControlDocument extends BaseModule ], ]; - echo json_encode($output); - exit(); + System::jsonExit($output); } } diff --git a/src/Module/ActivityPub/Featured.php b/src/Module/ActivityPub/Featured.php new file mode 100644 index 000000000..a4b8c3fef --- /dev/null +++ b/src/Module/ActivityPub/Featured.php @@ -0,0 +1,51 @@ +. + * + */ + +namespace Friendica\Module\ActivityPub; + +use Friendica\BaseModule; +use Friendica\Core\System; +use Friendica\Model\User; +use Friendica\Protocol\ActivityPub; + +/** + * ActivityPub featured posts + */ +class Featured extends BaseModule +{ + protected function rawContent(array $request = []) + { + if (empty($this->parameters['nickname'])) { + throw new \Friendica\Network\HTTPException\NotFoundException(); + } + + $owner = User::getOwnerDataByNick($this->parameters['nickname']); + if (empty($owner)) { + throw new \Friendica\Network\HTTPException\NotFoundException(); + } + + $page = $request['page'] ?? null; + + $featured = ActivityPub\Transmitter::getFeatured($owner, $page); + + System::jsonExit($featured, 'application/activity+json'); + } +} diff --git a/src/Module/ActivityPub/Followers.php b/src/Module/ActivityPub/Followers.php index 54584de18..b04d1976e 100644 --- a/src/Module/ActivityPub/Followers.php +++ b/src/Module/ActivityPub/Followers.php @@ -22,6 +22,7 @@ namespace Friendica\Module\ActivityPub; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Protocol\ActivityPub; @@ -48,8 +49,6 @@ class Followers extends BaseModule $followers = ActivityPub\Transmitter::getContacts($owner, [Contact::FOLLOWER, Contact::FRIEND], 'followers', $page, (string)HTTPSignature::getSigner('', $_SERVER)); - header('Content-Type: application/activity+json'); - echo json_encode($followers); - exit(); + System::jsonExit($followers, 'application/activity+json'); } } diff --git a/src/Module/ActivityPub/Following.php b/src/Module/ActivityPub/Following.php index 67d3010a3..e3ae10e87 100644 --- a/src/Module/ActivityPub/Following.php +++ b/src/Module/ActivityPub/Following.php @@ -22,6 +22,7 @@ namespace Friendica\Module\ActivityPub; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Protocol\ActivityPub; @@ -46,8 +47,6 @@ class Following extends BaseModule $following = ActivityPub\Transmitter::getContacts($owner, [Contact::SHARING, Contact::FRIEND], 'following', $page); - header('Content-Type: application/activity+json'); - echo json_encode($following); - exit(); + System::jsonExit($following, 'application/activity+json'); } } diff --git a/src/Module/ActivityPub/Inbox.php b/src/Module/ActivityPub/Inbox.php index b16eb9fd4..a9858d94c 100644 --- a/src/Module/ActivityPub/Inbox.php +++ b/src/Module/ActivityPub/Inbox.php @@ -50,7 +50,7 @@ class Inbox extends BaseModule $filename = 'failed-activitypub'; } $tempfile = tempnam(System::getTempPath(), $filename); - file_put_contents($tempfile, json_encode(['parameters' => $this->parameters, 'header' => $_SERVER, 'body' => $postdata], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + file_put_contents($tempfile, json_encode(['parameters' => $this->parameters, 'header' => $_SERVER, 'body' => $postdata], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); Logger::notice('Incoming message stored', ['file' => $tempfile]); } diff --git a/src/Module/ActivityPub/Objects.php b/src/Module/ActivityPub/Objects.php index f3a37b7da..d52c02bef 100644 --- a/src/Module/ActivityPub/Objects.php +++ b/src/Module/ActivityPub/Objects.php @@ -31,7 +31,6 @@ use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Network\HTTPException; use Friendica\Protocol\ActivityPub; -use Friendica\Security\PermissionSet\Repository\PermissionSet; use Friendica\Util\HTTPSignature; use Friendica\Util\Network; use Friendica\Util\Strings; @@ -130,6 +129,7 @@ class Objects extends BaseModule // Relaxed CORS header for public items header('Access-Control-Allow-Origin: *'); + System::jsonExit($data, 'application/activity+json'); } } diff --git a/src/Module/ActivityPub/Outbox.php b/src/Module/ActivityPub/Outbox.php index 3fa143cfb..4bc1ca693 100644 --- a/src/Module/ActivityPub/Outbox.php +++ b/src/Module/ActivityPub/Outbox.php @@ -22,6 +22,7 @@ namespace Friendica\Module\ActivityPub; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\Model\User; use Friendica\Protocol\ActivityPub; use Friendica\Util\HTTPSignature; @@ -46,8 +47,7 @@ class Outbox extends BaseModule $requester = HTTPSignature::getSigner('', $_SERVER); $outbox = ActivityPub\Transmitter::getOutbox($owner, $page, $requester); - header('Content-Type: application/activity+json'); - echo json_encode($outbox); - exit(); + + System::jsonExit($outbox, 'application/activity+json'); } } diff --git a/src/Module/DFRN/Poll.php b/src/Module/DFRN/Poll.php index 65a3448fb..516f86384 100644 --- a/src/Module/DFRN/Poll.php +++ b/src/Module/DFRN/Poll.php @@ -22,6 +22,8 @@ namespace Friendica\Module\DFRN; use Friendica\BaseModule; +use Friendica\Core\System; +use Friendica\Module\Response; use Friendica\Protocol\OStatus; /** @@ -31,9 +33,7 @@ class Poll extends BaseModule { protected function rawContent(array $request = []) { - header("Content-type: application/atom+xml"); - $last_update = $_GET['last_update'] ?? ''; - echo OStatus::feed($this->parameters['nickname'], $last_update, 10); - exit(); + $last_update = $request['last_update'] ?? ''; + System::httpExit(OStatus::feed($this->parameters['nickname'], $last_update, 10), Response::TYPE_ATOM); } } diff --git a/src/Module/Diaspora/Fetch.php b/src/Module/Diaspora/Fetch.php index 97cbb5878..bcb6ba0ca 100644 --- a/src/Module/Diaspora/Fetch.php +++ b/src/Module/Diaspora/Fetch.php @@ -28,6 +28,7 @@ use Friendica\DI; use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Model\User; +use Friendica\Module\Response; use Friendica\Network\HTTPException; use Friendica\Protocol\Diaspora; use Friendica\Util\Strings; @@ -84,9 +85,6 @@ class Fetch extends BaseModule $xml = Diaspora::buildPostXml($status["type"], $status["message"]); // Send the envelope - header("Content-Type: application/magic-envelope+xml; charset=utf-8"); - echo Diaspora::buildMagicEnvelope($xml, $user); - - exit(); + System::httpExit(Diaspora::buildMagicEnvelope($xml, $user), Response::TYPE_XML, 'application/magic-envelope+xml'); } } diff --git a/src/Module/Events/Json.php b/src/Module/Events/Json.php index 8c6192e0a..dac6e5c1b 100644 --- a/src/Module/Events/Json.php +++ b/src/Module/Events/Json.php @@ -21,6 +21,7 @@ namespace Friendica\Module\Events; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Event; @@ -94,9 +95,7 @@ class Json extends \Friendica\BaseModule $events = self::map($events); } - header('Content-Type: application/json'); - echo json_encode($events); - exit(); + System::jsonExit($events); } private static function map(array $events): array diff --git a/src/Module/Feed.php b/src/Module/Feed.php index 3a1246028..8db92c275 100644 --- a/src/Module/Feed.php +++ b/src/Module/Feed.php @@ -22,6 +22,7 @@ namespace Friendica\Module; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\DI; use Friendica\Protocol\Feed as ProtocolFeed; @@ -43,10 +44,8 @@ class Feed extends BaseModule { protected function content(array $request = []): string { - $a = DI::app(); - - $last_update = $_GET['last_update'] ?? ''; - $nocache = !empty($_GET['nocache']) && local_user(); + $last_update = $request['last_update'] ?? ''; + $nocache = !empty($request['nocache']) && local_user(); $type = null; // @TODO: Replace with parameter from router @@ -67,8 +66,6 @@ class Feed extends BaseModule $type = 'posts'; } - header("Content-type: application/atom+xml; charset=utf-8"); - echo ProtocolFeed::atom($this->parameters['nickname'], $last_update, 10, $type, $nocache, true); - exit(); + System::httpExit(ProtocolFeed::atom($this->parameters['nickname'], $last_update, 10, $type, $nocache, true), Response::TYPE_ATOM); } } diff --git a/src/Module/Friendica.php b/src/Module/Friendica.php index 3fccda5f8..c5a66fa3d 100644 --- a/src/Module/Friendica.php +++ b/src/Module/Friendica.php @@ -182,8 +182,6 @@ class Friendica extends BaseModule 'no_scrape_url' => DI::baseUrl()->get() . '/noscrape', ]; - header('Content-type: application/json; charset=utf-8'); - echo json_encode($data); - exit(); + System::jsonExit($data); } } diff --git a/src/Module/Notifications/Ping.php b/src/Module/Notifications/Ping.php index 1501c4277..b0412b5dc 100644 --- a/src/Module/Notifications/Ping.php +++ b/src/Module/Notifications/Ping.php @@ -291,9 +291,7 @@ class Ping extends BaseModule if (isset($_GET['callback'])) { // JSONP support - header("Content-type: application/javascript"); - echo $_GET['callback'] . '(' . json_encode(['result' => $data]) . ')'; - exit; + System::httpExit($_GET['callback'] . '(' . json_encode(['result' => $data]) . ')', Response::TYPE_BLANK, 'application/javascript'); } else { System::jsonExit(['result' => $data]); } diff --git a/src/Module/OpenSearch.php b/src/Module/OpenSearch.php index 94215aaa7..35d6890ec 100644 --- a/src/Module/OpenSearch.php +++ b/src/Module/OpenSearch.php @@ -24,6 +24,7 @@ namespace Friendica\Module; use DOMDocument; use DOMElement; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\DI; use Friendica\Util\XML; @@ -38,8 +39,6 @@ class OpenSearch extends BaseModule */ protected function rawContent(array $request = []) { - header('Content-type: application/opensearchdescription+xml'); - $hostname = DI::baseUrl()->getHostname(); $baseUrl = DI::baseUrl()->get(); @@ -85,8 +84,6 @@ class OpenSearch extends BaseModule 'template' => "$baseUrl/opensearch", ]); - echo $xml->saveXML(); - - exit(); + System::httpExit($xml->saveXML(), Response::TYPE_XML, 'application/opensearchdescription+xml'); } } diff --git a/src/Module/ParseUrl.php b/src/Module/ParseUrl.php index afb54ea32..91d09240b 100644 --- a/src/Module/ParseUrl.php +++ b/src/Module/ParseUrl.php @@ -88,8 +88,7 @@ class ParseUrl extends BaseModule if ($format == 'json') { System::jsonExit($arr['text']); } else { - echo $arr['text']; - exit(); + System::httpExit($arr['text']); } } @@ -122,8 +121,7 @@ class ParseUrl extends BaseModule System::jsonExit($ret); } else { - echo BBCode::embedURL($url, empty($_GET['noAttachment']), $title, $description, $_GET['tags'] ?? ''); - exit(); + System::httpExit(BBCode::embedURL($url, empty($_GET['noAttachment']), $title, $description, $_GET['tags'] ?? '')); } } } diff --git a/src/Module/PublicRSAKey.php b/src/Module/PublicRSAKey.php index d803de754..523ab174f 100644 --- a/src/Module/PublicRSAKey.php +++ b/src/Module/PublicRSAKey.php @@ -22,6 +22,7 @@ namespace Friendica\Module; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\DI; use Friendica\Model\User; use Friendica\Network\HTTPException\BadRequestException; @@ -48,9 +49,7 @@ class PublicRSAKey extends BaseModule Crypto::pemToMe($user['spubkey'], $modulus, $exponent); - header('Content-type: application/magic-public-key'); - echo 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true); - - exit(); + $content = 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true); + System::httpExit($content, Response::TYPE_BLANK, 'application/magic-public-key'); } } diff --git a/src/Module/ReallySimpleDiscovery.php b/src/Module/ReallySimpleDiscovery.php index d699455f4..9b8cb31a5 100644 --- a/src/Module/ReallySimpleDiscovery.php +++ b/src/Module/ReallySimpleDiscovery.php @@ -22,6 +22,7 @@ namespace Friendica\Module; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\DI; use Friendica\Util\XML; @@ -33,10 +34,8 @@ class ReallySimpleDiscovery extends BaseModule { protected function rawContent(array $request = []) { - header('Content-Type: text/xml'); - $xml = null; - echo XML::fromArray([ + $content = XML::fromArray([ 'rsd' => [ '@attributes' => [ 'version' => '1.0', @@ -69,6 +68,6 @@ class ReallySimpleDiscovery extends BaseModule ], ], ], $xml); - exit(); + System::httpExit($content, Response::TYPE_XML); } } diff --git a/src/Module/Response.php b/src/Module/Response.php index f9d46da83..ba7d43202 100644 --- a/src/Module/Response.php +++ b/src/Module/Response.php @@ -97,7 +97,7 @@ class Response implements ICanCreateResponses switch ($type) { case static::TYPE_HTML: - $content_type = $content_type ?? 'text/html'; + $content_type = $content_type ?? 'text/html; charset=utf-8'; break; case static::TYPE_JSON: $content_type = $content_type ?? 'application/json'; diff --git a/src/Module/Search/Acl.php b/src/Module/Search/Acl.php index f00c9f9ec..88b1788de 100644 --- a/src/Module/Search/Acl.php +++ b/src/Module/Search/Acl.php @@ -27,6 +27,7 @@ use Friendica\Core\Hook; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\Search; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; @@ -61,8 +62,7 @@ class Acl extends BaseModule $o = self::regularContactSearch($type); } - echo json_encode($o); - exit; + System::jsonExit($o); } private static function globalContactSearch() diff --git a/src/Module/Special/HTTPException.php b/src/Module/Special/HTTPException.php index 95448606e..8b520f6b5 100644 --- a/src/Module/Special/HTTPException.php +++ b/src/Module/Special/HTTPException.php @@ -74,7 +74,7 @@ class HTTPException $content = Renderer::replaceMacros($tpl, self::getVars($e)); } - System::httpExit($e->getCode(), $e->getDescription(), $content); + System::httpError($e->getCode(), $e->getDescription(), $content); } /** diff --git a/src/Module/Statistics.php b/src/Module/Statistics.php index 88b01e255..db145bd43 100644 --- a/src/Module/Statistics.php +++ b/src/Module/Statistics.php @@ -26,6 +26,7 @@ use Friendica\BaseModule; use Friendica\Core\Addon; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\L10n; +use Friendica\Core\System; use Friendica\Network\HTTPException\NotFoundException; use Friendica\Util\Profiler; use Psr\Log\LoggerInterface; @@ -78,9 +79,7 @@ class Statistics extends BaseModule 'services' => $services, ], $services); - header("Content-Type: application/json"); - echo json_encode($statistics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); $this->logger->debug("statistics.", ['statistics' => $statistics]); - exit(); + System::jsonExit($statistics); } } diff --git a/src/Module/ThemeDetails.php b/src/Module/ThemeDetails.php index cc5b75154..6398a00c9 100644 --- a/src/Module/ThemeDetails.php +++ b/src/Module/ThemeDetails.php @@ -22,6 +22,7 @@ namespace Friendica\Module; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\Core\Theme; /** @@ -40,7 +41,7 @@ class ThemeDetails extends BaseModule $version = $info['version'] ?? ''; $credits = $info['credits'] ?? ''; - echo json_encode([ + System::jsonExit([ 'img' => Theme::getScreenshot($theme), 'desc' => $description, 'version' => $version, diff --git a/src/Module/WellKnown/HostMeta.php b/src/Module/WellKnown/HostMeta.php index c5553272e..2ca66f93b 100644 --- a/src/Module/WellKnown/HostMeta.php +++ b/src/Module/WellKnown/HostMeta.php @@ -23,7 +23,9 @@ namespace Friendica\Module\WellKnown; use Friendica\BaseModule; use Friendica\Core\Renderer; +use Friendica\Core\System; use Friendica\DI; +use Friendica\Module\Response; use Friendica\Protocol\Salmon; use Friendica\Util\Crypto; @@ -37,8 +39,6 @@ class HostMeta extends BaseModule { $config = DI::config(); - header('Content-type: text/xml'); - if (!$config->get('system', 'site_pubkey', false)) { $res = Crypto::newKeypair(1024); @@ -47,13 +47,13 @@ class HostMeta extends BaseModule } $tpl = Renderer::getMarkupTemplate('xrd_host.tpl'); - echo Renderer::replaceMacros($tpl, [ + $content = Renderer::replaceMacros($tpl, [ '$zhost' => DI::baseUrl()->getHostname(), '$zroot' => DI::baseUrl()->get(), '$domain' => DI::baseUrl()->get(), '$bigkey' => Salmon::salmonKey($config->get('system', 'site_pubkey')) ]); - exit(); + System::httpExit($content, Response::TYPE_XML, 'application/xrd+xml'); } } diff --git a/src/Module/WellKnown/NodeInfo.php b/src/Module/WellKnown/NodeInfo.php index d34823bb4..0feb5e379 100644 --- a/src/Module/WellKnown/NodeInfo.php +++ b/src/Module/WellKnown/NodeInfo.php @@ -22,6 +22,7 @@ namespace Friendica\Module\WellKnown; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\DI; /** @@ -51,8 +52,6 @@ class NodeInfo extends BaseModule ] ]; - header('Content-type: application/json; charset=utf-8'); - echo json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - exit; + System::jsonExit($nodeinfo); } } diff --git a/src/Module/WellKnown/XSocialRelay.php b/src/Module/WellKnown/XSocialRelay.php index bf767a3e7..d89d08e08 100644 --- a/src/Module/WellKnown/XSocialRelay.php +++ b/src/Module/WellKnown/XSocialRelay.php @@ -22,6 +22,7 @@ namespace Friendica\Module\WellKnown; use Friendica\BaseModule; +use Friendica\Core\System; use Friendica\DI; use Friendica\Model\Search; use Friendica\Protocol\Relay; @@ -76,8 +77,6 @@ class XSocialRelay extends BaseModule $relay['protocols']['diaspora'] = ['receive' => DI::baseUrl()->get() . '/receive/public']; } - header('Content-type: application/json; charset=utf-8'); - echo json_encode($relay, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - exit; + System::jsonExit($relay); } } diff --git a/src/Module/Xrd.php b/src/Module/Xrd.php index d24628ce6..3d8f50fc2 100644 --- a/src/Module/Xrd.php +++ b/src/Module/Xrd.php @@ -46,9 +46,9 @@ class Xrd extends BaseModule $uri = urldecode(trim($_GET['uri'])); if (strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'application/jrd+json') !== false) { - $mode = 'json'; + $mode = Response::TYPE_JSON; } else { - $mode = 'xml'; + $mode = Response::TYPE_XML; } } else { if (empty($_GET['resource'])) { @@ -57,9 +57,9 @@ class Xrd extends BaseModule $uri = urldecode(trim($_GET['resource'])); if (strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'application/xrd+xml') !== false) { - $mode = 'xml'; + $mode = Response::TYPE_XML; } else { - $mode = 'json'; + $mode = Response::TYPE_JSON; } } @@ -101,7 +101,7 @@ class Xrd extends BaseModule $avatar = ['type' => 'image/jpeg']; } - if ($mode == 'xml') { + if ($mode == Response::TYPE_JSON) { self::printXML($alias, DI::baseUrl()->get(), $user, $owner, $avatar); } else { self::printJSON($alias, DI::baseUrl()->get(), $owner, $avatar); @@ -238,9 +238,6 @@ class Xrd extends BaseModule { $salmon_key = Salmon::salmonKey($owner['spubkey']); - header('Access-Control-Allow-Origin: *'); - header('Content-type: text/xml'); - $tpl = Renderer::getMarkupTemplate('xrd_person.tpl'); $o = Renderer::replaceMacros($tpl, [ @@ -263,7 +260,8 @@ class Xrd extends BaseModule $arr = ['user' => $user, 'xml' => $o]; Hook::callAll('personal_xrd', $arr); - echo $arr['xml']; - exit(); + header('Access-Control-Allow-Origin: *'); + + System::httpExit($arr['xml'], Response::TYPE_XML, 'application/xrd+xml'); } } diff --git a/src/Protocol/ActivityPub.php b/src/Protocol/ActivityPub.php index 9ab2a3c88..78496e243 100644 --- a/src/Protocol/ActivityPub.php +++ b/src/Protocol/ActivityPub.php @@ -65,6 +65,10 @@ class ActivityPub 'diaspora' => 'https://diasporafoundation.org/ns/', 'litepub' => 'http://litepub.social/ns#', 'toot' => 'http://joinmastodon.org/ns#', + 'featured' => [ + "@id" => "toot:featured", + "@type" => "@id", + ], 'schema' => 'http://schema.org#', 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag', diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index caf97231f..679733044 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -802,7 +802,7 @@ class Receiver } $tempfile = tempnam(System::getTempPath(), $file); - file_put_contents($tempfile, json_encode(['activity' => $activity, 'body' => $body, 'uid' => $uid, 'trust_source' => $trust_source, 'push' => $push, 'signer' => $signer, 'object_data' => $object_data], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + file_put_contents($tempfile, json_encode(['activity' => $activity, 'body' => $body, 'uid' => $uid, 'trust_source' => $trust_source, 'push' => $push, 'signer' => $signer, 'object_data' => $object_data], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); Logger::notice('Unknown activity stored', ['type' => $type, 'object_type' => $object_data['object_type'], $object_data['object_object_type'] ?? '', 'file' => $tempfile]); } diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 553993edc..a39a4c502 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -288,6 +288,69 @@ class Transmitter return $data; } + /** + * Public posts for the given owner + * + * @param array $owner Owner array + * @param integer $page Page number + * + * @return array of posts + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function getFeatured($owner, $page = null) + { + $condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", + Contact::getIdForURL($owner['url'], 0, false), Post\Collection::FEATURED]; + + $condition = DBA::mergeConditions($condition, + ['uid' => $owner['uid'], + 'author-id' => Contact::getIdForURL($owner['url'], 0, false), + 'private' => [Item::PUBLIC, Item::UNLISTED], + 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], + 'network' => Protocol::FEDERATED, + 'parent-network' => Protocol::FEDERATED, + 'origin' => true, + 'deleted' => false, + 'visible' => true]); + + $count = Post::count($condition); + + $data = ['@context' => ActivityPub::CONTEXT]; + $data['id'] = DI::baseUrl() . '/featured/' . $owner['nickname']; + $data['type'] = 'OrderedCollection'; + $data['totalItems'] = $count; + + if (empty($page)) { + $data['first'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=1'; + } else { + $data['type'] = 'OrderedCollectionPage'; + $list = []; + + $items = Post::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]); + while ($item = Post::fetch($items)) { + $activity = self::createActivityFromItem($item['id'], true); + $activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type']; + + // Only list "Create" activity objects here, no reshares + if (!empty($activity['object']) && ($activity['type'] == 'Create')) { + $list[] = $activity['object']; + } + } + DBA::close($items); + + if (!empty($list)) { + $data['next'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=' . ($page + 1); + } + + $data['partOf'] = DI::baseUrl() . '/featured/' . $owner['nickname']; + + $data['orderedItems'] = $list; + } + + return $data; + } + /** * Return the service array containing information the used software and it's url * @@ -328,8 +391,9 @@ class Transmitter if ($uid != 0) { $data['following'] = DI::baseUrl() . '/following/' . $owner['nick']; $data['followers'] = DI::baseUrl() . '/followers/' . $owner['nick']; - $data['inbox'] = DI::baseUrl() . '/inbox/' . $owner['nick']; - $data['outbox'] = DI::baseUrl() . '/outbox/' . $owner['nick']; + $data['inbox'] = DI::baseUrl() . '/inbox/' . $owner['nick']; + $data['outbox'] = DI::baseUrl() . '/outbox/' . $owner['nick']; + $data['featured'] = DI::baseUrl() . '/featured/' . $owner['nick']; } else { $data['inbox'] = DI::baseUrl() . '/friendica/inbox'; } diff --git a/static/routes.config.php b/static/routes.config.php index 21afd2307..ddd678d27 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -361,7 +361,9 @@ return [ '/dirfind' => [Module\Search\Directory::class, [R::GET]], '/directory' => [Module\Directory::class, [R::GET]], - '/events/json' => [Module\Events\Json::class, [R::GET]], + '/events/json' => [Module\Events\Json::class, [R::GET]], + + '/featured/{nickname}' => [Module\ActivityPub\Featured::class, [R::GET]], '/feed' => [ '/{nickname}' => [Module\Feed::class, [R::GET]], diff --git a/view/templates/oexchange_xrd.tpl b/view/templates/oexchange_xrd.tpl index aa952c0f4..7c31f2126 100644 --- a/view/templates/oexchange_xrd.tpl +++ b/view/templates/oexchange_xrd.tpl @@ -1,4 +1,3 @@ - @@ -31,4 +30,3 @@ type="text/html" /> - diff --git a/view/templates/poco_xml.tpl b/view/templates/poco_xml.tpl index 82c67c8de..0e38a692c 100644 --- a/view/templates/poco_xml.tpl +++ b/view/templates/poco_xml.tpl @@ -1,4 +1,3 @@ - {{if $response.sorted}}{{$response.sorted}}{{/if}}