--- /dev/null
+<?php
+
+namespace Friendica\Core;
+
+use Friendica\BaseObject;
+use Friendica\Database\DBA;
+use Friendica\Model\Contact;
+use Friendica\Network\Probe;
+use Friendica\Network\HTTPException;
+use Friendica\Object\Search\ContactResult;
+use Friendica\Object\Search\ResultList;
+use Friendica\Protocol\PortableContact;
+use Friendica\Util\Network;
+use Friendica\Util\Strings;
+
+class Search extends BaseObject
+{
+ const DEFAULT_DIRECTORY = 'https://dir.friendica.social';
+
+ /**
+ * Search a user based on his/her profile address
+ * pattern: @username@domain.tld
+ *
+ * @param string $user The user to search for
+ *
+ * @return ResultList|null
+ * @throws HTTPException\InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public static function getContactsFromProbe($user)
+ {
+ if ((filter_var($user, FILTER_VALIDATE_EMAIL) && Network::isEmailDomainValid($user)) ||
+ (substr(Strings::normaliseLink($user), 0, 7) == "http://")) {
+
+ $user_data = Probe::uri($user);
+ if (empty($user_data)) {
+ return null;
+ }
+
+ if (!(in_array($user_data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]))) {
+ return null;
+ }
+
+ $contactDetails = Contact::getDetailsByURL(defaults($user_data, 'url', ''), local_user());
+ $itemUrl = (($contactDetails["addr"] != "") ? $contactDetails["addr"] : defaults($user_data, 'url', ''));
+
+ $result = new ContactResult(
+ defaults($user_data, 'name', ''),
+ defaults($user_data, 'addr', ''),
+ $itemUrl,
+ defaults($user_data, 'url', ''),
+ defaults($user_data, 'photo', ''),
+ defaults($user_data, 'network', ''),
+ defaults($contactDetails, 'cid', 0),
+ 0,
+ defaults($user_data, 'tags', '')
+ );
+
+ return new ResultList(1, 1, 1, [$result]);
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Search in the global directory for occurrences of the search string
+ * This is mainly based on the JSON results of https://dir.friendica.social
+ *
+ * @param string $search
+ * @param int $page
+ *
+ * @return ResultList|null
+ * @throws HTTPException\InternalServerErrorException
+ */
+ public static function getContactsFromGlobalDirectory($search, $page = 1)
+ {
+ $config = self::getApp()->getConfig();
+ $server = $config->get('system', 'directory', self::DEFAULT_DIRECTORY);
+
+ $searchUrl = $server . '/search?q=' . urlencode($search);
+
+ if ($page > 1) {
+ $searchUrl .= '&page=' . $page;
+ }
+
+ $red = 0;
+ $resultJson = Network::fetchUrl($searchUrl, false,$red, 0, 'application/json');
+
+ $results = json_decode($resultJson, true);
+
+ $resultList = new ResultList(
+ defaults($results, 'page', 1),
+ defaults($results, 'count', 1),
+ defaults($results, 'itemsperpage', 1)
+ );
+
+ $profiles = defaults($results, 'profiles', []);
+
+ foreach ($profiles as $profile) {
+ $contactDetails = Contact::getDetailsByURL(defaults($profile, 'profile_url', ''), local_user());
+ $itemUrl = (!empty($contactDetails['addr']) ? $contactDetails['addr'] : defaults($profile, 'profile_url', ''));
+
+ $result = new ContactResult(
+ defaults($profile, 'name', ''),
+ defaults($profile, 'addr', ''),
+ $itemUrl,
+ defaults($profile, 'profile_url', ''),
+ defaults($profile, 'photo', ''),
+ Protocol::DFRN,
+ defaults($contactDetails, 'cid', 0),
+ 0,
+ defaults($profile, 'tags', ''));
+
+ $resultList->addResult($result);
+ }
+
+ return $resultList;
+ }
+
+ /**
+ * Search in the local database for occurrences of the search string
+ *
+ * @param string $search
+ * @param int $start
+ * @param int $itemPage
+ * @param bool $community
+ *
+ * @return ResultList|null
+ * @throws HTTPException\InternalServerErrorException
+ */
+ public static function getContactsFromLocalDirectory($search, $start = 0, $itemPage = 80, $community = false)
+ {
+ $config = self::getApp()->getConfig();
+
+ $diaspora = $config->get('system', 'diaspora_enabled') ? Protocol::DIASPORA : Protocol::DFRN;
+ $ostatus = !$config->get('system', 'ostatus_disabled') ? Protocol::OSTATUS : Protocol::DFRN;
+
+ $wildcard = Strings::escapeHtml('%' . $search . '%');
+
+ $count = DBA::count('gcontact', [
+ 'NOT `hide`
+ AND `network` IN (?, ?, ?, ?)
+ AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`))
+ AND (`url` LIKE ? OR `name` LIKE ? OR `location` LIKE ?
+ OR `addr` LIKE ? OR `about` LIKE ? OR `keywords` LIKE ?)
+ AND `community` = ?',
+ Protocol::ACTIVITYPUB, Protocol::DFRN, $ostatus, $diaspora,
+ $wildcard, $wildcard, $wildcard,
+ $wildcard, $wildcard, $wildcard,
+ $community,
+ ]);
+
+ if (empty($count)) {
+ return null;
+ }
+
+ $data = DBA::select('gcontact', ['nurl'], [
+ 'NOT `hide`
+ AND `network` IN (?, ?, ?, ?)
+ AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`))
+ AND (`url` LIKE ? OR `name` LIKE ? OR `location` LIKE ?
+ OR `addr` LIKE ? OR `about` LIKE ? OR `keywords` LIKE ?)
+ AND `community` = ?',
+ Protocol::ACTIVITYPUB, Protocol::DFRN, $ostatus, $diaspora,
+ $wildcard, $wildcard, $wildcard,
+ $wildcard, $wildcard, $wildcard,
+ $community,
+ ], [
+ 'group_by' => ['nurl', 'updated'],
+ 'limit' => [$start, $itemPage],
+ 'order' => ['updated' => 'DESC']
+ ]);
+
+ if (!DBA::isResult($data)) {
+ return null;
+ }
+
+ $resultList = new ResultList($start, $itemPage, $count);
+
+ while ($row = DBA::fetch($data)) {
+ if (PortableContact::alternateOStatusUrl($row["nurl"])) {
+ continue;
+ }
+
+ $urlParts = parse_url($row["nurl"]);
+
+ // Ignore results that look strange.
+ // For historic reasons the gcontact table does contain some garbage.
+ if (!empty($urlParts['query']) || !empty($urlParts['fragment'])) {
+ continue;
+ }
+
+ $contact = Contact::getDetailsByURL($row["nurl"], local_user());
+
+ if ($contact["name"] == "") {
+ $contact["name"] = end(explode("/", $urlParts["path"]));
+ }
+
+ $result = new ContactResult(
+ $contact["name"],
+ $contact["addr"],
+ $contact["addr"],
+ $contact["url"],
+ $contact["photo"],
+ $contact["network"],
+ $contact["cid"],
+ $contact["zid"],
+ $contact["keywords"]
+ );
+
+ $resultList->addResult($result);
+ }
+
+ DBA::close($data);
+
+ // Add found profiles from the global directory to the local directory
+ Worker::add(PRIORITY_LOW, 'DiscoverPoCo', "dirsearch", urlencode($search));
+
+ return $resultList;
+ }
+}
namespace Friendica\Model;
use Friendica\BaseObject;
-use Friendica\Core\Protocol;
-use Friendica\Core\Worker;
use Friendica\Database\DBA;
-use Friendica\Network\Probe;
-use Friendica\Object\Search\ContactResult;
-use Friendica\Object\Search\ContactResultList;
-use Friendica\Protocol\PortableContact;
-use Friendica\Util\Network;
-use Friendica\Util\Strings;
/**
- * Model for searches
+ * Model for DB specific logic for the search entity
*/
class Search extends BaseObject
{
- const DEFAULT_DIRECTORY = 'https://dir.friendica.social';
-
/**
* Returns the list of user defined tags (e.g. #Friendica)
*
return $tags;
}
-
- /**
- * Search a user based on his/her profile address
- * pattern: @username@domain.tld
- *
- * @param string $user The user to search for
- *
- * @return ContactResultList|null
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function searchUser($user)
- {
- if ((filter_var($user, FILTER_VALIDATE_EMAIL) && Network::isEmailDomainValid($user)) ||
- (substr(Strings::normaliseLink($user), 0, 7) == "http://")) {
-
- $user_data = Probe::uri($user);
- if (empty($user_data)) {
- return null;
- }
-
- if (!(in_array($user_data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]))) {
- return null;
- }
-
- $contactDetails = Contact::getDetailsByURL(defaults($user_data, 'url', ''), local_user());
- $itemurl = (($contactDetails["addr"] != "") ? $contactDetails["addr"] : defaults($user_data, 'url', ''));
-
- $result = new ContactResult(
- defaults($user_data, 'name', ''),
- defaults($user_data, 'addr', ''),
- $itemurl,
- defaults($user_data, 'url', ''),
- defaults($user_data, 'photo', ''),
- defaults($user_data, 'network', ''),
- defaults($contactDetails, 'cid', 0),
- 0,
- defaults($user_data, 'tags', '')
- );
-
- return new ContactResultList(1, 1, 1, [$result]);
-
- } else {
- return null;
- }
- }
-
- /**
- * Search in the global directory for occurrences of the search string
- * This is mainly based on the JSON results of https://dir.friendica.social
- *
- * @param string $search
- * @param int $page
- *
- * @return ContactResultList|null
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
- public static function searchDirectory($search, $page = 1)
- {
- $config = self::getApp()->getConfig();
- $server = $config->get('system', 'directory', self::DEFAULT_DIRECTORY);
-
- $searchUrl = $server . '/search?q=' . urlencode($search);
-
- if ($page > 1) {
- $searchUrl .= '&page=' . $page;
- }
-
- $red = 0;
- $resultJson = Network::fetchUrl($searchUrl, false,$red, 0, 'application/json');
-
- $results = json_decode($resultJson, true);
-
- $resultList = new ContactResultList(
- defaults($results, 'page', 1),
- defaults($results, 'count', 1),
- defaults($results, 'itemsperpage', 1)
- );
-
- $profiles = defaults($results, 'profiles', []);
-
- foreach ($profiles as $profile) {
- $contactDetails = Contact::getDetailsByURL(defaults($profile, 'profile_url', ''), local_user());
- $itemurl = (!empty($contactDetails['addr']) ? $contactDetails['addr'] : defaults($profile, 'profile_url', ''));
-
- $result = new ContactResult(
- defaults($profile, 'name', ''),
- defaults($profile, 'addr', ''),
- $itemurl,
- defaults($profile, 'profile_url', ''),
- defaults($profile, 'photo', ''),
- Protocol::DFRN,
- defaults($contactDetails, 'cid', 0),
- 0,
- defaults($profile, 'tags', ''));
-
- $resultList->addResult($result);
- }
-
- return $resultList;
- }
-
- /**
- * Search in the local database for occurrences of the search string
- *
- * @param string $search
- * @param int $start
- * @param int $itemPage
- * @param bool $community
- *
- * @return ContactResultList|null
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
- public static function searchLocal($search, $start = 0, $itemPage = 80, $community = false)
- {
- $config = self::getApp()->getConfig();
-
- $diaspora = $config->get('system', 'diaspora_enabled') ? Protocol::DIASPORA : Protocol::DFRN;
- $ostatus = !$config->get('system', 'ostatus_disabled') ? Protocol::OSTATUS : Protocol::DFRN;
-
- $wildcard = Strings::escapeHtml('%' . $search . '%');
-
- $count = DBA::count('gcontact', [
- 'NOT `hide`
- AND `network` IN (?, ?, ?, ?)
- AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`))
- AND (`url` LIKE ? OR `name` LIKE ? OR `location` LIKE ?
- OR `addr` LIKE ? OR `about` LIKE ? OR `keywords` LIKE ?)
- AND `community` = ?',
- Protocol::ACTIVITYPUB, Protocol::DFRN, $ostatus, $diaspora,
- $wildcard, $wildcard, $wildcard,
- $wildcard, $wildcard, $wildcard,
- $community,
- ]);
-
- if (empty($count)) {
- return null;
- }
-
- $data = DBA::select('gcontact', ['nurl'], [
- 'NOT `hide`
- AND `network` IN (?, ?, ?, ?)
- AND ((`last_contact` >= `last_failure`) OR (`updated` >= `last_failure`))
- AND (`url` LIKE ? OR `name` LIKE ? OR `location` LIKE ?
- OR `addr` LIKE ? OR `about` LIKE ? OR `keywords` LIKE ?)
- AND `community` = ?',
- Protocol::ACTIVITYPUB, Protocol::DFRN, $ostatus, $diaspora,
- $wildcard, $wildcard, $wildcard,
- $wildcard, $wildcard, $wildcard,
- $community,
- ], [
- 'group_by' => ['nurl', 'updated'],
- 'limit' => [$start, $itemPage],
- 'order' => ['updated' => 'DESC']
- ]);
-
- if (!DBA::isResult($data)) {
- return null;
- }
-
- $resultList = new ContactResultList($start, $itemPage, $count);
-
- while ($row = DBA::fetch($data)) {
- if (PortableContact::alternateOStatusUrl($row["nurl"])) {
- continue;
- }
-
- $urlparts = parse_url($row["nurl"]);
-
- // Ignore results that look strange.
- // For historic reasons the gcontact table does contain some garbage.
- if (!empty($urlparts['query']) || !empty($urlparts['fragment'])) {
- continue;
- }
-
- $contact = Contact::getDetailsByURL($row["nurl"], local_user());
-
- if ($contact["name"] == "") {
- $contact["name"] = end(explode("/", $urlparts["path"]));
- }
-
- $result = new ContactResult(
- $contact["name"],
- $contact["addr"],
- $contact["addr"],
- $contact["url"],
- $contact["photo"],
- $contact["network"],
- $contact["cid"],
- $contact["zid"],
- $contact["keywords"]
- );
-
- $resultList->addResult($result);
- }
-
- DBA::close($data);
-
- // Add found profiles from the global directory to the local directory
- Worker::add(PRIORITY_LOW, 'DiscoverPoCo', "dirsearch", urlencode($search));
-
- return $resultList;
- }
}
use Friendica\Content\Pager;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
-use Friendica\Object\Search\ContactResultList;
+use Friendica\Core\Search;
+use Friendica\Object\Search\ContactResult;
+use Friendica\Object\Search\ResultList;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Model;
+use Friendica\Network\HTTPException;
use Friendica\Util\Strings;
/**
* @param string $prefix A optional prefix (e.g. @ or !) for searching
*
* @return string
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function performSearch($prefix = '')
if (strpos($search, '@') === 0) {
$search = substr($search, 1);
$header = L10n::t('People Search - %s', $search);
- $results = Model\Search::searchUser($search);
+ $results = Search::getContactsFromProbe($search);
}
if (strpos($search, '!') === 0) {
if ($localSearch && empty($results)) {
$pager->setItemsPerPage(80);
- $results = Model\Search::searchLocal($search, $pager->getStart(), $pager->getItemsPerPage(), $community);
+ $results = Search::getContactsFromLocalDirectory($search, $pager->getStart(), $pager->getItemsPerPage(), $community);
} elseif (strlen($config->get('system', 'directory')) && empty($results)) {
- $results = Model\Search::searchDirectory($search, $pager->getPage());
+ $results = Search::getContactsFromGlobalDirectory($search, $pager->getPage());
$pager->setItemsPerPage($results->getItemsPage());
}
/**
* Prints a human readable search result
*
- * @param ContactResultList $results
+ * @param ResultList $results
* @param Pager $pager
* @param string $header
*
* @return string The result
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
- protected static function printResult(ContactResultList $results, Pager $pager, $header = '')
+ protected static function printResult(ResultList $results, Pager $pager, $header = '')
{
if (empty($results) || empty($results->getResults())) {
info(L10n::t('No matches') . EOL);
$entries = [];
foreach ($results->getResults() as $result) {
- $alt_text = '';
- $location = '';
- $about = '';
- $accountType = '';
- $photo_menu = [];
-
- // If We already know this contact then don't show the "connect" button
- if ($result->getCid() > 0 || $result->getPCid() > 0) {
- $connLink = "";
- $connTxt = "";
- $contact = Model\Contact::getById(
- ($result->getCid() > 0) ? $result->getCid() : $result->getPCid()
- );
-
- if (!empty($contact)) {
- $photo_menu = Model\Contact::photoMenu($contact);
- $details = Contact::getContactTemplateVars($contact);
- $alt_text = $details['alt_text'];
- $location = $contact['location'];
- $about = $contact['about'];
- $accountType = Model\Contact::getAccountType($contact);
+ // in case the result is a contact result, add a contact-specific entry
+ if ($result instanceof ContactResult) {
+
+ $alt_text = '';
+ $location = '';
+ $about = '';
+ $accountType = '';
+ $photo_menu = [];
+
+ // If We already know this contact then don't show the "connect" button
+ if ($result->getCid() > 0 || $result->getPCid() > 0) {
+ $connLink = "";
+ $connTxt = "";
+ $contact = Model\Contact::getById(
+ ($result->getCid() > 0) ? $result->getCid() : $result->getPCid()
+ );
+
+ if (!empty($contact)) {
+ $photo_menu = Model\Contact::photoMenu($contact);
+ $details = Contact::getContactTemplateVars($contact);
+ $alt_text = $details['alt_text'];
+ $location = $contact['location'];
+ $about = $contact['about'];
+ $accountType = Model\Contact::getAccountType($contact);
+ } else {
+ $photo_menu = [];
+ }
} else {
- $photo_menu = [];
+ $connLink = $a->getBaseURL() . '/follow/?url=' . $result->getUrl();
+ $connTxt = L10n::t('Connect');
+
+ $photo_menu['profile'] = [L10n::t("View Profile"), Model\Contact::magicLink($result->getUrl())];
+ $photo_menu['follow'] = [L10n::t("Connect/Follow"), $connLink];
}
- } else {
- $connLink = $a->getBaseURL() . '/follow/?url=' . $result->getUrl();
- $connTxt = L10n::t('Connect');
- $photo_menu['profile'] = [L10n::t("View Profile"), Model\Contact::magicLink($result->getUrl())];
- $photo_menu['follow'] = [L10n::t("Connect/Follow"), $connLink];
+ $photo = str_replace("http:///photo/", get_server() . "/photo/", $result->getPhoto());
+
+ $entry = [
+ 'alt_text' => $alt_text,
+ 'url' => Model\Contact::magicLink($result->getUrl()),
+ 'itemurl' => $result->getItem(),
+ 'name' => $result->getName(),
+ 'thumb' => ProxyUtils::proxifyUrl($photo, false, ProxyUtils::SIZE_THUMB),
+ 'img_hover' => $result->getTags(),
+ 'conntxt' => $connTxt,
+ 'connlnk' => $connLink,
+ 'photo_menu' => $photo_menu,
+ 'details' => $location,
+ 'tags' => $result->getTags(),
+ 'about' => $about,
+ 'account_type' => $accountType,
+ 'network' => ContactSelector::networkToName($result->getNetwork(), $result->getUrl()),
+ 'id' => ++$id,
+ ];
+ $entries[] = $entry;
}
-
- $photo = str_replace("http:///photo/", get_server() . "/photo/", $result->getPhoto());
-
- $entry = [
- 'alt_text' => $alt_text,
- 'url' => Model\Contact::magicLink($result->getUrl()),
- 'itemurl' => $result->getItem(),
- 'name' => $result->getName(),
- 'thumb' => ProxyUtils::proxifyUrl($photo, false, ProxyUtils::SIZE_THUMB),
- 'img_hover' => $result->getTags(),
- 'conntxt' => $connTxt,
- 'connlnk' => $connLink,
- 'photo_menu' => $photo_menu,
- 'details' => $location,
- 'tags' => $result->getTags(),
- 'about' => $about,
- 'account_type' => $accountType,
- 'network' => ContactSelector::networkToName($result->getNetwork(), $result->getUrl()),
- 'id' => ++$id,
- ];
- $entries[] = $entry;
}
$tpl = Renderer::getMarkupTemplate('viewcontact_template.tpl');
*
* @see Search for details
*/
-class ContactResult
+class ContactResult implements IResult
{
/**
* @var int
/**
* @param string $name
* @param string $addr
+ * @param string $item
* @param string $url
* @param string $photo
* @param string $network
* @param int $cid
- * @param int $pcid
+ * @param int $pCid
* @param string $tags
*/
- public function __construct($name, $addr, $item, $url, $photo, $network, $cid = 0, $pcid = 0, $tags = '')
+ public function __construct($name, $addr, $item, $url, $photo, $network, $cid = 0, $pCid = 0, $tags = '')
{
$this->name = $name;
$this->addr = $addr;
$this->network = $network;
$this->cid = $cid;
- $this->pCid = $pcid;
+ $this->pCid = $pCid;
$this->tags = $tags;
}
}
+++ /dev/null
-<?php
-
-namespace Friendica\Object\Search;
-
-use Friendica\Model\Search;
-
-/**
- * A list of search results for contacts with metadata
- *
- * @see Search for details
- */
-class ContactResultList
-{
- /**
- * Page of the result list
- * @var int
- */
- private $page;
- /**
- * Total count of results
- * @var int
- */
- private $total;
- /**
- * items per page
- * @var int
- */
- private $itemsPage;
- /**
- * Array of results
- *
- * @var ContactResult[]
- */
- private $results;
-
- /**
- * @return int
- */
- public function getPage()
- {
- return $this->page;
- }
-
- /**
- * @return int
- */
- public function getTotal()
- {
- return $this->total;
- }
-
- /**
- * @return int
- */
- public function getItemsPage()
- {
- return $this->itemsPage;
- }
-
- /**
- * @return ContactResult[]
- */
- public function getResults()
- {
- return $this->results;
- }
-
- /**
- * @param int $page
- * @param int $total
- * @param int $itemsPage
- * @param ContactResult[] $results
- */
- public function __construct($page = 0, $total = 0, $itemsPage = 0, array $results = [])
- {
- $this->page = $page;
- $this->total = $total;
- $this->itemsPage = $itemsPage;
-
- $this->results = $results;
- }
-
- /**
- * Adds a result to the result list
- *
- * @param ContactResult $result
- */
- public function addResult(ContactResult $result)
- {
- $this->results[] = $result;
- }
-}
--- /dev/null
+<?php
+
+
+namespace Friendica\Object\Search;
+
+
+interface IResult
+{
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+namespace Friendica\Object\Search;
+
+use Friendica\Model\Search;
+
+/**
+ * A list of search results with metadata
+ *
+ * @see Search for details
+ */
+class ResultList
+{
+ /**
+ * Page of the result list
+ * @var int
+ */
+ private $page;
+ /**
+ * Total count of results
+ * @var int
+ */
+ private $total;
+ /**
+ * items per page
+ * @var int
+ */
+ private $itemsPage;
+ /**
+ * Array of results
+ *
+ * @var IResult[]
+ */
+ private $results;
+
+ /**
+ * @return int
+ */
+ public function getPage()
+ {
+ return $this->page;
+ }
+
+ /**
+ * @return int
+ */
+ public function getTotal()
+ {
+ return $this->total;
+ }
+
+ /**
+ * @return int
+ */
+ public function getItemsPage()
+ {
+ return $this->itemsPage;
+ }
+
+ /**
+ * @return IResult[]
+ */
+ public function getResults()
+ {
+ return $this->results;
+ }
+
+ /**
+ * @param int $page
+ * @param int $total
+ * @param int $itemsPage
+ * @param IResult[] $results
+ */
+ public function __construct($page = 0, $total = 0, $itemsPage = 0, array $results = [])
+ {
+ $this->page = $page;
+ $this->total = $total;
+ $this->itemsPage = $itemsPage;
+
+ $this->results = $results;
+ }
+
+ /**
+ * Adds a result to the result list
+ *
+ * @param IResult $result
+ */
+ public function addResult(IResult $result)
+ {
+ $this->results[] = $result;
+ }
+}