diff --git a/mod/network.php b/mod/network.php index fddec60c8..515219827 100644 --- a/mod/network.php +++ b/mod/network.php @@ -40,22 +40,6 @@ function network_init(App $a) Hook::add('head', __FILE__, 'network_infinite_scroll_head'); - $search = (!empty($_GET['search']) ? Strings::escapeHtml($_GET['search']) : ''); - - if (($search != '') && !empty($_GET['submit'])) { - $a->internalRedirect('search?search=' . urlencode($search)); - } - - if (!empty($_GET['save'])) { - $exists = DBA::exists('search', ['uid' => local_user(), 'term' => $search]); - if (!$exists) { - DBA::insert('search', ['uid' => local_user(), 'term' => $search]); - } - } - if (!empty($_GET['remove'])) { - DBA::delete('search', ['uid' => local_user(), 'term' => $search]); - } - $is_a_date_query = false; $group_id = (($a->argc > 1 && is_numeric($a->argv[1])) ? intval($a->argv[1]) : 0); @@ -155,47 +139,10 @@ function network_init(App $a) $a->page['aside'] .= ForumManager::widget(local_user(), $cid); $a->page['aside'] .= Widget::postedByYear('network', local_user(), false); $a->page['aside'] .= Widget::networks('network', defaults($_GET, 'nets', '') ); - $a->page['aside'] .= saved_searches($search); + $a->page['aside'] .= Widget\SavedSearches::getHTML($a->query_string); $a->page['aside'] .= Widget::fileAs('network', defaults($_GET, 'file', '') ); } -function saved_searches($search) -{ - $srchurl = '/network?f=' - . (!empty($_GET['cid']) ? '&cid=' . rawurlencode($_GET['cid']) : '') - . (!empty($_GET['star']) ? '&star=' . rawurlencode($_GET['star']) : '') - . (!empty($_GET['bmark']) ? '&bmark=' . rawurlencode($_GET['bmark']) : '') - . (!empty($_GET['conv']) ? '&conv=' . rawurlencode($_GET['conv']) : '') - . (!empty($_GET['nets']) ? '&nets=' . rawurlencode($_GET['nets']) : '') - . (!empty($_GET['cmin']) ? '&cmin=' . rawurlencode($_GET['cmin']) : '') - . (!empty($_GET['cmax']) ? '&cmax=' . rawurlencode($_GET['cmax']) : '') - . (!empty($_GET['file']) ? '&file=' . rawurlencode($_GET['file']) : ''); - ; - - $terms = DBA::select('search', ['id', 'term'], ['uid' => local_user()]); - $saved = []; - - while ($rr = DBA::fetch($terms)) { - $saved[] = [ - 'id' => $rr['id'], - 'term' => $rr['term'], - 'encodedterm' => urlencode($rr['term']), - 'delete' => L10n::t('Remove term'), - 'selected' => ($search == $rr['term']), - ]; - } - - $tpl = Renderer::getMarkupTemplate('saved_searches_aside.tpl'); - $o = Renderer::replaceMacros($tpl, [ - '$title' => L10n::t('Saved Searches'), - '$add' => L10n::t('add'), - '$searchbox' => HTML::search($search, 'netsearch-box', $srchurl), - '$saved' => $saved, - ]); - - return $o; -} - /** * Return selected tab from query * diff --git a/mod/search.php b/mod/search.php deleted file mode 100644 index b28ad7b0e..000000000 --- a/mod/search.php +++ /dev/null @@ -1,242 +0,0 @@ - $rr['id'], - 'term' => $rr['term'], - 'encodedterm' => urlencode($rr['term']), - 'delete' => L10n::t('Remove term'), - 'selected' => ($search==$rr['term']), - ]; - } - - - $tpl = Renderer::getMarkupTemplate("saved_searches_aside.tpl"); - - $o .= Renderer::replaceMacros($tpl, [ - '$title' => L10n::t('Saved Searches'), - '$add' => '', - '$searchbox' => '', - '$saved' => $saved, - ]); - } - - return $o; -} - - -function search_init(App $a) { - $search = (!empty($_GET['search']) ? Strings::escapeTags(trim(rawurldecode($_GET['search']))) : ''); - - if (local_user()) { - if (!empty($_GET['save']) && $search) { - $r = q("SELECT * FROM `search` WHERE `uid` = %d AND `term` = '%s' LIMIT 1", - intval(local_user()), - DBA::escape($search) - ); - if (!DBA::isResult($r)) { - DBA::insert('search', ['uid' => local_user(), 'term' => $search]); - } - } - if (!empty($_GET['remove']) && $search) { - DBA::delete('search', ['uid' => local_user(), 'term' => $search]); - } - - /// @todo Check if there is a case at all that "aside" is prefilled here - if (!isset($a->page['aside'])) { - $a->page['aside'] = ''; - } - - $a->page['aside'] .= search_saved_searches(); - } -} - -function search_content(App $a) { - if (Config::get('system','block_public') && !Session::isAuthenticated()) { - notice(L10n::t('Public access denied.') . EOL); - return; - } - - if (Config::get('system','local_search') && !Session::isAuthenticated()) { - $e = new \Friendica\Network\HTTPException\ForbiddenException(L10n::t("Only logged in users are permitted to perform a search.")); - $e->httpdesc = L10n::t("Public access denied."); - throw $e; - } - - if (Config::get('system','permit_crawling') && !Session::isAuthenticated()) { - // Default values: - // 10 requests are "free", after the 11th only a call per minute is allowed - - $free_crawls = intval(Config::get('system','free_crawls')); - if ($free_crawls == 0) - $free_crawls = 10; - - $crawl_permit_period = intval(Config::get('system','crawl_permit_period')); - if ($crawl_permit_period == 0) - $crawl_permit_period = 10; - - $remote = $_SERVER["REMOTE_ADDR"]; - $result = Cache::get("remote_search:".$remote); - if (!is_null($result)) { - $resultdata = json_decode($result); - if (($resultdata->time > (time() - $crawl_permit_period)) && ($resultdata->accesses > $free_crawls)) { - throw new \Friendica\Network\HTTPException\TooManyRequestsException(L10n::t("Only one search per minute is permitted for not logged in users.")); - } - Cache::set("remote_search:".$remote, json_encode(["time" => time(), "accesses" => $resultdata->accesses + 1]), Cache::HOUR); - } else - Cache::set("remote_search:".$remote, json_encode(["time" => time(), "accesses" => 1]), Cache::HOUR); - } - - Nav::setSelected('search'); - - $search = (!empty($_REQUEST['search']) ? Strings::escapeTags(trim(rawurldecode($_REQUEST['search']))) : ''); - - $tag = false; - if (!empty($_GET['tag'])) { - $tag = true; - $search = (!empty($_GET['tag']) ? '#' . Strings::escapeTags(trim(rawurldecode($_GET['tag']))) : ''); - } - - // contruct a wrapper for the search header - $o = Renderer::replaceMacros(Renderer::getMarkupTemplate("content_wrapper.tpl"),[ - 'name' => "search-header", - '$title' => L10n::t("Search"), - '$title_size' => 3, - '$content' => HTML::search($search,'search-box','search', false) - ]); - - if (strpos($search,'#') === 0) { - $tag = true; - $search = substr($search,1); - } - if (strpos($search,'@') === 0) { - return BaseSearchModule::performSearch(); - } - if (strpos($search,'!') === 0) { - return BaseSearchModule::performSearch(); - } - - if (parse_url($search, PHP_URL_SCHEME) != '') { - $id = Item::fetchByLink($search); - if (!empty($id)) { - $item = Item::selectFirst(['guid'], ['id' => $id]); - if (DBA::isResult($item)) { - $a->internalRedirect('display/' . $item['guid']); - } - } - } - - if (!empty($_GET['search-option'])) - switch($_GET['search-option']) { - case 'fulltext': - break; - case 'tags': - $tag = true; - break; - case 'contacts': - return BaseSearchModule::performSearch('@'); - case 'forums': - return BaseSearchModule::performSearch('!'); - } - - if (!$search) - return $o; - - if (Config::get('system','only_tag_search')) - $tag = true; - - // Here is the way permissions work in the search module... - // Only public posts can be shown - // OR your own posts if you are a logged in member - // No items will be shown if the member has a blocked profile wall. - - $pager = new Pager($a->query_string); - - if ($tag) { - Logger::log("Start tag search for '".$search."'", Logger::DEBUG); - - $condition = ["(`uid` = 0 OR (`uid` = ? AND NOT `global`)) - AND `otype` = ? AND `type` = ? AND `term` = ?", - local_user(), TERM_OBJ_POST, TERM_HASHTAG, $search]; - $params = ['order' => ['received' => true], - 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; - $terms = DBA::select('term', ['oid'], $condition, $params); - - $itemids = []; - while ($term = DBA::fetch($terms)) { - $itemids[] = $term['oid']; - } - DBA::close($terms); - - if (!empty($itemids)) { - $params = ['order' => ['id' => true]]; - $items = Item::selectForUser(local_user(), [], ['id' => $itemids], $params); - $r = Item::inArray($items); - } else { - $r = []; - } - } else { - Logger::log("Start fulltext search for '".$search."'", Logger::DEBUG); - - $condition = ["(`uid` = 0 OR (`uid` = ? AND NOT `global`)) - AND `body` LIKE CONCAT('%',?,'%')", - local_user(), $search]; - $params = ['order' => ['id' => true], - 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; - $items = Item::selectForUser(local_user(), [], $condition, $params); - $r = Item::inArray($items); - } - - if (!DBA::isResult($r)) { - info(L10n::t('No results.') . EOL); - return $o; - } - - - if ($tag) { - $title = L10n::t('Items tagged with: %s', $search); - } else { - $title = L10n::t('Results for: %s', $search); - } - - $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate("section_title.tpl"),[ - '$title' => $title - ]); - - Logger::log("Start Conversation for '".$search."'", Logger::DEBUG); - $o .= conversation($a, $r, $pager, 'search', false, false, 'commented', local_user()); - - $o .= $pager->renderMinimal(count($r)); - - Logger::log("Done '".$search."'", Logger::DEBUG); - - return $o; -} diff --git a/src/BaseModule.php b/src/BaseModule.php index 0383487ab..5185771d1 100644 --- a/src/BaseModule.php +++ b/src/BaseModule.php @@ -120,7 +120,7 @@ abstract class BaseModule extends BaseObject $a = \get_app(); $x = explode('.', $hash); - if (time() > (IntVal($x[0]) + $max_livetime)) { + if (time() > (intval($x[0]) + $max_livetime)) { return false; } diff --git a/src/Content/Text/HTML.php b/src/Content/Text/HTML.php index eee443a8f..ea9a4737c 100644 --- a/src/Content/Text/HTML.php +++ b/src/Content/Text/HTML.php @@ -893,9 +893,9 @@ class HTML * @param bool $aside Display the search widgit aside. * * @return string Formatted HTML. - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \Exception */ - public static function search($s, $id = 'search-box', $url = 'search', $aside = true) + public static function search($s, $id = 'search-box', $aside = true) { $mode = 'text'; @@ -905,24 +905,25 @@ class HTML $save_label = $mode === 'text' ? L10n::t('Save') : L10n::t('Follow'); $values = [ - '$s' => $s, - '$id' => $id, - '$action_url' => $url, - '$search_label' => L10n::t('Search'), - '$save_label' => $save_label, - '$savedsearch' => 'savedsearch', - '$search_hint' => L10n::t('@name, !forum, #tags, content'), - '$mode' => $mode - ]; + '$s' => $s, + '$q' => urlencode($s), + '$id' => $id, + '$search_label' => L10n::t('Search'), + '$save_label' => $save_label, + '$search_hint' => L10n::t('@name, !forum, #tags, content'), + '$mode' => $mode, + '$return_url' => urlencode('search?q=' . $s), + ]; if (!$aside) { - $values['$searchoption'] = [ - L10n::t("Full Text"), - L10n::t("Tags"), - L10n::t("Contacts")]; + $values['$search_options'] = [ + 'fulltext' => L10n::t('Full Text'), + 'tags' => L10n::t('Tags'), + 'contacts' => L10n::t('Contacts') + ]; if (Config::get('system', 'poco_local_search')) { - $values['$searchoption'][] = L10n::t("Forums"); + $values['$searchoption']['forums'] = L10n::t('Forums'); } } diff --git a/src/Content/Widget/SavedSearches.php b/src/Content/Widget/SavedSearches.php new file mode 100644 index 000000000..7f7c171e0 --- /dev/null +++ b/src/Content/Widget/SavedSearches.php @@ -0,0 +1,47 @@ + local_user()]); + if (DBA::isResult($saved_searches)) { + $saved = []; + foreach ($saved_searches as $saved_search) { + $saved[] = [ + 'id' => $saved_search['id'], + 'term' => $saved_search['term'], + 'encodedterm' => urlencode($saved_search['term']), + 'delete' => L10n::t('Remove term'), + 'selected' => $search == $saved_search['term'], + ]; + } + + $tpl = Renderer::getMarkupTemplate('widget/saved_searches.tpl'); + + $o = Renderer::replaceMacros($tpl, [ + '$title' => L10n::t('Saved Searches'), + '$add' => '', + '$searchbox' => '', + '$saved' => $saved, + '$return_url' => urlencode($return_url), + ]); + } + + return $o; + } +} diff --git a/src/Module/BaseSearchModule.php b/src/Module/BaseSearchModule.php index ed39f071c..9766c000c 100644 --- a/src/Module/BaseSearchModule.php +++ b/src/Module/BaseSearchModule.php @@ -2,6 +2,7 @@ namespace Friendica\Module; +use Friendica\App\Arguments; use Friendica\BaseModule; use Friendica\Content\ContactSelector; use Friendica\Content\Pager; @@ -23,13 +24,14 @@ class BaseSearchModule extends BaseModule /** * Performs a search with an optional prefix * + * @param string $search Search query * @param string $prefix A optional prefix (e.g. @ or !) for searching * * @return string * @throws HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function performSearch($prefix = '') + public static function performSearch($search, $prefix = '') { $a = self::getApp(); $config = $a->getConfig(); @@ -38,7 +40,7 @@ class BaseSearchModule extends BaseModule $localSearch = $config->get('system', 'poco_local_search'); - $search = $prefix . Strings::escapeTags(trim(defaults($_REQUEST, 'search', ''))); + $search = $prefix . $search; if (!$search) { return ''; @@ -62,7 +64,9 @@ class BaseSearchModule extends BaseModule $header = L10n::t('Forum Search - %s', $search); } - $pager = new Pager($a->query_string); + /** @var Arguments $args */ + $args = self::getClass(Arguments::class); + $pager = new Pager($args->getQueryString()); if ($localSearch && empty($results)) { $pager->setItemsPerPage(80); diff --git a/src/Module/Search/Directory.php b/src/Module/Search/Directory.php index 20cdd5492..405fb0cc8 100644 --- a/src/Module/Search/Directory.php +++ b/src/Module/Search/Directory.php @@ -6,6 +6,7 @@ use Friendica\Content\Widget; use Friendica\Core\L10n; use Friendica\Module\BaseSearchModule; use Friendica\Module\Login; +use Friendica\Util\Strings; /** * Directory search module @@ -19,6 +20,8 @@ class Directory extends BaseSearchModule return Login::form(); } + $search = Strings::escapeTags(trim(rawurldecode($_REQUEST['search'] ?? ''))); + $a = self::getApp(); if (empty($a->page['aside'])) { @@ -28,6 +31,6 @@ class Directory extends BaseSearchModule $a->page['aside'] .= Widget::findPeople(); $a->page['aside'] .= Widget::follow(); - return self::performSearch(); + return self::performSearch($search); } } diff --git a/src/Module/Search/Index.php b/src/Module/Search/Index.php new file mode 100644 index 000000000..9c0d9e1df --- /dev/null +++ b/src/Module/Search/Index.php @@ -0,0 +1,200 @@ +httpdesc = L10n::t('Public access denied.'); + throw $e; + } + + if (Config::get('system', 'permit_crawling') && !Session::isAuthenticated()) { + // Default values: + // 10 requests are "free", after the 11th only a call per minute is allowed + + $free_crawls = intval(Config::get('system', 'free_crawls')); + if ($free_crawls == 0) + $free_crawls = 10; + + $crawl_permit_period = intval(Config::get('system', 'crawl_permit_period')); + if ($crawl_permit_period == 0) + $crawl_permit_period = 10; + + $remote = $_SERVER['REMOTE_ADDR']; + $result = Cache::get('remote_search:' . $remote); + if (!is_null($result)) { + $resultdata = json_decode($result); + if (($resultdata->time > (time() - $crawl_permit_period)) && ($resultdata->accesses > $free_crawls)) { + throw new HTTPException\TooManyRequestsException(L10n::t('Only one search per minute is permitted for not logged in users.')); + } + Cache::set('remote_search:' . $remote, json_encode(['time' => time(), 'accesses' => $resultdata->accesses + 1]), CacheClass::HOUR); + } else { + Cache::set('remote_search:' . $remote, json_encode(['time' => time(), 'accesses' => 1]), CacheClass::HOUR); + } + } + + if (local_user()) { + self::getApp()->page['aside'] .= Widget\SavedSearches::getHTML('search?q=' . $search, $search); + } + + Nav::setSelected('search'); + + $tag = false; + if (!empty($_GET['tag'])) { + $tag = true; + $search = '#' . Strings::escapeTags(trim(rawurldecode($_GET['tag']))); + } + + // contruct a wrapper for the search header + $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('content_wrapper.tpl'), [ + 'name' => 'search-header', + '$title' => L10n::t('Search'), + '$title_size' => 3, + '$content' => HTML::search($search, 'search-box', false) + ]); + + if (strpos($search, '#') === 0) { + $tag = true; + $search = substr($search, 1); + } + + if (strpos($search, '@') === 0 || strpos($search, '!') === 0) { + return self::performSearch($search); + } + + if (parse_url($search, PHP_URL_SCHEME) != '') { + $id = Item::fetchByLink($search); + if (!empty($id)) { + $item = Item::selectFirst(['guid'], ['id' => $id]); + if (DBA::isResult($item)) { + self::getApp()->internalRedirect('display/' . $item['guid']); + } + } + } + + if (!empty($_GET['search-option'])) { + switch ($_GET['search-option']) { + case 'fulltext': + break; + case 'tags': + $tag = true; + break; + case 'contacts': + return self::performSearch($search, '@'); + case 'forums': + return self::performSearch($search, '!'); + } + } + + if (!$search) { + return $o; + } + + $tag = $tag || Config::get('system', 'only_tag_search'); + + // Here is the way permissions work in the search module... + // Only public posts can be shown + // OR your own posts if you are a logged in member + // No items will be shown if the member has a blocked profile wall. + + /** @var Arguments $args */ + $args = self::getClass(Arguments::class); + $pager = new Pager($args->getQueryString()); + + if ($tag) { + Logger::info('Start tag search.', ['q' => $search]); + + $condition = [ + "(`uid` = 0 OR (`uid` = ? AND NOT `global`)) + AND `otype` = ? AND `type` = ? AND `term` = ?", + local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG, $search + ]; + $params = [ + 'order' => ['received' => true], + 'limit' => [$pager->getStart(), $pager->getItemsPerPage()] + ]; + $terms = DBA::select('term', ['oid'], $condition, $params); + + $itemids = []; + while ($term = DBA::fetch($terms)) { + $itemids[] = $term['oid']; + } + + DBA::close($terms); + + if (!empty($itemids)) { + $params = ['order' => ['id' => true]]; + $items = Item::selectForUser(local_user(), [], ['id' => $itemids], $params); + $r = Item::inArray($items); + } else { + $r = []; + } + } else { + Logger::info('Start fulltext search.', ['q' => $search]); + + $condition = [ + "(`uid` = 0 OR (`uid` = ? AND NOT `global`)) + AND `body` LIKE CONCAT('%',?,'%')", + local_user(), $search + ]; + $params = [ + 'order' => ['id' => true], + 'limit' => [$pager->getStart(), $pager->getItemsPerPage()] + ]; + $items = Item::selectForUser(local_user(), [], $condition, $params); + $r = Item::inArray($items); + } + + if (!DBA::isResult($r)) { + info(L10n::t('No results.')); + return $o; + } + + if ($tag) { + $title = L10n::t('Items tagged with: %s', $search); + } else { + $title = L10n::t('Results for: %s', $search); + } + + $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [ + '$title' => $title + ]); + + Logger::info('Start Conversation.', ['q' => $search]); + + $o .= conversation(self::getApp(), $r, $pager, 'search', false, false, 'commented', local_user()); + + $o .= $pager->renderMinimal(count($r)); + + return $o; + } +} diff --git a/src/Module/Search/Saved.php b/src/Module/Search/Saved.php new file mode 100644 index 000000000..772f4782c --- /dev/null +++ b/src/Module/Search/Saved.php @@ -0,0 +1,44 @@ +get(2, 'none'); + $search = Strings::escapeTags(trim(rawurldecode($args->get(3, '')))); + + $return_url = $_GET['return_url'] ?? 'search?q=' . urlencode($search); + + if (local_user()) { + switch ($action) { + case 'add': + $fields = ['uid' => local_user(), 'term' => $search]; + if (!DBA::exists('search', $fields)) { + DBA::insert('search', $fields); + info(L10n::t('Search term successfully saved.')); + } else { + info(L10n::t('Search term already saved.')); + } + break; + + case 'remove': + DBA::delete('search', ['uid' => local_user(), 'term' => $search]); + info(L10n::t('Search term successfully removed.')); + break; + } + } + + self::getApp()->internalRedirect($return_url); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 73ac54991..f688a1a86 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -189,7 +189,10 @@ return [ ], '/search' => [ - '/acl' => [Module\Search\Acl::class, [R::GET, R::POST]], + '[/]' => [Module\Search\Index::class, [R::GET]], + '/acl' => [Module\Search\Acl::class, [R::GET, R::POST]], + '/saved/add/{term}' => [Module\Search\Saved::class, [R::GET]], + '/saved/remove/{term}' => [Module\Search\Saved::class, [R::GET]], ], '/settings' => [ diff --git a/view/templates/saved_searches_aside.tpl b/view/templates/saved_searches_aside.tpl deleted file mode 100644 index caf60cc0b..000000000 --- a/view/templates/saved_searches_aside.tpl +++ /dev/null @@ -1,15 +0,0 @@ - -
diff --git a/view/templates/searchbox.tpl b/view/templates/searchbox.tpl index 4b1a51f87..d566befba 100644 --- a/view/templates/searchbox.tpl +++ b/view/templates/searchbox.tpl @@ -1,20 +1,18 @@