First commit
This commit is contained in:
commit
201edf2e4a
115 changed files with 29451 additions and 0 deletions
141
src/classes/Content/L10n.php
Normal file
141
src/classes/Content/L10n.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Content;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class L10n
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $lang;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $strings;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $lang_path;
|
||||
|
||||
public function __construct(string $language = 'en', string $lang_path = '')
|
||||
{
|
||||
$this->lang = $language;
|
||||
$this->lang_path = $lang_path;
|
||||
|
||||
$this->loadTranslationTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads string translation table
|
||||
*
|
||||
* First addon strings are loaded, then globals
|
||||
*
|
||||
* Uses an App object shim since all the strings files refer to $a->strings
|
||||
*
|
||||
* @param string $lang language code to load
|
||||
*/
|
||||
private function loadTranslationTable(): void
|
||||
{
|
||||
if (file_exists($this->lang_path . '/' . $this->lang . '/strings.php')) {
|
||||
$this->strings = include $this->lang_path . '/' . $this->lang . '/strings.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the localized version of a singular/plural string with optional string interpolation
|
||||
*
|
||||
* This function takes two english strings as parameters, singular and plural, as
|
||||
* well as a count. If a localized version exists for the current language, they
|
||||
* are used instead. Discrimination between singular and plural is done using the
|
||||
* localized function if any or the default one. Finally, a string interpolation
|
||||
* is performed using the count as parameter.
|
||||
*
|
||||
* Usages:
|
||||
* - L10n::tt('Like', 'Likes', $count)
|
||||
* - L10n::tt("%s user deleted", "%s users deleted", count($users))
|
||||
*
|
||||
* @param string $singular
|
||||
* @param string $plural
|
||||
* @param int $count
|
||||
* @return string
|
||||
*/
|
||||
public function tt(string $singular, string $plural, int $count): string
|
||||
{
|
||||
if (!empty($this->strings[$singular])) {
|
||||
$t = $this->strings[$singular];
|
||||
if (is_array($t)) {
|
||||
$plural_function = 'string_plural_select_' . str_replace('-', '_', $this->lang);
|
||||
if (function_exists($plural_function)) {
|
||||
$i = $plural_function($count);
|
||||
} else {
|
||||
$i = $this->stringPluralSelectDefault($count);
|
||||
}
|
||||
|
||||
// for some languages there is only a single array item
|
||||
if (!isset($t[$i])) {
|
||||
$s = $t[0];
|
||||
} else {
|
||||
$s = $t[$i];
|
||||
}
|
||||
} else {
|
||||
$s = $t;
|
||||
}
|
||||
} elseif ($this->stringPluralSelectDefault($count)) {
|
||||
$s = $plural;
|
||||
} else {
|
||||
$s = $singular;
|
||||
}
|
||||
|
||||
$s = @sprintf($s, $count);
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the localized version of the provided string with optional string interpolation
|
||||
*
|
||||
* This function takes a english string as parameter, and if a localized version
|
||||
* exists for the current language, substitutes it before performing an eventual
|
||||
* string interpolation (sprintf) with additional optional arguments.
|
||||
*
|
||||
* Usages:
|
||||
* - L10n::t('This is an example')
|
||||
* - L10n::t('URL %s returned no result', $url)
|
||||
* - L10n::t('Current version: %s, new version: %s', $current_version, $new_version)
|
||||
*
|
||||
* @param string $s
|
||||
* @param array $vars Variables to interpolate in the translation string
|
||||
* @return string
|
||||
*/
|
||||
public function t($s, ...$vars): string
|
||||
{
|
||||
if (empty($s)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!empty($this->strings[$s])) {
|
||||
$t = $this->strings[$s];
|
||||
$s = is_array($t) ? $t[0] : $t;
|
||||
}
|
||||
|
||||
if (count($vars) > 0) {
|
||||
$s = sprintf($s, ...$vars);
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a fallback which will not collide with a function defined in any language file
|
||||
*/
|
||||
private function stringPluralSelectDefault(int $n): bool
|
||||
{
|
||||
return $n != 1;
|
||||
}
|
||||
|
||||
}
|
||||
285
src/classes/Content/Pager.php
Normal file
285
src/classes/Content/Pager.php
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Content;
|
||||
|
||||
/**
|
||||
* The Pager has two very different output, Minimal and Full, see renderMinimal() and renderFull() for more details.
|
||||
*
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Pager
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $page = 1;
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $itemsPerPage = 50;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $baseQueryString = '';
|
||||
|
||||
/**
|
||||
* @var \Friendica\Directory\Content\L10n
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
/**
|
||||
* Instantiates a new Pager with the base parameters.
|
||||
*
|
||||
* Guesses the page number from the GET parameter 'page'.
|
||||
*
|
||||
* @param \Friendica\Directory\Content\L10n $l10n
|
||||
* @param \Psr\Http\Message\ServerRequestInterface $request
|
||||
* @param integer $itemsPerPage An optional number of items per page to override the default value
|
||||
*/
|
||||
public function __construct(L10n $l10n, \Psr\Http\Message\ServerRequestInterface $request, int $itemsPerPage = 50)
|
||||
{
|
||||
$this->l10n = $l10n;
|
||||
$this->setQueryString($request);
|
||||
$this->setItemsPerPage($itemsPerPage);
|
||||
$this->setPage(filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset for a LIMIT clause. Starts at 0.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getStart()
|
||||
{
|
||||
return max(0, ($this->page * $this->itemsPerPage) - $this->itemsPerPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of items per page
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getItemsPerPage()
|
||||
{
|
||||
return $this->itemsPerPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current page number
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base query string.
|
||||
*
|
||||
* Warning: this isn't the same value as passed to the constructor.
|
||||
* See setQueryString() for the inventory of transformations
|
||||
*
|
||||
* @see setBaseQuery()
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseQueryString(): string
|
||||
{
|
||||
return $this->baseQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of items per page, 1 minimum.
|
||||
*
|
||||
* @param integer $itemsPerPage
|
||||
*/
|
||||
public function setItemsPerPage($itemsPerPage): void
|
||||
{
|
||||
$this->itemsPerPage = max(1, intval($itemsPerPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current page number. Starts at 1.
|
||||
*
|
||||
* @param integer $page
|
||||
*/
|
||||
public function setPage($page): void
|
||||
{
|
||||
$this->page = max(1, intval($page));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base query string from a full query string.
|
||||
*
|
||||
* Strips the 'page' parameter, and remove the 'q=' string for some reason.
|
||||
*
|
||||
* @param \Psr\Http\Message\ServerRequestInterface $request
|
||||
*/
|
||||
public function setQueryString(\Psr\Http\Message\ServerRequestInterface $request): void
|
||||
{
|
||||
$queryParams = $request->getQueryParams();
|
||||
unset($queryParams['page']);
|
||||
|
||||
$this->baseQueryString = $request->getUri()->getPath() . ($queryParams ? '?' . http_build_query($queryParams) : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the provided URI has its query string punctuation in order.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return string
|
||||
*/
|
||||
private function ensureQueryParameter($uri)
|
||||
{
|
||||
if (strpos($uri, '?') === false && ($pos = strpos($uri, '&')) !== false) {
|
||||
$uri = substr($uri, 0, $pos) . '?' . substr($uri, $pos + 1);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Minimal pager (newer/older)
|
||||
*
|
||||
* This mode is intended for reverse chronological pages and presents only two links, newer (previous) and older (next).
|
||||
* The itemCount is the number of displayed items. If no items are displayed, the older button is disabled.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $pager = new Pager($a->query_string);
|
||||
*
|
||||
* $params = ['order' => ['sort_field' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
|
||||
* $items = DBA::toArray(DBA::select($table, $fields, $condition, $params));
|
||||
*
|
||||
* $html = $pager->renderMinimal(count($items));
|
||||
*
|
||||
* @param integer $itemCount The number of displayed items on the page
|
||||
* @return array of links
|
||||
*/
|
||||
public function renderMinimal(int $itemCount, string $previous_label = 'Previous', string $next_label = 'Next')
|
||||
{
|
||||
$displayedItemCount = max(0, $itemCount);
|
||||
|
||||
$data = [
|
||||
'class' => 'pager',
|
||||
'prev' => [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)),
|
||||
'text' => $this->l10n->t($previous_label),
|
||||
'class' => 'previous' . ($this->getPage() == 1 ? ' disabled' : '')
|
||||
],
|
||||
'next' => [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),
|
||||
'text' => $this->l10n->t($next_label),
|
||||
'class' => 'next' . ($displayedItemCount <= 0 ? ' disabled' : '')
|
||||
]
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Full pager (first / prev / 1 / 2 / ... / 14 / 15 / next / last)
|
||||
*
|
||||
* This mode presents page numbers as well as first, previous, next and last links.
|
||||
* The itemCount is the total number of items including those not displayed.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $total = DBA::count($table, $condition);
|
||||
*
|
||||
* $pager = new Pager($a->query_string, $total);
|
||||
*
|
||||
* $params = ['limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
|
||||
* $items = DBA::toArray(DBA::select($table, $fields, $condition, $params));
|
||||
*
|
||||
* $html = $pager->renderFull();
|
||||
*
|
||||
* @param integer $itemCount The total number of items including those note displayed on the page
|
||||
* @return array of links
|
||||
*/
|
||||
public function renderFull($itemCount)
|
||||
{
|
||||
$totalItemCount = max(0, intval($itemCount));
|
||||
|
||||
$data = [];
|
||||
|
||||
$data['class'] = 'pagination';
|
||||
if ($totalItemCount > $this->getItemsPerPage()) {
|
||||
$data['first'] = [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=1'),
|
||||
'text' => $this->l10n->t('First'),
|
||||
'class' => $this->getPage() == 1 ? 'disabled' : ''
|
||||
];
|
||||
$data['prev'] = [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)),
|
||||
'text' => $this->l10n->t('Previous'),
|
||||
'class' => $this->getPage() == 1 ? 'disabled' : ''
|
||||
];
|
||||
|
||||
$numpages = $totalItemCount / $this->getItemsPerPage();
|
||||
|
||||
$numstart = 1;
|
||||
$numstop = $numpages;
|
||||
|
||||
$numpages_limit = 6;
|
||||
|
||||
// Limit the number of displayed page number buttons.
|
||||
if ($numpages > $numpages_limit) {
|
||||
$numstart = (($this->getPage() > $numpages_limit / 2) ? ($this->getPage() - $numpages_limit / 2) : 1);
|
||||
$numstop = (($this->getPage() > ($numpages - $numpages_limit)) ? $numpages : ($numstart + $numpages_limit - 1));
|
||||
}
|
||||
|
||||
$pages = [];
|
||||
|
||||
for ($i = $numstart; $i <= $numstop; $i++) {
|
||||
if ($i == $this->getPage()) {
|
||||
$pages[$i] = [
|
||||
'url' => '#',
|
||||
'text' => $i,
|
||||
'class' => 'current active'
|
||||
];
|
||||
} else {
|
||||
$pages[$i] = [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $i),
|
||||
'text' => $i,
|
||||
'class' => 'n'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (($totalItemCount % $this->getItemsPerPage()) != 0) {
|
||||
if ($i == $this->getPage()) {
|
||||
$pages[$i] = [
|
||||
'url' => '#',
|
||||
'text' => $i,
|
||||
'class' => 'current active'
|
||||
];
|
||||
} else {
|
||||
$pages[$i] = [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $i),
|
||||
'text' => $i,
|
||||
'class' => 'n'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$data['pages'] = $pages;
|
||||
|
||||
$lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages);
|
||||
|
||||
$data['next'] = [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),
|
||||
'text' => $this->l10n->t('Next'),
|
||||
'class' => $this->getPage() == $lastpage ? 'disabled' : ''
|
||||
];
|
||||
$data['last'] = [
|
||||
'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $lastpage),
|
||||
'text' => $this->l10n->t('Last'),
|
||||
'class' => $this->getPage() == $lastpage ? 'disabled' : ''
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
79
src/classes/Controllers/Api/Search.php
Normal file
79
src/classes/Controllers/Api/Search.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Api;
|
||||
|
||||
use \Friendica\Directory\Content\Pager;
|
||||
use PDO;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Search
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\Profile
|
||||
*/
|
||||
private $profileModel;
|
||||
/**
|
||||
* @var \Friendica\Directory\Content\L10n
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\Profile $profileModel,
|
||||
\Friendica\Directory\Content\L10n $l10n
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->profileModel = $profileModel;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function render(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
$pager = new Pager($this->l10n, $request, 20);
|
||||
|
||||
$originalQuery = $query = filter_input(INPUT_GET, 'q');
|
||||
|
||||
$field = filter_input(INPUT_GET, 'field', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW & FILTER_FLAG_STRIP_HIGH);
|
||||
|
||||
if ($field) {
|
||||
$query .= '%';
|
||||
$sql_where = '`' . $field . '` LIKE :query';
|
||||
} else {
|
||||
$sql_where = "MATCH (p.`name`, p.`pdesc`, p.`profile_url`, p.`locality`, p.`region`, p.`country`, p.`tags` )
|
||||
AGAINST (:query IN BOOLEAN MODE)";
|
||||
}
|
||||
|
||||
$values = ['query' => $query];
|
||||
|
||||
$account_type = $args['account_type'] ?? 'All';
|
||||
if ($account_type != 'All') {
|
||||
$sql_where .= '
|
||||
AND `account_type` = :account_type';
|
||||
$values['account_type'] = $account_type;
|
||||
}
|
||||
|
||||
$profiles = $this->profileModel->getListForDisplay($pager->getItemsPerPage(), $pager->getStart(), $sql_where, $values);
|
||||
|
||||
$count = $this->profileModel->getCountForDisplay($sql_where, $values);
|
||||
|
||||
$vars = [
|
||||
'query' => $originalQuery,
|
||||
'page' => $pager->getPage(),
|
||||
'itemsperpage' => $pager->getItemsPerPage(),
|
||||
'count' => $count,
|
||||
'profiles' => $profiles
|
||||
];
|
||||
|
||||
// Render index view
|
||||
return $response->withJson($vars);
|
||||
}
|
||||
}
|
||||
80
src/classes/Controllers/Api/Submit.php
Normal file
80
src/classes/Controllers/Api/Submit.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Api;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Submit
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\ProfilePollQueue
|
||||
*/
|
||||
private $profilePollQueueModel;
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\ProfilePollQueue $profilePollQueueModel,
|
||||
\Psr\Log\LoggerInterface $logger
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->profilePollQueueModel = $profilePollQueueModel;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function execute(Request $request, Response $response): Response
|
||||
{
|
||||
try {
|
||||
$hexUrl = filter_input(INPUT_GET, 'url');
|
||||
if (!$hexUrl) {
|
||||
throw new \Exception('Missing url GET parameter', 400);
|
||||
}
|
||||
|
||||
$url = strtolower(hex2bin($hexUrl));
|
||||
|
||||
$this->logger->info('Received profile URL: ' . $url);
|
||||
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
if (!$host) {
|
||||
$this->logger->warning('Missing hostname in received profile URL: ' . $url);
|
||||
throw new \Exception('Missing hostname', 400);
|
||||
}
|
||||
|
||||
if (!\Friendica\Directory\Utils\Network::isPublicHost($host)) {
|
||||
$this->logger->warning('Private/reserved IP in received profile URL: ' . $url);
|
||||
throw new \Exception('Private/reserved hostname', 400);
|
||||
}
|
||||
|
||||
$profileUriInfo = \Friendica\Directory\Models\Profile::extractInfoFromProfileUrl($url);
|
||||
if (!$profileUriInfo) {
|
||||
$this->logger->warning('Invalid received profile URL: ' . $url);
|
||||
throw new \Exception('Invalid Profile URL', 400);
|
||||
}
|
||||
|
||||
$this->atlas->perform(
|
||||
'INSERT INTO `server_poll_queue` SET `base_url` = :base_url ON DUPLICATE KEY UPDATE `request_count` = `request_count` + 1',
|
||||
['base_url' => $profileUriInfo['server_uri']]
|
||||
);
|
||||
|
||||
$this->profilePollQueueModel->add($url);
|
||||
|
||||
$this->logger->info('Successfully received profile URL');
|
||||
} catch (\Exception $ex) {
|
||||
$response = $response->withStatus($ex->getCode(), $ex->getMessage());
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
60
src/classes/Controllers/Api/SyncPull.php
Normal file
60
src/classes/Controllers/Api/SyncPull.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Api;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class SyncPull
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Psr\Log\LoggerInterface $logger
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function execute(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$since = $args['since'] ?? null;
|
||||
|
||||
$stmt = 'SELECT `profile_url`
|
||||
FROM `profile` p
|
||||
JOIN `server` s ON s.`id` = p.`server_id`
|
||||
WHERE p.`available`
|
||||
AND NOT p.`hidden`
|
||||
AND s.`available`
|
||||
AND NOT s.`hidden`';
|
||||
$values = [];
|
||||
|
||||
if ($since) {
|
||||
$stmt .= '
|
||||
AND p.`updated` >= FROM_UNIXTIME(:since)';
|
||||
$values['since'] = [$since, \PDO::PARAM_INT];
|
||||
}
|
||||
|
||||
$profiles = $this->atlas->fetchColumn($stmt, $values);
|
||||
|
||||
$response = $response->withJson([
|
||||
'now' => time(),
|
||||
'count' => count($profiles),
|
||||
'results' => $profiles
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
136
src/classes/Controllers/Console.php
Normal file
136
src/classes/Controllers/Console.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers;
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* Description of Console
|
||||
*
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Console extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var \Slim\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
// Disables the default help handling
|
||||
protected $helpOptions = [];
|
||||
protected $customHelpOptions = ['h', 'help', '?'];
|
||||
|
||||
protected $routes = [
|
||||
'directory-add' => \Friendica\Directory\Routes\Console\DirectoryAdd::class,
|
||||
'directory-poll' => \Friendica\Directory\Routes\Console\DirectoryPoll::class,
|
||||
'profile-hide' => \Friendica\Directory\Routes\Console\ProfileHide::class,
|
||||
'profile-poll' => \Friendica\Directory\Routes\Console\ProfilePoll::class,
|
||||
'server-hide' => \Friendica\Directory\Routes\Console\ServerHide::class,
|
||||
'server-poll' => \Friendica\Directory\Routes\Console\ServerPoll::class,
|
||||
'install' => \Friendica\Directory\Routes\Console\Install::class,
|
||||
'updatedb' => \Friendica\Directory\Routes\Console\UpdateDb::class,
|
||||
'dbupdate' => \Friendica\Directory\Routes\Console\UpdateDb::class,
|
||||
];
|
||||
|
||||
public function __construct(\Slim\Container $container, ?array $argv = null)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$commandList = '';
|
||||
foreach ($this->routes as $command => $class) {
|
||||
$this->out($class);
|
||||
|
||||
$commandList .= ' ' . $command . ' ' . $class::description . "\n";
|
||||
}
|
||||
|
||||
$help = <<<HELP
|
||||
Usage: bin/console [--version] [-h|--help|-?] <command> [<args>] [-v]
|
||||
|
||||
Commands:
|
||||
$commandList
|
||||
|
||||
Options:
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
$showHelp = false;
|
||||
$subHelp = false;
|
||||
$command = null;
|
||||
|
||||
if ($this->getOption('version')) {
|
||||
//$this->out('Friendica Console version ' . FRIENDICA_VERSION);
|
||||
|
||||
return 0;
|
||||
} elseif ((count($this->options) === 0 || $this->getOption($this->customHelpOptions) === true || $this->getOption($this->customHelpOptions) === 1) && count($this->args) === 0
|
||||
) {
|
||||
$showHelp = true;
|
||||
} elseif (count($this->args) >= 2 && $this->getArgument(0) == 'help') {
|
||||
$command = $this->getArgument(1);
|
||||
$subHelp = true;
|
||||
array_shift($this->args);
|
||||
array_shift($this->args);
|
||||
} elseif (count($this->args) >= 1) {
|
||||
$command = $this->getArgument(0);
|
||||
array_shift($this->args);
|
||||
}
|
||||
|
||||
if (is_null($command)) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Increasing the logger level if -v is provided
|
||||
if ($this->getOption('v')) {
|
||||
/** @var \Monolog\Logger $logger */
|
||||
$handler = $this->container->get('logger')->popHandler();
|
||||
|
||||
$handler->setLevel(\Monolog\Logger::DEBUG);
|
||||
|
||||
$this->container->get('logger')->pushHandler($handler);
|
||||
}
|
||||
|
||||
$console = $this->getSubConsole($command);
|
||||
|
||||
if ($subHelp) {
|
||||
$console->setOption($this->customHelpOptions, true);
|
||||
}
|
||||
|
||||
return $console->execute();
|
||||
}
|
||||
|
||||
private function getSubConsole($command): \Asika\SimpleConsole\Console
|
||||
{
|
||||
$this->container->get('logger')->debug('Command: ' . $command);
|
||||
|
||||
if (!isset($this->routes[$command])) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Command ' . $command . ' doesn\'t exist');
|
||||
}
|
||||
|
||||
$subargs = $this->args;
|
||||
array_unshift($subargs, $this->executable);
|
||||
|
||||
$routeClassName = $this->routes[$command];
|
||||
|
||||
$consoleRoute = new $routeClassName($this->container);
|
||||
|
||||
/** @var \Asika\SimpleConsole\Console $subconsole */
|
||||
$subconsole = $consoleRoute($subargs);
|
||||
|
||||
foreach ($this->options as $name => $value) {
|
||||
$subconsole->setOption($name, $value);
|
||||
}
|
||||
|
||||
return $subconsole;
|
||||
}
|
||||
|
||||
}
|
||||
71
src/classes/Controllers/Console/DirectoryAdd.php
Normal file
71
src/classes/Controllers/Console/DirectoryAdd.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class DirectoryAdd extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
protected $atlas;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
?array $argv = null
|
||||
)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->atlas = $atlas;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console directory-add - Adds provided directory to queue
|
||||
Usage
|
||||
bin/console directory-add <directory_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Adds provided directory to queue
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$directory_url = $this->getArgument(0);
|
||||
|
||||
$result = $this->atlas->perform('INSERT IGNORE INTO `directory_poll_queue` SET
|
||||
`directory_url` = :directory_url',
|
||||
['directory_url' => $directory_url]
|
||||
);
|
||||
|
||||
if (!$result) {
|
||||
throw new \RuntimeException('Unable to add repository with URL: ' . $directory_url);
|
||||
}
|
||||
|
||||
$this->out('Successfully added the repository to the queue.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
66
src/classes/Controllers/Console/DirectoryPoll.php
Normal file
66
src/classes/Controllers/Console/DirectoryPoll.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class DirectoryPoll extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
protected $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Pollers\Directory
|
||||
*/
|
||||
protected $pollDirectory;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Pollers\Directory $pollDirectory,
|
||||
?array $argv = null
|
||||
)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->atlas = $atlas;
|
||||
$this->pollDirectory = $pollDirectory;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console directory-poll - Polls provided directory
|
||||
Usage
|
||||
bin/console directory-poll <directory_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Polls provided directory
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$this->pollDirectory->__invoke($this->getArgument(0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
158
src/classes/Controllers/Console/Install.php
Normal file
158
src/classes/Controllers/Console/Install.php
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
use Atlas\Pdo\Connection;
|
||||
use Monolog\Logger;
|
||||
use Seld\CliPrompt\CliPrompt;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Install extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(
|
||||
Logger $logger,
|
||||
?array $argv = null
|
||||
)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console install - Install directory
|
||||
Usage
|
||||
bin/console install <server_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Install directory
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$this->out('Friendica Directory Install Wizard');
|
||||
$this->out('==================================');
|
||||
|
||||
$config_file_path = __DIR__ . '/../../../../config/local.json';
|
||||
|
||||
if (is_file($config_file_path)) {
|
||||
throw new \RuntimeException('Local config file already exists, did you want to run "bin/console dbupdate" ?');
|
||||
}
|
||||
|
||||
if (!is_writable(dirname($config_file_path))) {
|
||||
throw new \RuntimeException('The config/ directory isn\'t writable, please check file permissions.');
|
||||
}
|
||||
|
||||
$this->out('Warning: This will override any existing database!');
|
||||
|
||||
|
||||
do {
|
||||
$this->out('Please enter your database hostname [localhost] ', false);
|
||||
|
||||
$host = CliPrompt::prompt();
|
||||
|
||||
if (!$host) {
|
||||
$host = 'localhost';
|
||||
}
|
||||
|
||||
do {
|
||||
$this->out('Please enter your database username: ', false);
|
||||
|
||||
$user = CliPrompt::prompt();
|
||||
} while (!$user);
|
||||
|
||||
$this->out('Please enter your database password: ', false);
|
||||
|
||||
$pass = CliPrompt::hiddenPrompt();
|
||||
|
||||
do {
|
||||
$this->out('Please enter your database name: ', false);
|
||||
|
||||
$base = CliPrompt::prompt();
|
||||
} while (!$base);
|
||||
|
||||
$localSettings = [
|
||||
'database' => [
|
||||
'driver' => 'mysql',
|
||||
'hostname' => $host,
|
||||
'database' => $base,
|
||||
'username' => $user,
|
||||
'password' => $pass,
|
||||
]
|
||||
];
|
||||
|
||||
try {
|
||||
$dsn = "{$localSettings['database']['driver']}:dbname={$localSettings['database']['database']};host={$localSettings['database']['hostname']}";
|
||||
|
||||
Connection::new($dsn, $localSettings['database']['username'], $localSettings['database']['password']);
|
||||
|
||||
break;
|
||||
} catch (\Exception $ex) {
|
||||
$this->logger->error($ex->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
} while (true);
|
||||
|
||||
$result = file_put_contents($config_file_path, json_encode($localSettings, JSON_PRETTY_PRINT));
|
||||
|
||||
if (!$result) {
|
||||
throw new \RuntimeException('Unable to write to config/local.json, please check writing permissions.');
|
||||
}
|
||||
|
||||
$this->out('Local config file successfully created.');
|
||||
|
||||
$this->out('Initializing database schema...');
|
||||
|
||||
$connectionUri = new \ByJG\Util\Uri("mysql://$user:$pass@$host/$base");
|
||||
|
||||
$migration = new \ByJG\DbMigration\Migration($connectionUri, __DIR__ . '/../../../sql/');
|
||||
|
||||
$migration->registerDatabase('mysql', \ByJG\DbMigration\Database\MySqlDatabase::class);
|
||||
|
||||
$migration->reset();
|
||||
|
||||
$this->out('Done.');
|
||||
|
||||
$this->out(<<<'STDOUT'
|
||||
|
||||
Note: You still need to manually set up a cronjob like the following on *nix:
|
||||
* * * * * cd /path/to/friendica-directory && bin/cron
|
||||
|
||||
======
|
||||
To populate your directory, you can either:
|
||||
- Add a new remote directory to pull from with "bin/console directory-add <directory URL>".
|
||||
- Add it as the main directory in your Friendica admin settings.
|
||||
STDOUT
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
79
src/classes/Controllers/Console/ProfileHide.php
Normal file
79
src/classes/Controllers/Console/ProfileHide.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
use Friendica\Directory\Models\Profile;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ProfileHide extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
protected $atlas;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
?array $argv = null
|
||||
)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->atlas = $atlas;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console profile-hide - Toggle profile hidden status
|
||||
Usage
|
||||
bin/console profile-hide <profile_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Toggle profile hidden status
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$profile_url = trim($this->getArgument(0), '/');
|
||||
|
||||
$profileInfo = Profile::extractInfoFromProfileUrl($profile_url);
|
||||
if (!$profileInfo) {
|
||||
throw new \RuntimeException('Invalid profile with URL: ' . $profile_url);
|
||||
}
|
||||
|
||||
$profile = $this->atlas->fetchOne('SELECT * FROM `profile` WHERE `addr` = :addr', ['addr' => $profileInfo['addr']]);
|
||||
if (!$profile) {
|
||||
throw new \RuntimeException('Unknown profile with URL: ' . $profile_url);
|
||||
}
|
||||
|
||||
$result = $this->atlas->fetchAffected('UPDATE `profile` SET `hidden` = 1 - `hidden` WHERE `id` = :id', ['id' => [$profile['id'], \PDO::PARAM_INT]]);
|
||||
if (!$result) {
|
||||
throw new \RuntimeException('Unable to update profile with ID: ' . $profile['id']);
|
||||
}
|
||||
|
||||
$this->out('Profile successfully ' . ($profile['hidden'] ? 'visible' : 'hidden'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
57
src/classes/Controllers/Console/ProfilePoll.php
Normal file
57
src/classes/Controllers/Console/ProfilePoll.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ProfilePoll extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var \Friendica\Directory\Pollers\Profile
|
||||
*/
|
||||
protected $pollProfile;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(\Friendica\Directory\Pollers\Profile $pollProfile, ?array $argv = null)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->pollProfile = $pollProfile;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console profile-poll - Polls provided profile
|
||||
Usage
|
||||
bin/console profile-poll <profile_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Polls provided profile
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$this->pollProfile->__invoke($this->getArgument(0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
81
src/classes/Controllers/Console/ServerHide.php
Normal file
81
src/classes/Controllers/Console/ServerHide.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ServerHide extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
protected $atlas;
|
||||
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\Server
|
||||
*/
|
||||
protected $serverModel;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\Server $serverModel,
|
||||
?array $argv = null
|
||||
)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->atlas = $atlas;
|
||||
$this->serverModel = $serverModel;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console server-hide - Toggle server hidden status
|
||||
Usage
|
||||
bin/console server-hide <server_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Toggle server hidden status
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$server_url = trim($this->getArgument(0), '/');
|
||||
|
||||
$server = $this->serverModel->getByUrlAlias($server_url);
|
||||
|
||||
if (!$server) {
|
||||
throw new \RuntimeException('Unknown server with URL: ' . $server_url);
|
||||
}
|
||||
|
||||
$result = $this->atlas->perform('UPDATE `server` SET `hidden` = 1 - `hidden` WHERE `id` = :id', ['id' => [$server['id'], \PDO::PARAM_INT]]);
|
||||
|
||||
if (!$result) {
|
||||
throw new \RuntimeException('Unable to update server with ID: ' . $server['id']);
|
||||
}
|
||||
|
||||
$this->out('Server successfully ' . ($server['hidden'] ? 'visible' : 'hidden'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
66
src/classes/Controllers/Console/ServerPoll.php
Normal file
66
src/classes/Controllers/Console/ServerPoll.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ServerPoll extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
protected $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Pollers\Server
|
||||
*/
|
||||
protected $pollServer;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Pollers\Server $pollServer,
|
||||
?array $argv = null
|
||||
)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->atlas = $atlas;
|
||||
$this->pollServer = $pollServer;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console server-poll - Polls provided server
|
||||
Usage
|
||||
bin/console server-poll <server_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Polls provided server
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$this->pollServer->__invoke($this->getArgument(0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
72
src/classes/Controllers/Console/UpdateDb.php
Normal file
72
src/classes/Controllers/Console/UpdateDb.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Console;
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class UpdateDb extends \Asika\SimpleConsole\Console
|
||||
{
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
/**
|
||||
* @var \ByJG\DbMigration\Migration
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
protected $helpOptions = ['h', 'help', '?'];
|
||||
|
||||
public function __construct(
|
||||
Logger $logger,
|
||||
\ByJG\DbMigration\Migration $migration,
|
||||
?array $argv = null
|
||||
)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->logger = $logger;
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console updatedb - Update database schema
|
||||
Usage
|
||||
bin/console updatedb <server_url> [-h|--help|-?] [-v]
|
||||
|
||||
Description
|
||||
Update database schema
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
||||
protected function doExecute()
|
||||
{
|
||||
if (count($this->args) == 0) {
|
||||
$this->out($this->getHelp());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($this->args) > 1) {
|
||||
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
|
||||
}
|
||||
|
||||
$this->out('Updating database schema to latest version...');
|
||||
|
||||
$this->migration->up();
|
||||
|
||||
$this->out('Database schema migrated to version ' . $this->migration->getCurrentVersion()['version']);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
224
src/classes/Controllers/Cron.php
Normal file
224
src/classes/Controllers/Cron.php
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Cron
|
||||
{
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var \Friendica\Directory\Pollers\Profile
|
||||
*/
|
||||
protected $profilePoller;
|
||||
|
||||
/**
|
||||
* @var \Friendica\Directory\Pollers\Server
|
||||
*/
|
||||
protected $serverPoller;
|
||||
|
||||
/**
|
||||
* @var \Friendica\Directory\Pollers\Directory
|
||||
*/
|
||||
protected $directoryPoller;
|
||||
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
protected $atlas;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [
|
||||
'directory_poll_delay' => 3600, // 1 hour
|
||||
'server_poll_delay' => 24 * 3600, // 1 day
|
||||
'profile_poll_delay' => 24 * 3600, // 1 day
|
||||
|
||||
'directory_poll_retry_base_delay' => 600, // 10 minutes
|
||||
'server_poll_retry_base_delay' => 1800, // 30 minutes
|
||||
'profile_poll_retry_base_delay' => 1800, // 30 minutes
|
||||
];
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
private $startTime;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Pollers\Profile $profilePoller,
|
||||
\Friendica\Directory\Pollers\Server $serverPoller,
|
||||
\Friendica\Directory\Pollers\Directory $directoryPoller,
|
||||
\Psr\Log\LoggerInterface $logger,
|
||||
array $settings = []
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->profilePoller = $profilePoller;
|
||||
$this->serverPoller = $serverPoller;
|
||||
$this->directoryPoller = $directoryPoller;
|
||||
$this->logger = $logger;
|
||||
$this->settings = array_merge($this->settings, $settings);
|
||||
$this->startTime = microtime(true);
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$this->logger->info('Start Cron job');
|
||||
|
||||
$this->pollDirectories(9);
|
||||
|
||||
$this->pollServers(24);
|
||||
|
||||
$this->pollProfiles(58);
|
||||
|
||||
$this->logger->info('Stop Cron job');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $time_limit
|
||||
*/
|
||||
private function pollDirectories(int $time_limit = null): void
|
||||
{
|
||||
$directories = $this->atlas->fetchAll(
|
||||
'SELECT `directory_url`, `retries_count`
|
||||
FROM `directory_poll_queue`
|
||||
WHERE `next_poll` <= NOW()
|
||||
ORDER BY ISNULL(`last_polled`) DESC'
|
||||
);
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
if ($time_limit && microtime(true) - $this->startTime > $time_limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
$directory_poll_result = $this->directoryPoller->__invoke($directory['directory_url']);
|
||||
|
||||
if ($directory_poll_result) {
|
||||
$new_retries_count = 0;
|
||||
$poll_delay = $this->settings['directory_poll_delay'];
|
||||
} else {
|
||||
$new_retries_count = $directory['retries_count'] + 1;
|
||||
$poll_delay = $this->settings['directory_poll_retry_base_delay'] * pow($new_retries_count, 3);
|
||||
}
|
||||
|
||||
$this->atlas->perform(
|
||||
'UPDATE `directory_poll_queue` SET
|
||||
`last_polled` = NOW(),
|
||||
`next_poll` = DATE_ADD(NOW(), INTERVAL :seconds SECOND),
|
||||
`retries_count` = :retries_count
|
||||
WHERE `directory_url` = :directory_url',
|
||||
[
|
||||
'seconds' => [$poll_delay, \PDO::PARAM_INT],
|
||||
'directory_url' => $directory['directory_url'],
|
||||
'retries_count' => [$new_retries_count, \PDO::PARAM_INT]
|
||||
]
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function pollServers(int $time_limit = null): void
|
||||
{
|
||||
$servers = $this->atlas->fetchAll(
|
||||
'SELECT `base_url`, `retries_count`
|
||||
FROM `server_poll_queue`
|
||||
WHERE `next_poll` <= NOW()
|
||||
ORDER BY ISNULL(`last_polled`) DESC, `request_count` DESC'
|
||||
);
|
||||
|
||||
foreach ($servers as $server_queue_item) {
|
||||
if ($time_limit && microtime(true) - $this->startTime > $time_limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
$new_base_url = null;
|
||||
|
||||
$server_id = $this->serverPoller->__invoke($server_queue_item['base_url']);
|
||||
|
||||
if ($server_id) {
|
||||
$new_base_url = $this->atlas->fetchValue('SELECT `base_url` FROM `server` WHERE `id` = :id', ['id' => [$server_id, \PDO::PARAM_INT]]);
|
||||
}
|
||||
|
||||
if ($new_base_url && $new_base_url != $server_queue_item['base_url']) {
|
||||
$this->atlas->perform('INSERT IGNORE INTO `server_poll_queue` SET `base_url` = :base_url', ['base_url' => $new_base_url]);
|
||||
$this->logger->info('New base URL: ' . $server_queue_item['base_url'] . ' => ' . $new_base_url);
|
||||
}
|
||||
|
||||
if ($new_base_url == $server_queue_item['base_url']) {
|
||||
$new_retries_count = 0;
|
||||
$poll_delay = $this->settings['server_poll_delay'];
|
||||
} else {
|
||||
$new_retries_count = $server_queue_item['retries_count'] + 1;
|
||||
$poll_delay = $this->settings['server_poll_retry_base_delay'] * pow($new_retries_count, 3);
|
||||
}
|
||||
|
||||
$this->atlas->perform(
|
||||
'UPDATE `server_poll_queue` SET
|
||||
`last_polled` = NOW(),
|
||||
`next_poll` = DATE_ADD(NOW(), INTERVAL :seconds SECOND),
|
||||
`retries_count` = :retries_count,
|
||||
`request_count` = 0
|
||||
WHERE `base_url` = :base_url',
|
||||
[
|
||||
'seconds' => [$poll_delay, \PDO::PARAM_INT],
|
||||
'base_url' => $server_queue_item['base_url'],
|
||||
'retries_count' => [$new_retries_count, \PDO::PARAM_INT]
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage() . ': ' . $e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function pollProfiles(int $time_limit = null): void
|
||||
{
|
||||
$profiles = $this->atlas->fetchAll(
|
||||
'SELECT `profile_url`, `retries_count`
|
||||
FROM `profile_poll_queue`
|
||||
WHERE `next_poll` <= NOW()
|
||||
ORDER BY RAND() ASC'
|
||||
);
|
||||
|
||||
foreach ($profiles as $profile) {
|
||||
if ($time_limit && microtime(true) - $this->startTime > $time_limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
$profile_poll_result = $this->profilePoller->__invoke($profile['profile_url']);
|
||||
|
||||
if ($profile_poll_result) {
|
||||
$new_retries_count = 0;
|
||||
$poll_delay = $this->settings['profile_poll_delay'];
|
||||
} else {
|
||||
$new_retries_count = $profile['retries_count'] + 1;
|
||||
$poll_delay = $this->settings['profile_poll_retry_base_delay'] * pow($new_retries_count, 3);
|
||||
}
|
||||
|
||||
|
||||
$this->atlas->perform('UPDATE `profile_poll_queue` SET
|
||||
`last_polled` = NOW(),
|
||||
`next_poll` = DATE_ADD(NOW(), INTERVAL :seconds SECOND),
|
||||
`retries_count` = :retries_count
|
||||
WHERE `profile_url` = :profile_url',
|
||||
[
|
||||
'seconds' => [$poll_delay, \PDO::PARAM_INT],
|
||||
'profile_url' => $profile['profile_url'],
|
||||
'retries_count' => [$new_retries_count, \PDO::PARAM_INT]
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage() . ': ' . $e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/classes/Controllers/Web/Directory.php
Normal file
86
src/classes/Controllers/Web/Directory.php
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Web;
|
||||
|
||||
use \Friendica\Directory\Content\Pager;
|
||||
use \Friendica\Directory\Views\Widget\PopularCountries;
|
||||
use \Friendica\Directory\Views\Widget\PopularTags;
|
||||
use PDO;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Directory
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\Profile
|
||||
*/
|
||||
private $profileModel;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\Widget\AccountTypeTabs
|
||||
*/
|
||||
private $accountTypeTabs;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\PhpRenderer
|
||||
*/
|
||||
private $renderer;
|
||||
/**
|
||||
* @var \Friendica\Directory\Content\L10n
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\Profile $profileModel,
|
||||
\Friendica\Directory\Views\Widget\AccountTypeTabs $accountTypeTabs,
|
||||
\Friendica\Directory\Views\PhpRenderer $renderer,
|
||||
\Friendica\Directory\Content\L10n $l10n
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->profileModel = $profileModel;
|
||||
$this->accountTypeTabs = $accountTypeTabs;
|
||||
$this->renderer = $renderer;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function render(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$popularTags = new PopularTags($this->atlas, $this->renderer);
|
||||
$popularCountries = new PopularCountries($this->atlas, $this->renderer);
|
||||
|
||||
$pager = new Pager($this->l10n, $request, 20);
|
||||
|
||||
$condition = '';
|
||||
$values = [];
|
||||
if (!empty($args['account_type'])) {
|
||||
$condition = '`account_type` = :account_type';
|
||||
$values = ['account_type' => $args['account_type']];
|
||||
}
|
||||
|
||||
$profiles = $this->profileModel->getListForDisplay($pager->getItemsPerPage(), $pager->getStart(), $condition, $values);
|
||||
|
||||
$count = $this->profileModel->getCountForDisplay($condition, $values);
|
||||
|
||||
$vars = [
|
||||
'title' => $this->l10n->t('People'),
|
||||
'profiles' => $profiles,
|
||||
'pager_full' => $pager->renderFull($count),
|
||||
'pager_minimal' => $pager->renderMinimal($count),
|
||||
'accountTypeTabs' => $this->accountTypeTabs->render('directory', $args['account_type'] ?? ''),
|
||||
'popularTags' => $popularTags->render(),
|
||||
'popularCountries' => $popularCountries->render(),
|
||||
];
|
||||
|
||||
$content = $this->renderer->fetch('directory.phtml', $vars);
|
||||
|
||||
// Render index view
|
||||
return $this->renderer->render($response, 'layout.phtml', ['baseUrl' => $request->getUri()->getBaseUrl(), 'content' => $content]);
|
||||
}
|
||||
}
|
||||
54
src/classes/Controllers/Web/Photo.php
Normal file
54
src/classes/Controllers/Web/Photo.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Web;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Photo
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
}
|
||||
|
||||
public function render(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$data = $this->atlas->fetchValue(
|
||||
'SELECT `data` FROM `photo` WHERE `profile_id` = :profile_id',
|
||||
['profile_id' => $args['profile_id']]
|
||||
);
|
||||
|
||||
if (!$data) {
|
||||
$data = file_get_contents('public/images/default-profile-sm.jpg');
|
||||
}
|
||||
|
||||
//Try and cache our result.
|
||||
$etag = md5($data);
|
||||
|
||||
$response = $response
|
||||
->withHeader('Etag', $etag)
|
||||
->withHeader('Expires', date('D, d M Y H:i:s' . ' GMT', strtotime('now + 1 week')))
|
||||
->withHeader('Cache-Control', 'max-age=' . intval(7 * 24 * 3600))
|
||||
->withoutHeader('Pragma');
|
||||
|
||||
if ($request->getServerParam('HTTP_IF_NONE_MATCH') == $etag) {
|
||||
$response = $response->withStatus(304, 'Not Modified');
|
||||
} else {
|
||||
$response = $response->withHeader('Content-type', 'image/jpeg');
|
||||
$response->getBody()->write($data);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
94
src/classes/Controllers/Web/Search.php
Normal file
94
src/classes/Controllers/Web/Search.php
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Web;
|
||||
|
||||
use \Friendica\Directory\Content\Pager;
|
||||
use PDO;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Search
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\Profile
|
||||
*/
|
||||
private $profileModel;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\PhpRenderer
|
||||
*/
|
||||
private $renderer;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\Widget\AccountTypeTabs
|
||||
*/
|
||||
private $accountTypeTabs;
|
||||
/**
|
||||
* @var \Friendica\Directory\Content\L10n
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\Profile $profileModel,
|
||||
\Friendica\Directory\Views\Widget\AccountTypeTabs $accountTypeTabs,
|
||||
\Friendica\Directory\Views\PhpRenderer $renderer,
|
||||
\Friendica\Directory\Content\L10n $l10n
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->profileModel = $profileModel;
|
||||
$this->accountTypeTabs = $accountTypeTabs;
|
||||
$this->renderer = $renderer;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function render(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
$pager = new Pager($this->l10n, $request, 20);
|
||||
|
||||
$originalQuery = $query = filter_input(INPUT_GET, 'q');
|
||||
|
||||
$field = filter_input(INPUT_GET, 'field', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW & FILTER_FLAG_STRIP_HIGH);
|
||||
|
||||
if ($field) {
|
||||
$query .= '%';
|
||||
$sql_where = '`' . $field . '` LIKE :query';
|
||||
} else {
|
||||
$sql_where = "MATCH (p.`name`, p.`pdesc`, p.`profile_url`, p.`locality`, p.`region`, p.`country`, p.`tags` )
|
||||
AGAINST (:query IN BOOLEAN MODE)";
|
||||
}
|
||||
|
||||
$values = ['query' => $query];
|
||||
|
||||
$account_type = $args['account_type'] ?? '';
|
||||
if ($account_type) {
|
||||
$sql_where .= '
|
||||
AND `account_type` = :account_type';
|
||||
$values['account_type'] = $account_type;
|
||||
}
|
||||
|
||||
$profiles = $this->profileModel->getListForDisplay($pager->getItemsPerPage(), $pager->getStart(), $sql_where, $values);
|
||||
|
||||
$count = $this->profileModel->getCountForDisplay($sql_where, $values);
|
||||
|
||||
$vars = [
|
||||
'query' => $originalQuery,
|
||||
'count' => $count,
|
||||
'accountTypeTabs' => $this->accountTypeTabs->render('search', $account_type, ['q' => $originalQuery]),
|
||||
'profiles' => $profiles,
|
||||
'pager_full' => $pager->renderFull($count),
|
||||
'pager_minimal' => $pager->renderMinimal($count),
|
||||
];
|
||||
|
||||
$content = $this->renderer->fetch('search.phtml', $vars);
|
||||
|
||||
// Render index view
|
||||
return $this->renderer->render($response, 'layout.phtml', ['baseUrl' => $request->getUri()->getBaseUrl(), 'content' => $content, 'noNavSearch' => true]);
|
||||
}
|
||||
}
|
||||
100
src/classes/Controllers/Web/Servers.php
Normal file
100
src/classes/Controllers/Web/Servers.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Controllers\Web;
|
||||
|
||||
use \Friendica\Directory\Content\Pager;
|
||||
use PDO;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Servers
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\PhpRenderer
|
||||
*/
|
||||
private $renderer;
|
||||
/**
|
||||
* @var \Friendica\Directory\Content\L10n
|
||||
*/
|
||||
private $l10n;
|
||||
/**
|
||||
* @var \Psr\SimpleCache\CacheInterface
|
||||
*/
|
||||
private $simplecache;
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Views\PhpRenderer $renderer,
|
||||
\Friendica\Directory\Content\L10n $l10n,
|
||||
\Psr\SimpleCache\CacheInterface $simplecache
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->renderer = $renderer;
|
||||
$this->l10n = $l10n;
|
||||
$this->simplecache = $simplecache;
|
||||
}
|
||||
|
||||
public function render(Request $request, Response $response): Response
|
||||
{
|
||||
$stable_version = $this->simplecache->get('stable_version');
|
||||
if (!$stable_version) {
|
||||
$stable_version = trim(file_get_contents('https://git.friendi.ca/friendica/friendica/raw/branch/master/VERSION'));
|
||||
$this->simplecache->set('stable_version', $stable_version);
|
||||
}
|
||||
|
||||
$dev_version = $this->simplecache->get('dev_version');
|
||||
if (!$dev_version) {
|
||||
$dev_version = trim(file_get_contents('https://git.friendi.ca/friendica/friendica/raw/branch/develop/VERSION'));
|
||||
$this->simplecache->set('dev_version', $dev_version);
|
||||
}
|
||||
|
||||
$pager = new Pager($this->l10n, $request, 20);
|
||||
|
||||
$stmt = 'SELECT *
|
||||
FROM `server` s
|
||||
WHERE `reg_policy` = "REGISTER_OPEN"
|
||||
AND `available`
|
||||
AND NOT `hidden`
|
||||
ORDER BY `health_score` DESC, `ssl_state` DESC, `info` != "" DESC, `dt_last_probed` DESC
|
||||
LIMIT :start, :limit';
|
||||
$servers = $this->atlas->fetchAll($stmt, [
|
||||
'start' => [$pager->getStart(), PDO::PARAM_INT],
|
||||
'limit' => [$pager->getItemsPerPage(), PDO::PARAM_INT]
|
||||
]);
|
||||
|
||||
foreach ($servers as $key => $server) {
|
||||
$servers[$key]['user_count'] = $this->atlas->fetchValue(
|
||||
'SELECT COUNT(*) FROM `profile` WHERE `available` AND `server_id` = :server_id',
|
||||
['server_id' => [$server['id'], PDO::PARAM_INT]]
|
||||
);
|
||||
}
|
||||
|
||||
$stmt = 'SELECT COUNT(*)
|
||||
FROM `server` s
|
||||
WHERE `reg_policy` = "REGISTER_OPEN"
|
||||
AND `available`
|
||||
AND NOT `hidden`';
|
||||
$count = $this->atlas->fetchValue($stmt);
|
||||
|
||||
$vars = [
|
||||
'title' => $this->l10n->t('Public Servers'),
|
||||
'servers' => $servers,
|
||||
'pager' => $pager->renderFull($count),
|
||||
'stable_version' => $stable_version,
|
||||
'dev_version' => $dev_version,
|
||||
];
|
||||
|
||||
$content = $this->renderer->fetch('servers.phtml', $vars);
|
||||
|
||||
// Render index view
|
||||
return $this->renderer->render($response, 'layout.phtml', ['baseUrl' => $request->getUri()->getBaseUrl(), 'content' => $content]);
|
||||
}
|
||||
}
|
||||
20
src/classes/Model.php
Normal file
20
src/classes/Model.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Model
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
protected $atlas;
|
||||
|
||||
public function __construct(\Atlas\Pdo\Connection $atlas)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
}
|
||||
}
|
||||
121
src/classes/Models/Profile.php
Normal file
121
src/classes/Models/Profile.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Models;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Profile extends \Friendica\Directory\Model
|
||||
{
|
||||
public function deleteById(int $profile_id): bool
|
||||
{
|
||||
$this->atlas->perform('DELETE FROM `photo` WHERE `profile_id` = :profile_id',
|
||||
['profile_id' => [$profile_id, \PDO::PARAM_INT]]
|
||||
);
|
||||
$this->atlas->perform('DELETE FROM `tag` WHERE `profile_id` = :profile_id',
|
||||
['profile_id' => [$profile_id, \PDO::PARAM_INT]]
|
||||
);
|
||||
$this->atlas->perform('DELETE FROM `profile` WHERE `id` = :profile_id',
|
||||
['profile_id' => [$profile_id, \PDO::PARAM_INT]]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $profile_uri
|
||||
* @return array|boolean
|
||||
*/
|
||||
public static function extractInfoFromProfileUrl(string $profile_uri)
|
||||
{
|
||||
if (substr($profile_uri, 0, 4) === 'http') {
|
||||
// http://friendica.mrpetovan.com/profile/hypolite
|
||||
// https://friendica.mrpetovan.com/profile/hypolite
|
||||
// http://friendica.mrpetovan.com/~hypolite
|
||||
// https://friendica.mrpetovan.com/~hypolite
|
||||
|
||||
$username = ltrim(basename($profile_uri), '~');
|
||||
|
||||
if (strpos($profile_uri, '~') !== false) {
|
||||
$server_uri = substr($profile_uri, 0, strpos($profile_uri, '/~'));
|
||||
} elseif (strpos($profile_uri, '/profile/') !== false) {
|
||||
$server_uri = substr($profile_uri, 0, strpos($profile_uri, '/profile/'));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// hypolite@friendica.mrpetovan.com
|
||||
// acct:hypolite@friendica.mrpetovan.com
|
||||
// acct://hypolite@friendica.mrpetovan.com
|
||||
|
||||
$local = str_replace('acct:', '', $profile_uri);
|
||||
|
||||
if (substr($local, 0, 2) == '//') {
|
||||
$local = substr($local, 2);
|
||||
}
|
||||
|
||||
if (strpos($local, '@') !== false) {
|
||||
$username = substr($local, 0, strpos($local, '@'));
|
||||
$server_uri = 'http://' . substr($local, strpos($local, '@') + 1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$hostname = str_replace(['https://', 'http://'], ['', ''], $server_uri);
|
||||
|
||||
$addr = $username . '@' . $hostname;
|
||||
|
||||
return [
|
||||
'username' => $username,
|
||||
'server_uri' => $server_uri,
|
||||
'hostname' => $hostname,
|
||||
'addr' => $addr
|
||||
];
|
||||
}
|
||||
|
||||
public function getListForDisplay(int $limit = 30, int $start = 0, string $condition = '', array $values = []): array
|
||||
{
|
||||
if ($condition) {
|
||||
$condition = 'AND ' . $condition;
|
||||
}
|
||||
|
||||
$values = array_merge($values, [
|
||||
'start' => [$start, \PDO::PARAM_INT],
|
||||
'limit' => [$limit, \PDO::PARAM_INT]
|
||||
]);
|
||||
|
||||
$stmt = 'SELECT p.`id`, p.`name`, p.`username`, p.`addr`, p.`account_type`, p.`pdesc`,
|
||||
p.`locality`, p.`region`, p.`country`, p.`profile_url`, p.`dfrn_request`, p.`photo`,
|
||||
p.`tags`, p.`last_activity`
|
||||
FROM `profile` p
|
||||
JOIN `server` s ON s.`id` = p.`server_id` AND s.`available` AND NOT s.`hidden`
|
||||
WHERE p.`available`
|
||||
AND NOT p.`hidden`
|
||||
' . $condition . '
|
||||
GROUP BY p.`id`
|
||||
ORDER BY `filled_fields` DESC, `last_activity` DESC, `updated` DESC
|
||||
LIMIT :start, :limit';
|
||||
$profiles = $this->atlas->fetchAll($stmt, $values);
|
||||
|
||||
return $profiles;
|
||||
}
|
||||
|
||||
|
||||
public function getCountForDisplay(string $condition = '', array $values = []): int
|
||||
{
|
||||
if ($condition) {
|
||||
$condition = 'AND ' . $condition;
|
||||
}
|
||||
|
||||
$stmt = 'SELECT COUNT(*)
|
||||
FROM `profile` p
|
||||
JOIN `server` s ON s.`id` = p.`server_id` AND s.`available` AND NOT s.`hidden`
|
||||
WHERE p.`available`
|
||||
AND NOT p.`hidden`
|
||||
' . $condition;
|
||||
$count = $this->atlas->fetchValue($stmt, $values);
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
25
src/classes/Models/ProfilePollQueue.php
Normal file
25
src/classes/Models/ProfilePollQueue.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Models;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ProfilePollQueue extends \Friendica\Directory\Model
|
||||
{
|
||||
public function add(string $profile_url): bool
|
||||
{
|
||||
$url = trim($profile_url);
|
||||
|
||||
if (!$url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->atlas->perform(
|
||||
'INSERT IGNORE INTO `profile_poll_queue` SET `profile_url` = :profile_url',
|
||||
['profile_url' => $url]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
42
src/classes/Models/Server.php
Normal file
42
src/classes/Models/Server.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Models;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Server extends \Friendica\Directory\Model
|
||||
{
|
||||
/**
|
||||
* @param string $server_url
|
||||
* @return array|null
|
||||
*/
|
||||
public function getByUrlAlias(string $server_url): ?array
|
||||
{
|
||||
$server_alias = str_replace(['http://', 'https://'], ['', ''], $server_url);
|
||||
|
||||
$server = $this->atlas->fetchOne('SELECT s.* FROM `server` s JOIN `server_alias` sa ON sa.`server_id` = s.`id` WHERE sa.`alias` = :alias',
|
||||
['alias' => $server_alias]
|
||||
);
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $server_url
|
||||
*/
|
||||
public function addAliasToServer(int $server_id, string $server_url): void
|
||||
{
|
||||
$server_alias = str_replace(['http://', 'https://'], ['', ''], $server_url);
|
||||
|
||||
$this->atlas->perform('INSERT INTO `server_alias`
|
||||
SET `server_id` = :server_id,
|
||||
`alias` = :alias,
|
||||
`timestamp` = NOW()
|
||||
ON DUPLICATE KEY UPDATE `timestamp` = NOW()',
|
||||
[
|
||||
'server_id' => $server_id,
|
||||
'alias' => strtolower($server_alias)
|
||||
]);
|
||||
}
|
||||
}
|
||||
120
src/classes/Pollers/Directory.php
Normal file
120
src/classes/Pollers/Directory.php
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Pollers;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Directory
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\ProfilePollQueue
|
||||
*/
|
||||
private $profilePollQueueModel;
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $settings = [
|
||||
'probe_timeout' => 5
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\ProfilePollQueue $profilePollQueueModel,
|
||||
\Psr\Log\LoggerInterface $logger,
|
||||
array $settings)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->profilePollQueueModel = $profilePollQueueModel;
|
||||
$this->logger = $logger;
|
||||
$this->settings = array_merge($this->settings, $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $directory_url
|
||||
* @param int|null $last_polled
|
||||
* @return bool
|
||||
*/
|
||||
public function __invoke(string $directory_url, int $last_polled = null): bool
|
||||
{
|
||||
$this->logger->info('Pull from directory with URL: ' . $directory_url);
|
||||
|
||||
try {
|
||||
$host = parse_url($directory_url, PHP_URL_HOST);
|
||||
if (!$host) {
|
||||
throw new \Exception('Missing hostname in polled directory URL: ' . $directory_url);
|
||||
}
|
||||
|
||||
if (!\Friendica\Directory\Utils\Network::isPublicHost($host)) {
|
||||
throw new \Exception('Private/reserved IP in polled directory URL: ' . $directory_url);
|
||||
}
|
||||
|
||||
$profiles = $this->getPullResult($directory_url, $last_polled);
|
||||
foreach ($profiles as $profile_url) {
|
||||
$this->profilePollQueueModel->add($profile_url);
|
||||
}
|
||||
|
||||
$this->logger->info('Successfully pulled ' . count($profiles) . ' profiles');
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getPullResult(string $directory_url, ?int $last_polled = null): array
|
||||
{
|
||||
$path = '/sync/pull/all';
|
||||
if ($last_polled) {
|
||||
$path = '/sync/pull/since/' . $last_polled;
|
||||
}
|
||||
|
||||
//Prepare the CURL call.
|
||||
$handle = curl_init();
|
||||
$options = array(
|
||||
//Timeouts
|
||||
CURLOPT_TIMEOUT => max($this->settings['probe_timeout'], 1), //Minimum of 1 second timeout.
|
||||
CURLOPT_CONNECTTIMEOUT => 1,
|
||||
//Redirecting
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 8,
|
||||
//SSL
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
// CURLOPT_VERBOSE => true,
|
||||
// CURLOPT_CERTINFO => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
|
||||
//Basic request
|
||||
CURLOPT_USERAGENT => 'friendica-directory-probe-1.0',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_URL => $directory_url . $path
|
||||
);
|
||||
curl_setopt_array($handle, $options);
|
||||
|
||||
$this->logger->info('Pulling profiles from directory URL: ' . $directory_url . $path);
|
||||
|
||||
//Probe the site.
|
||||
$pull_data = curl_exec($handle);
|
||||
|
||||
//Done with CURL now.
|
||||
curl_close($handle);
|
||||
|
||||
$data = json_decode($pull_data, true);
|
||||
|
||||
if (!isset($data['results']) || !is_array($data['results'])) {
|
||||
throw new \Exception('Invalid directory pull data for directory with URL: ' . $directory_url . $path);
|
||||
}
|
||||
|
||||
return $data['results'];
|
||||
}
|
||||
}
|
||||
337
src/classes/Pollers/Profile.php
Normal file
337
src/classes/Pollers/Profile.php
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Pollers;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Profile
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\Server
|
||||
*/
|
||||
private $serverModel;
|
||||
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\Profile
|
||||
*/
|
||||
private $profileModel;
|
||||
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $settings = [
|
||||
'probe_timeout' => 5,
|
||||
'remove_profile_health_threshold' => -60
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\Server $serverModel,
|
||||
\Friendica\Directory\Models\Profile $profileModel,
|
||||
\Psr\Log\LoggerInterface $logger,
|
||||
array $settings
|
||||
)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->serverModel = $serverModel;
|
||||
$this->profileModel = $profileModel;
|
||||
$this->logger = $logger;
|
||||
$this->settings = array_merge($this->settings, $settings);
|
||||
}
|
||||
|
||||
public function __invoke(string $profile_uri)
|
||||
{
|
||||
if (!strlen($profile_uri)) {
|
||||
$this->logger->error('Received empty profile URI', ['class' => __CLASS__]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$submit_start = microtime(true);
|
||||
|
||||
$this->logger->info('Poll profile URI: ' . $profile_uri);
|
||||
|
||||
$host = parse_url($profile_uri, PHP_URL_HOST);
|
||||
|
||||
if (!$host) {
|
||||
$this->logger->warning('Missing hostname in polled profile URL: ' . $profile_uri);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\Friendica\Directory\Utils\Network::isPublicHost($host)) {
|
||||
$this->logger->warning('Private/reserved IP in polled profile URL: ' . $profile_uri);
|
||||
return false;
|
||||
}
|
||||
|
||||
$profileUriInfo = \Friendica\Directory\Models\Profile::extractInfoFromProfileUrl($profile_uri);
|
||||
if (!$profileUriInfo) {
|
||||
$this->logger->warning('Profile URI invalid');
|
||||
return false;
|
||||
}
|
||||
|
||||
$server = $this->serverModel->getByUrlAlias($profileUriInfo['server_uri']);
|
||||
if (!$server) {
|
||||
$this->atlas->perform('INSERT IGNORE INTO `server_poll_queue` SET `base_url` = :base_url', ['base_url' => $profileUriInfo['server_uri']]);
|
||||
|
||||
// No server entry yet, no need to continue.
|
||||
$this->logger->info('Profile poll aborted, no server entry yet for ' . $profileUriInfo['server_uri']);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($server['hidden']) {
|
||||
$this->logger->info('Profile poll aborted, server is hidden: ' . $server['base_url']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$username = $profileUriInfo['username'];
|
||||
$addr = $profileUriInfo['addr'];
|
||||
|
||||
$profile_id = $this->atlas->fetchValue(
|
||||
'SELECT `id` FROM `profile` WHERE `server_id` = :server_id AND `username` = :username',
|
||||
['server_id' => $server['id'], 'username' => $username]
|
||||
);
|
||||
|
||||
if ($profile_id) {
|
||||
$this->atlas->perform(
|
||||
'UPDATE `profile` SET
|
||||
`available` = 0,
|
||||
`updated` = NOW()
|
||||
WHERE `id` = :profile_id',
|
||||
['profile_id' => [$profile_id, \PDO::PARAM_INT]]
|
||||
);
|
||||
|
||||
$this->atlas->perform(
|
||||
'DELETE FROM `tag` WHERE `profile_id` = :profile_id',
|
||||
['profile_id' => [$profile_id, \PDO::PARAM_INT]]
|
||||
);
|
||||
}
|
||||
|
||||
//Skip the profile scrape?
|
||||
$noscrape = $server['noscrape_url'];
|
||||
|
||||
$params = [];
|
||||
if ($noscrape) {
|
||||
$this->logger->debug('Calling ' . $server['noscrape_url'] . '/' . $username);
|
||||
$params = \Friendica\Directory\Utils\Scrape::retrieveNoScrapeData($server['noscrape_url'] . '/' . $username);
|
||||
$noscrape = !!$params; //If the result was false, do a scrape after all.
|
||||
}
|
||||
|
||||
if (!$noscrape) {
|
||||
$this->logger->notice('Parsing profile page ' . $profile_uri);
|
||||
$params = \Friendica\Directory\Utils\Scrape::retrieveProfileData($profile_uri);
|
||||
}
|
||||
|
||||
// Empty result is due to an offline site.
|
||||
if (count($params) < 2) {
|
||||
//But for sites that are already in bad status. Do a cleanup now.
|
||||
if ($profile_id && $server['health_score'] < $this->settings['remove_profile_health_threshold']) {
|
||||
$this->profileModel->deleteById($profile_id);
|
||||
}
|
||||
|
||||
$this->logger->info('Poll aborted, empty result');
|
||||
return false;
|
||||
} elseif (!empty($params['explicit-hide']) && $profile_id) {
|
||||
// We don't care about valid dfrn if the user indicates to be hidden.
|
||||
$this->profileModel->deleteById($profile_id);
|
||||
$this->logger->info('Poll aborted, profile asked to be removed from directory');
|
||||
return true; //This is a good update.
|
||||
}
|
||||
|
||||
if (!empty($params['hide']) || empty($params['fn']) || empty($params['photo'])) {
|
||||
if ($profile_id) {
|
||||
$this->profileModel->deleteById($profile_id);
|
||||
}
|
||||
|
||||
if (!empty($params['hide'])) {
|
||||
$this->logger->info('Poll aborted, hidden profile.');
|
||||
} else {
|
||||
$this->logger->info('Poll aborted, incomplete profile.');
|
||||
}
|
||||
|
||||
return true; //This is a good update.
|
||||
}
|
||||
|
||||
// This is most likely a problem with the site configuration. Ignore.
|
||||
if (self::validateParams($params)) {
|
||||
$this->logger->warning('Poll aborted, parameters invalid.', ['params' => $params]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$account_type = 'People';
|
||||
if (!empty($params['comm'])) {
|
||||
$account_type = 'Forum';
|
||||
}
|
||||
|
||||
$tags = [];
|
||||
if (!empty($params['tags'])) {
|
||||
$incoming_tags = explode(' ', $params['tags']);
|
||||
foreach ($incoming_tags as $term) {
|
||||
$term = strip_tags(trim($term));
|
||||
$term = substr($term, 0, 254);
|
||||
|
||||
$tags[] = $term;
|
||||
}
|
||||
|
||||
$tags = array_unique($tags);
|
||||
}
|
||||
|
||||
$filled_fields = intval(!empty($params['pdesc'])) * 4 + intval(!empty($params['tags'])) * 2 + intval(!empty($params['locality']) || !empty($params['region']) || !empty($params['country-name']));
|
||||
|
||||
$this->atlas->perform('INSERT INTO `profile` SET
|
||||
`id` = :profile_id,
|
||||
`server_id` = :server_id,
|
||||
`username` = :username,
|
||||
`name` = :name,
|
||||
`pdesc` = :pdesc,
|
||||
`locality` = :locality,
|
||||
`region` = :region,
|
||||
`country` = :country,
|
||||
`profile_url` = :profile_url,
|
||||
`dfrn_request` = :dfrn_request,
|
||||
`tags` = :tags,
|
||||
`addr` = :addr,
|
||||
`account_type` = :account_type,
|
||||
`filled_fields` = :filled_fields,
|
||||
`last_activity` = :last_activity,
|
||||
`available` = 1,
|
||||
`created` = NOW(),
|
||||
`updated` = NOW()
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`server_id` = :server_id,
|
||||
`username` = :username,
|
||||
`name` = :name,
|
||||
`pdesc` = :pdesc,
|
||||
`locality` = :locality,
|
||||
`region` = :region,
|
||||
`country` = :country,
|
||||
`profile_url` = :profile_url,
|
||||
`dfrn_request` = :dfrn_request,
|
||||
`photo` = :photo,
|
||||
`tags` = :tags,
|
||||
`addr` = :addr,
|
||||
`account_type` = :account_type,
|
||||
`filled_fields` = :filled_fields,
|
||||
`last_activity` = :last_activity,
|
||||
`available` = 1,
|
||||
`updated` = NOW()',
|
||||
[
|
||||
'profile_id' => $profile_id,
|
||||
'server_id' => $server['id'],
|
||||
'username' => $username,
|
||||
'name' => $params['fn'],
|
||||
'pdesc' => $params['pdesc'] ?? '',
|
||||
'locality' => $params['locality'] ?? '',
|
||||
'region' => $params['region'] ?? '',
|
||||
'country' => $params['country-name'] ?? '',
|
||||
'profile_url' => $profile_uri,
|
||||
'dfrn_request' => $params['dfrn-request'] ?? null,
|
||||
'photo' => $params['photo'],
|
||||
'tags' => implode(' ', $tags),
|
||||
'addr' => $addr,
|
||||
'account_type' => $account_type,
|
||||
'filled_fields' => $filled_fields,
|
||||
'last_activity' => $params['last-activity'] ?? null,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$profile_id) {
|
||||
$profile_id = $this->atlas->lastInsertId();
|
||||
}
|
||||
|
||||
if (!empty($params['tags'])) {
|
||||
$incoming_tags = explode(' ', $params['tags']);
|
||||
foreach ($incoming_tags as $term) {
|
||||
$term = strip_tags(trim($term));
|
||||
$term = substr($term, 0, 254);
|
||||
|
||||
if (strlen($term)) {
|
||||
$this->atlas->perform('INSERT IGNORE INTO `tag` (`profile_id`, `term`) VALUES (:profile_id, :term)', ['term' => $term, 'profile_id' => $profile_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$submit_photo_start = microtime(true);
|
||||
|
||||
$status = false;
|
||||
|
||||
if ($profile_id) {
|
||||
$img_str = \Friendica\Directory\Utils\Network::fetchURL($params['photo'], true);
|
||||
$img = new \Friendica\Directory\Utils\Photo($img_str);
|
||||
if ($img->getImage()) {
|
||||
$img->scaleImageSquare(80);
|
||||
|
||||
$this->atlas->perform('INSERT INTO `photo` SET
|
||||
`profile_id` = :profile_id,
|
||||
`data` = :data
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`data` = :data',
|
||||
[
|
||||
'profile_id' => $profile_id,
|
||||
'data' => $img->imageString()
|
||||
]
|
||||
);
|
||||
}
|
||||
$status = true;
|
||||
}
|
||||
|
||||
$submit_end = microtime(true);
|
||||
$photo_time = round(($submit_end - $submit_photo_start) * 1000);
|
||||
$time = round(($submit_end - $submit_start) * 1000);
|
||||
|
||||
//Record the scrape speed in a scrapes table.
|
||||
if ($server && $status) {
|
||||
$this->atlas->perform('INSERT INTO `site_scrape` SET
|
||||
`server_id` = :server_id,
|
||||
`request_time` = :request_time,
|
||||
`scrape_time` = :scrape_time,
|
||||
`photo_time` = :photo_time,
|
||||
`total_time` = :total_time',
|
||||
[
|
||||
'server_id' => $server['id'],
|
||||
'request_time' => $params['_timings']['fetch'],
|
||||
'scrape_time' => $params['_timings']['scrape'],
|
||||
'photo_time' => $photo_time,
|
||||
'total_time' => $time
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->logger->info('Profile poll successful');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function validateParams(array $params): int
|
||||
{
|
||||
$errors = 0;
|
||||
if (empty($params['key'])) {
|
||||
$errors++;
|
||||
}
|
||||
if (empty($params['dfrn-request'])) {
|
||||
$errors++;
|
||||
}
|
||||
if (empty($params['dfrn-confirm'])) {
|
||||
$errors++;
|
||||
}
|
||||
if (empty($params['dfrn-notify'])) {
|
||||
$errors++;
|
||||
}
|
||||
if (empty($params['dfrn-poll'])) {
|
||||
$errors++;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
||||
369
src/classes/Pollers/Server.php
Normal file
369
src/classes/Pollers/Server.php
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Pollers;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Server
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $atlas;
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\ProfilePollQueue
|
||||
*/
|
||||
private $profilePollQueueModel;
|
||||
/**
|
||||
* @var \Friendica\Directory\Models\Server
|
||||
*/
|
||||
private $serverModel;
|
||||
/**
|
||||
* @var \Psr\SimpleCache\CacheInterface
|
||||
*/
|
||||
private $simplecache;
|
||||
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $settings = [
|
||||
'probe_timeout' => 5
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
\Atlas\Pdo\Connection $atlas,
|
||||
\Friendica\Directory\Models\ProfilePollQueue $profilePollQueueModel,
|
||||
\Friendica\Directory\Models\Server $serverModel,
|
||||
\Psr\SimpleCache\CacheInterface $simplecache,
|
||||
\Psr\Log\LoggerInterface $logger,
|
||||
array $settings)
|
||||
{
|
||||
$this->atlas = $atlas;
|
||||
$this->profilePollQueueModel = $profilePollQueueModel;
|
||||
$this->serverModel = $serverModel;
|
||||
$this->simplecache = $simplecache;
|
||||
$this->logger = $logger;
|
||||
$this->settings = array_merge($this->settings, $settings);
|
||||
}
|
||||
|
||||
public function __invoke(string $polled_url): int
|
||||
{
|
||||
$this->logger->info('Poll server with URL: ' . $polled_url);
|
||||
|
||||
$host = parse_url($polled_url, PHP_URL_HOST);
|
||||
|
||||
if (!$host) {
|
||||
$this->logger->warning('Missing hostname in polled server URL: ' . $polled_url);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!\Friendica\Directory\Utils\Network::isPublicHost($host)) {
|
||||
$this->logger->warning('Private/reserved IP in polled server URL: ' . $polled_url);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$server = $this->serverModel->getByUrlAlias($polled_url);
|
||||
|
||||
if (
|
||||
$server
|
||||
&& substr($polled_url, 0, 7) == 'http://'
|
||||
&& substr($server['base_url'], 0, 8) == 'https://'
|
||||
) {
|
||||
$this->logger->info('Favoring the HTTPS version of server with URL: ' . $polled_url);
|
||||
return $server['id'];
|
||||
}
|
||||
|
||||
if ($server) {
|
||||
$this->atlas->perform('UPDATE `server` SET `available` = 0 WHERE `id` = :server_id', ['server_id' => $server['id']]);
|
||||
}
|
||||
|
||||
$probe_result = $this->getProbeResult($polled_url);
|
||||
|
||||
$parse_success = !empty($probe_result['data']);
|
||||
|
||||
if ($parse_success) {
|
||||
$base_url = $probe_result['data']['url'];
|
||||
|
||||
// Maybe we know the server under the canonical URL?
|
||||
if (!$server) {
|
||||
$server = $this->serverModel->getByUrlAlias($base_url);
|
||||
}
|
||||
|
||||
if (!$server) {
|
||||
$this->atlas->perform('INSERT INTO `server` SET
|
||||
`base_url` = :base_url,
|
||||
`first_noticed` = NOW(),
|
||||
`available` = 0,
|
||||
`health_score` = 50',
|
||||
['base_url' => $polled_url]
|
||||
);
|
||||
|
||||
$server = [
|
||||
'id' => $this->atlas->lastInsertId(),
|
||||
'base_url' => $base_url,
|
||||
'health_score' => 50
|
||||
];
|
||||
}
|
||||
|
||||
$this->serverModel->addAliasToServer($server['id'], $polled_url);
|
||||
$this->serverModel->addAliasToServer($server['id'], $base_url);
|
||||
|
||||
$avg_ping = $this->getAvgPing($base_url);
|
||||
if ($probe_result['time'] && $avg_ping) {
|
||||
$speed_score = max(1, $avg_ping > 10 ? $probe_result['time'] / $avg_ping : $probe_result['time'] / 50);
|
||||
} else {
|
||||
$speed_score = null;
|
||||
}
|
||||
|
||||
$this->atlas->perform('INSERT INTO `site_probe`
|
||||
SET `server_id` = :server_id,
|
||||
`request_time` = :request_time,
|
||||
`avg_ping` = :avg_ping,
|
||||
`speed_score` = :speed_score,
|
||||
`timestamp` = NOW()',
|
||||
[
|
||||
'server_id' => $server['id'],
|
||||
'request_time' => $probe_result['time'],
|
||||
'avg_ping' => $avg_ping,
|
||||
'speed_score' => $speed_score
|
||||
]
|
||||
);
|
||||
|
||||
if (isset($probe_result['data']['addons'])) {
|
||||
$addons = $probe_result['data']['addons'];
|
||||
} else {
|
||||
// Backward compatibility
|
||||
$addons = $probe_result['data']['plugins'];
|
||||
}
|
||||
|
||||
$this->atlas->perform(
|
||||
'UPDATE `server`
|
||||
SET `available` = 1,
|
||||
`last_seen` = NOW(),
|
||||
`base_url` = :base_url,
|
||||
`name` = :name,
|
||||
`version` = :version,
|
||||
`addons` = :addons,
|
||||
`reg_policy` = :reg_policy,
|
||||
`info` = :info,
|
||||
`admin_name` = :admin_name,
|
||||
`admin_profile` = :admin_profile,
|
||||
`noscrape_url` = :noscrape_url,
|
||||
`ssl_state` = :ssl_state
|
||||
WHERE `id` = :server_id',
|
||||
[
|
||||
'server_id' => $server['id'],
|
||||
'base_url' => strtolower($probe_result['data']['url']),
|
||||
'name' => $probe_result['data']['site_name'],
|
||||
'version' => $probe_result['data']['version'],
|
||||
'addons' => implode(',', $addons),
|
||||
'reg_policy' => $probe_result['data']['register_policy'],
|
||||
'info' => $probe_result['data']['info'],
|
||||
'admin_name' => $probe_result['data']['admin']['name'],
|
||||
'admin_profile' => $probe_result['data']['admin']['profile'],
|
||||
'noscrape_url' => $probe_result['data']['no_scrape_url'] ?? null,
|
||||
'ssl_state' => $probe_result['ssl_state']
|
||||
]
|
||||
);
|
||||
|
||||
//Add the admin to the directory
|
||||
$this->profilePollQueueModel->add($probe_result['data']['admin']['profile']);
|
||||
}
|
||||
|
||||
if ($server) {
|
||||
//Get the new health.
|
||||
$version = $parse_success ? $probe_result['data']['version'] : '';
|
||||
$health_score = $this->computeHealthScore($server['health_score'], $parse_success, $probe_result['time'], $version, $probe_result['ssl_state']);
|
||||
|
||||
$this->atlas->perform(
|
||||
'UPDATE `server` SET `health_score` = :health_score WHERE `id` = :server_id',
|
||||
['health_score' => $health_score, 'server_id' => $server['id']]
|
||||
);
|
||||
}
|
||||
|
||||
if ($parse_success) {
|
||||
$this->logger->info('Server poll successful');
|
||||
} else {
|
||||
$this->logger->info('Server poll unsuccessful');
|
||||
}
|
||||
|
||||
return $parse_success ? $server['id'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $base_url
|
||||
* @return float|null
|
||||
*/
|
||||
private function getAvgPing(string $base_url)
|
||||
{
|
||||
$net_ping = \Net_Ping::factory();
|
||||
$net_ping->setArgs(['count' => 5]);
|
||||
$ping_result = $net_ping->ping(parse_url($base_url, PHP_URL_HOST));
|
||||
|
||||
if (is_a($ping_result, 'Net_Ping_Result')) {
|
||||
$avg_ping = $ping_result->getAvg();
|
||||
} else {
|
||||
$avg_ping = null;
|
||||
}
|
||||
|
||||
unset($net_ping);
|
||||
|
||||
return $avg_ping;
|
||||
}
|
||||
|
||||
private function getProbeResult(string $base_url): array
|
||||
{
|
||||
//Prepare the CURL call.
|
||||
$handle = curl_init();
|
||||
$options = array(
|
||||
//Timeouts
|
||||
CURLOPT_TIMEOUT => max($this->settings['probe_timeout'], 1), //Minimum of 1 second timeout.
|
||||
CURLOPT_CONNECTTIMEOUT => 1,
|
||||
//Redirecting
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 8,
|
||||
//SSL
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
// CURLOPT_VERBOSE => true,
|
||||
// CURLOPT_CERTINFO => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
|
||||
//Basic request
|
||||
CURLOPT_USERAGENT => 'friendica-directory-probe-1.0',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_URL => $base_url . '/friendica/json'
|
||||
);
|
||||
curl_setopt_array($handle, $options);
|
||||
|
||||
//Probe the site.
|
||||
$probe_start = microtime(true);
|
||||
$probe_data = curl_exec($handle);
|
||||
$probe_end = microtime(true);
|
||||
|
||||
//Check for SSL problems.
|
||||
$curl_statuscode = curl_errno($handle);
|
||||
$sslcert_issues = in_array($curl_statuscode, array(
|
||||
60, //Could not authenticate certificate with known CA's
|
||||
83 //Issuer check failed
|
||||
));
|
||||
|
||||
//When it's the certificate that doesn't work.
|
||||
if ($sslcert_issues) {
|
||||
//Probe again, without strict SSL.
|
||||
$options[CURLOPT_SSL_VERIFYPEER] = false;
|
||||
|
||||
//Replace the handle.
|
||||
curl_close($handle);
|
||||
$handle = curl_init();
|
||||
curl_setopt_array($handle, $options);
|
||||
|
||||
//Probe.
|
||||
$probe_start = microtime(true);
|
||||
$probe_data = curl_exec($handle);
|
||||
$probe_end = microtime(true);
|
||||
|
||||
//Store new status.
|
||||
$curl_statuscode = curl_errno($handle);
|
||||
}
|
||||
|
||||
//Gather more meta.
|
||||
$time = round(($probe_end - $probe_start) * 1000);
|
||||
$curl_info = curl_getinfo($handle);
|
||||
|
||||
//Done with CURL now.
|
||||
curl_close($handle);
|
||||
|
||||
try {
|
||||
$data = json_decode($probe_data, true);
|
||||
} catch (\Exception $ex) {
|
||||
$data = false;
|
||||
}
|
||||
|
||||
$ssl_state = 0;
|
||||
if (parse_url($base_url, PHP_URL_SCHEME) == 'https') {
|
||||
if ($sslcert_issues) {
|
||||
$ssl_state = -1;
|
||||
} else {
|
||||
$ssl_state = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ['data' => $data, 'time' => $time, 'curl_info' => $curl_info, 'ssl_state' => $ssl_state];
|
||||
}
|
||||
|
||||
private function computeHealthScore(int $original_health, bool $probe_success, int $time = null, string $version = null, int $ssl_state = null): int
|
||||
{
|
||||
//Probe failed, costs you 30 points.
|
||||
if (!$probe_success) {
|
||||
return max($original_health - 30, -100);
|
||||
}
|
||||
|
||||
//A good probe gives you 10 points.
|
||||
$delta = 10;
|
||||
$max_health = 100;
|
||||
|
||||
//Speed scoring.
|
||||
if (intval($time) > 0) {
|
||||
//Penalty / bonus points.
|
||||
if ($time > 800) {
|
||||
$delta -= 10; //Bad speed.
|
||||
} elseif ($time > 400) {
|
||||
$delta -= 5; //Still not good.
|
||||
} elseif ($time > 250) {
|
||||
$delta += 0; //This is normal.
|
||||
} elseif ($time > 120) {
|
||||
$delta += 5; //Good speed.
|
||||
} else {
|
||||
$delta += 10; //Excellent speed.
|
||||
}
|
||||
|
||||
//Cap for bad speeds.
|
||||
if ($time > 800) {
|
||||
$max_health = 40;
|
||||
} elseif ($time > 400) {
|
||||
$max_health = 60;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ssl_state == 1) {
|
||||
$delta += 10;
|
||||
} elseif ($ssl_state == -1) {
|
||||
$delta -= 10;
|
||||
}
|
||||
|
||||
//Version check.
|
||||
if (!empty($version)) {
|
||||
$versionParts = explode('.', $version);
|
||||
|
||||
if (intval($versionParts[0]) == 3) {
|
||||
$max_health = 30; // Really old version
|
||||
} else {
|
||||
$stable_version = $this->simplecache->get('stable_version');
|
||||
if (!$stable_version) {
|
||||
$stable_version = trim(file_get_contents('https://git.friendi.ca/friendica/friendica/raw/branch/master/VERSION'));
|
||||
$this->simplecache->set('stable_version', $stable_version);
|
||||
}
|
||||
|
||||
$dev_version = $this->simplecache->get('dev_version');
|
||||
if (!$dev_version) {
|
||||
$dev_version = trim(file_get_contents('https://git.friendi.ca/friendica/friendica/raw/branch/develop/VERSION'));
|
||||
$this->simplecache->set('dev_version', $dev_version);
|
||||
}
|
||||
|
||||
if ($version == $dev_version) {
|
||||
$max_health = 95; //Develop can be unstable
|
||||
} elseif ($version !== $stable_version) {
|
||||
$delta = min($delta, 0) - 10; // Losing score as time passes if node isn't updated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max(min($max_health, $original_health + $delta), -100);
|
||||
}
|
||||
}
|
||||
21
src/classes/Routes/Console/BaseRoute.php
Normal file
21
src/classes/Routes/Console/BaseRoute.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
abstract class BaseRoute
|
||||
{
|
||||
/**
|
||||
* @var \Slim\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public abstract function __invoke(array $argv);
|
||||
}
|
||||
17
src/classes/Routes/Console/DirectoryAdd.php
Normal file
17
src/classes/Routes/Console/DirectoryAdd.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class DirectoryAdd extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\DirectoryAdd(
|
||||
$this->container->get('atlas'),
|
||||
$args
|
||||
));
|
||||
}
|
||||
}
|
||||
18
src/classes/Routes/Console/DirectoryPoll.php
Normal file
18
src/classes/Routes/Console/DirectoryPoll.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class DirectoryPoll extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\DirectoryPoll(
|
||||
$this->container->get('atlas'),
|
||||
$this->container->get('\Friendica\Directory\Pollers\Directory'),
|
||||
$args
|
||||
));
|
||||
}
|
||||
}
|
||||
16
src/classes/Routes/Console/Install.php
Normal file
16
src/classes/Routes/Console/Install.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Install extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\Install(
|
||||
$this->container->get('logger')
|
||||
));
|
||||
}
|
||||
}
|
||||
17
src/classes/Routes/Console/ProfileHide.php
Normal file
17
src/classes/Routes/Console/ProfileHide.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ProfileHide extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\ProfileHide(
|
||||
$this->container->get('atlas'),
|
||||
$args
|
||||
));
|
||||
}
|
||||
}
|
||||
17
src/classes/Routes/Console/ProfilePoll.php
Normal file
17
src/classes/Routes/Console/ProfilePoll.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ProfilePoll extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\ProfilePoll(
|
||||
$this->container->get('\Friendica\Directory\Pollers\Profile'),
|
||||
$args
|
||||
));
|
||||
}
|
||||
}
|
||||
18
src/classes/Routes/Console/ServerHide.php
Normal file
18
src/classes/Routes/Console/ServerHide.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ServerHide extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\ServerHide(
|
||||
$this->container->get('atlas'),
|
||||
$this->container->get('\Friendica\Directory\Models\Server'),
|
||||
$args
|
||||
));
|
||||
}
|
||||
}
|
||||
18
src/classes/Routes/Console/ServerPoll.php
Normal file
18
src/classes/Routes/Console/ServerPoll.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class ServerPoll extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\ServerPoll(
|
||||
$this->container->get('atlas'),
|
||||
$this->container->get('\Friendica\Directory\Pollers\Server'),
|
||||
$args
|
||||
));
|
||||
}
|
||||
}
|
||||
17
src/classes/Routes/Console/UpdateDb.php
Normal file
17
src/classes/Routes/Console/UpdateDb.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Console;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class UpdateDb extends BaseRoute
|
||||
{
|
||||
public function __invoke(array $args)
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Console\UpdateDb(
|
||||
$this->container->get('logger'),
|
||||
$this->container->get('migration')
|
||||
));
|
||||
}
|
||||
}
|
||||
21
src/classes/Routes/Http/BaseRoute.php
Normal file
21
src/classes/Routes/Http/BaseRoute.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Http;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
abstract class BaseRoute
|
||||
{
|
||||
/**
|
||||
* @var \Slim\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public abstract function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response;
|
||||
}
|
||||
20
src/classes/Routes/Http/Directory.php
Normal file
20
src/classes/Routes/Http/Directory.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Http;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Directory extends BaseRoute
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Web\Directory(
|
||||
$this->container->atlas,
|
||||
$this->container->get('\Friendica\Directory\Models\Profile'),
|
||||
$this->container->get('\Friendica\Directory\Views\Widget\AccountTypeTabs'),
|
||||
$this->container->renderer,
|
||||
$this->container->l10n)
|
||||
)->render($request, $response, $args);
|
||||
}
|
||||
}
|
||||
16
src/classes/Routes/Http/Photo.php
Normal file
16
src/classes/Routes/Http/Photo.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Http;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Photo extends BaseRoute
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Web\Photo(
|
||||
$this->container->atlas
|
||||
))->render($request, $response, $args);
|
||||
}
|
||||
}
|
||||
30
src/classes/Routes/Http/Search.php
Normal file
30
src/classes/Routes/Http/Search.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Http;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Search extends BaseRoute
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
if ($request->getAttribute('negotiation')->getMediaType() == 'application/json') {
|
||||
$controller = new \Friendica\Directory\Controllers\Api\Search(
|
||||
$this->container->atlas,
|
||||
$this->container->get('\Friendica\Directory\Models\Profile'),
|
||||
$this->container->l10n
|
||||
);
|
||||
} else {
|
||||
$controller = new \Friendica\Directory\Controllers\Web\Search(
|
||||
$this->container->atlas,
|
||||
$this->container->get('\Friendica\Directory\Models\Profile'),
|
||||
$this->container->get('\Friendica\Directory\Views\Widget\AccountTypeTabs'),
|
||||
$this->container->renderer,
|
||||
$this->container->l10n
|
||||
);
|
||||
}
|
||||
|
||||
return $controller->render($request, $response, $args);
|
||||
}
|
||||
}
|
||||
19
src/classes/Routes/Http/Servers.php
Normal file
19
src/classes/Routes/Http/Servers.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Http;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Servers extends BaseRoute
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Web\Servers(
|
||||
$this->container->atlas,
|
||||
$this->container->renderer,
|
||||
$this->container->l10n,
|
||||
$this->container->simplecache)
|
||||
)->render($request, $response);
|
||||
}
|
||||
}
|
||||
18
src/classes/Routes/Http/Submit.php
Normal file
18
src/classes/Routes/Http/Submit.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Http;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Submit extends BaseRoute
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Api\Submit(
|
||||
$this->container->atlas,
|
||||
$this->container->get('\Friendica\Directory\Models\ProfilePollQueue'),
|
||||
$this->container->logger
|
||||
))->execute($request, $response);
|
||||
}
|
||||
}
|
||||
17
src/classes/Routes/Http/SyncPull.php
Normal file
17
src/classes/Routes/Http/SyncPull.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Routes\Http;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class SyncPull extends BaseRoute
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args): \Slim\Http\Response
|
||||
{
|
||||
return (new \Friendica\Directory\Controllers\Api\SyncPull(
|
||||
$this->container->atlas,
|
||||
$this->container->logger
|
||||
))->execute($request, $response, $args);
|
||||
}
|
||||
}
|
||||
66
src/classes/Utils/Network.php
Normal file
66
src/classes/Utils/Network.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
namespace Friendica\Directory\Utils;
|
||||
|
||||
/**
|
||||
* Description of Network
|
||||
*
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Network
|
||||
{
|
||||
public static function fetchURL(string $url, bool $binary = false, int $timeout = 20): string
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
if (!$ch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, max(intval($timeout), 1)); //Minimum of 1 second timeout.
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 8);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
if ($binary) {
|
||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$s = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a hostname is public and non-reserved
|
||||
*
|
||||
* @param string $host
|
||||
* @return bool
|
||||
*/
|
||||
public static function isPublicHost(string $host): bool
|
||||
{
|
||||
if (!$host) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($host === 'localhost') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 2606
|
||||
if ($host === 'example.com' || $host === 'example.net' || $host === 'example.org') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter_var($host, FILTER_VALIDATE_IP) && !filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
183
src/classes/Utils/Photo.php
Normal file
183
src/classes/Utils/Photo.php
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Utils;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Photo
|
||||
{
|
||||
/**
|
||||
* @var resource|false
|
||||
*/
|
||||
private $image;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $width;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $height;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->image = @imagecreatefromstring($data);
|
||||
if ($this->image !== FALSE) {
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->image) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function getImage()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
public function scaleImage($max)
|
||||
{
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
|
||||
$dest_width = $dest_height = 0;
|
||||
|
||||
if ((!$width) || (!$height)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($width > $max && $height > $max) {
|
||||
if ($width > $height) {
|
||||
$dest_width = $max;
|
||||
$dest_height = intval(($height * $max) / $width);
|
||||
} else {
|
||||
$dest_width = intval(($width * $max) / $height);
|
||||
$dest_height = $max;
|
||||
}
|
||||
} else {
|
||||
if ($width > $max) {
|
||||
$dest_width = $max;
|
||||
$dest_height = intval(($height * $max) / $width);
|
||||
} else {
|
||||
if ($height > $max) {
|
||||
$dest_width = intval(($width * $max) / $height);
|
||||
$dest_height = $max;
|
||||
} else {
|
||||
$dest_width = $width;
|
||||
$dest_height = $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dest = imagecreatetruecolor($dest_width, $dest_height);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function scaleImageUp($min)
|
||||
{
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
|
||||
$dest_width = $dest_height = 0;
|
||||
|
||||
if ((!$width) || (!$height)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($width < $min && $height < $min) {
|
||||
if ($width > $height) {
|
||||
$dest_width = $min;
|
||||
$dest_height = intval(($height * $min) / $width);
|
||||
} else {
|
||||
$dest_width = intval(($width * $min) / $height);
|
||||
$dest_height = $min;
|
||||
}
|
||||
} else {
|
||||
if ($width < $min) {
|
||||
$dest_width = $min;
|
||||
$dest_height = intval(($height * $min) / $width);
|
||||
} else {
|
||||
if ($height < $min) {
|
||||
$dest_width = intval(($width * $min) / $height);
|
||||
$dest_height = $min;
|
||||
} else {
|
||||
$dest_width = $width;
|
||||
$dest_height = $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dest = imagecreatetruecolor($dest_width, $dest_height);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function scaleImageSquare($dim)
|
||||
{
|
||||
$dest = imagecreatetruecolor($dim, $dim);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function cropImage($max, $x, $y, $w, $h)
|
||||
{
|
||||
$dest = imagecreatetruecolor($max, $max);
|
||||
if ($this->image) {
|
||||
imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h);
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $dest;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
}
|
||||
|
||||
public function saveImage($path)
|
||||
{
|
||||
imagejpeg($this->image, $path, 100);
|
||||
}
|
||||
|
||||
public function imageString()
|
||||
{
|
||||
ob_start();
|
||||
imagejpeg($this->image, NULL, 100);
|
||||
$return = ob_get_clean();
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
203
src/classes/Utils/Scrape.php
Normal file
203
src/classes/Utils/Scrape.php
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Utils;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class Scrape
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return array|false
|
||||
*/
|
||||
public static function retrieveNoScrapeData(string $url)
|
||||
{
|
||||
$submit_noscrape_start = microtime(true);
|
||||
$data = Network::fetchURL($url);
|
||||
$submit_noscrape_request_end = microtime(true);
|
||||
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = json_decode($data, true);
|
||||
if (!$params || !count($params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['tags'])) {
|
||||
$params['tags'] = implode(' ', (array)$params['tags']);
|
||||
} else {
|
||||
$params['tags'] = '';
|
||||
}
|
||||
|
||||
$submit_noscrape_end = microtime(true);
|
||||
$params['_timings'] = array(
|
||||
'fetch' => round(($submit_noscrape_request_end - $submit_noscrape_start) * 1000),
|
||||
'scrape' => round(($submit_noscrape_end - $submit_noscrape_request_end) * 1000)
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public static function retrieveProfileData(string $url, int $max_nodes = 3500): array
|
||||
{
|
||||
|
||||
$minNodes = 100; //Lets do at least 100 nodes per type.
|
||||
$timeout = 10; //Timeout will affect batch processing.
|
||||
|
||||
//Try and cheat our way into faster profiles.
|
||||
if (strpos($url, 'tab=profile') === false) {
|
||||
$url .= (strpos($url, '?') > 0 ? '&' : '?') . 'tab=profile';
|
||||
}
|
||||
|
||||
$scrape_start = microtime(true);
|
||||
|
||||
$params = [];
|
||||
$html = Network::fetchURL($url, $timeout);
|
||||
|
||||
$scrape_fetch_end = microtime(true);
|
||||
|
||||
if (!$html) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$html5 = new \Masterminds\HTML5();
|
||||
$dom = $html5->loadHTML($html);
|
||||
|
||||
if (!$dom) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$items = $dom->getElementsByTagName('meta');
|
||||
|
||||
// get DFRN link elements
|
||||
$nodes_left = max(intval($max_nodes), $minNodes);
|
||||
$targets = array('hide', 'comm', 'tags');
|
||||
$targets_left = count($targets);
|
||||
foreach ($items as $item) {
|
||||
$meta_name = $item->getAttribute('name');
|
||||
if ($meta_name == 'dfrn-global-visibility') {
|
||||
$z = strtolower(trim($item->getAttribute('content')));
|
||||
if ($z != 'true') {
|
||||
$params['hide'] = 1;
|
||||
}
|
||||
if ($z === 'false') {
|
||||
$params['explicit-hide'] = 1;
|
||||
}
|
||||
$targets_left = self::popScrapeTarget($targets, 'hide');
|
||||
}
|
||||
if ($meta_name == 'friendika.community' || $meta_name == 'friendica.community') {
|
||||
$z = strtolower(trim($item->getAttribute('content')));
|
||||
if ($z == 'true') {
|
||||
$params['comm'] = 1;
|
||||
}
|
||||
$targets_left = self::popScrapeTarget($targets, 'comm');
|
||||
}
|
||||
if ($meta_name == 'keywords') {
|
||||
$z = str_replace(',', ' ', strtolower(trim($item->getAttribute('content'))));
|
||||
if (strlen($z)) {
|
||||
$params['tags'] = $z;
|
||||
}
|
||||
$targets_left = self::popScrapeTarget($targets, 'tags');
|
||||
}
|
||||
$nodes_left--;
|
||||
if ($nodes_left <= 0 || $targets_left <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$items = $dom->getElementsByTagName('link');
|
||||
|
||||
// get DFRN link elements
|
||||
|
||||
$nodes_left = max(intval($max_nodes), $minNodes);
|
||||
foreach ($items as $item) {
|
||||
$link_rel = $item->getAttribute('rel');
|
||||
if (substr($link_rel, 0, 5) == "dfrn-") {
|
||||
$params[$link_rel] = $item->getAttribute('href');
|
||||
}
|
||||
$nodes_left--;
|
||||
if ($nodes_left <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Pull out hCard profile elements
|
||||
|
||||
$nodes_left = max(intval($max_nodes), $minNodes);
|
||||
$items = $dom->getElementsByTagName('*');
|
||||
$targets = array('fn', 'pdesc', 'photo', 'key', 'locality', 'region', 'postal-code', 'country-name');
|
||||
$targets_left = count($targets);
|
||||
foreach ($items as $item) {
|
||||
if (self::attributeContains($item->getAttribute('class'), 'vcard')) {
|
||||
$level2 = $item->getElementsByTagName('*');
|
||||
foreach ($level2 as $vcard_element) {
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'fn')) {
|
||||
$params['fn'] = $vcard_element->textContent;
|
||||
$targets_left = self::popScrapeTarget($targets, 'fn');
|
||||
}
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'title')) {
|
||||
$params['pdesc'] = $vcard_element->textContent;
|
||||
$targets_left = self::popScrapeTarget($targets, 'pdesc');
|
||||
}
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'photo')) {
|
||||
$params['photo'] = $vcard_element->getAttribute('src');
|
||||
$targets_left = self::popScrapeTarget($targets, 'photo');
|
||||
}
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'key')) {
|
||||
$params['key'] = $vcard_element->textContent;
|
||||
$targets_left = self::popScrapeTarget($targets, 'key');
|
||||
}
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'locality')) {
|
||||
$params['locality'] = $vcard_element->textContent;
|
||||
$targets_left = self::popScrapeTarget($targets, 'locality');
|
||||
}
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'region')) {
|
||||
$params['region'] = $vcard_element->textContent;
|
||||
$targets_left = self::popScrapeTarget($targets, 'region');
|
||||
}
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'postal-code')) {
|
||||
$params['postal-code'] = $vcard_element->textContent;
|
||||
$targets_left = self::popScrapeTarget($targets, 'postal-code');
|
||||
}
|
||||
if (self::attributeContains($vcard_element->getAttribute('class'), 'country-name')) {
|
||||
$params['country-name'] = $vcard_element->textContent;
|
||||
$targets_left = self::popScrapeTarget($targets, 'country-name');
|
||||
}
|
||||
}
|
||||
}
|
||||
$nodes_left--;
|
||||
if ($nodes_left <= 0 || $targets_left <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$scrape_end = microtime(true);
|
||||
$fetch_time = round(($scrape_fetch_end - $scrape_start) * 1000);
|
||||
$scrape_time = round(($scrape_end - $scrape_fetch_end) * 1000);
|
||||
|
||||
$params['_timings'] = array(
|
||||
'fetch' => $fetch_time,
|
||||
'scrape' => $scrape_time
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
private static function attributeContains(string $attr, string $s): bool
|
||||
{
|
||||
$a = explode(' ', $attr);
|
||||
return count($a) && in_array($s, $a);
|
||||
}
|
||||
|
||||
private static function popScrapeTarget(array &$array, string $name): int
|
||||
{
|
||||
$at = array_search($name, $array);
|
||||
unset($array[$at]);
|
||||
return count($array);
|
||||
}
|
||||
|
||||
}
|
||||
63
src/classes/Views/PhpRenderer.php
Normal file
63
src/classes/Views/PhpRenderer.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Views;
|
||||
|
||||
/**
|
||||
* Zend-Escaper wrapper for Slim PHP Renderer
|
||||
*
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class PhpRenderer extends \Slim\Views\PhpRenderer
|
||||
{
|
||||
/**
|
||||
* @var \Zend\Escaper\Escaper
|
||||
*/
|
||||
private $escaper;
|
||||
/**
|
||||
* @var \Friendica\Directory\Content\L10n
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
public function __construct(
|
||||
\Zend\Escaper\Escaper $escaper,
|
||||
\Friendica\Directory\Content\L10n $l10n,
|
||||
string $templatePath = "",
|
||||
array $attributes = array()
|
||||
)
|
||||
{
|
||||
parent::__construct($templatePath, $attributes);
|
||||
|
||||
$this->escaper = $escaper;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function e(string $value): string
|
||||
{
|
||||
return $this->escapeHtml($value);
|
||||
}
|
||||
|
||||
public function escapeHtml(string $value): string
|
||||
{
|
||||
return $this->escaper->escapeHtml($value);
|
||||
}
|
||||
|
||||
public function escapeCss(string $value): string
|
||||
{
|
||||
return $this->escaper->escapeCss($value);
|
||||
}
|
||||
|
||||
public function escapeJs(string $value): string
|
||||
{
|
||||
return $this->escaper->escapeJs($value);
|
||||
}
|
||||
|
||||
public function escapeHtmlAttr(string $value): string
|
||||
{
|
||||
return $this->escaper->escapeHtmlAttr($value);
|
||||
}
|
||||
|
||||
public function escapeUrl(string $value): string
|
||||
{
|
||||
return $this->escaper->escapeUrl($value);
|
||||
}
|
||||
}
|
||||
61
src/classes/Views/Widget/AccountTypeTabs.php
Normal file
61
src/classes/Views/Widget/AccountTypeTabs.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Views\Widget;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class AccountTypeTabs
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $connection;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\PhpRenderer
|
||||
*/
|
||||
private $renderer;
|
||||
/**
|
||||
* @var \Slim\Router
|
||||
*/
|
||||
private $router;
|
||||
|
||||
public function __construct(\Atlas\Pdo\Connection $connection, \Friendica\Directory\Views\PhpRenderer $renderer, \Slim\Router $router)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->renderer = $renderer;
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
public function render(string $route_name, string $current_type = '', array $queryParams = []): string
|
||||
{
|
||||
$stmt = '
|
||||
SELECT DISTINCT(`account_type`) AS `account_type`
|
||||
FROM `profile`
|
||||
WHERE `available`
|
||||
AND NOT `hidden`';
|
||||
$account_types = $this->connection->fetchAll($stmt);
|
||||
|
||||
$tabs = [
|
||||
[
|
||||
'title' => 'All',
|
||||
'link' => $this->router->pathFor($route_name, [], $queryParams),
|
||||
'active' => empty($current_type)
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($account_types as $account_type) {
|
||||
$tabs[] = [
|
||||
'title' => $account_type['account_type'],
|
||||
'link' => $this->router->pathFor($route_name, ['account_type' => strtolower($account_type['account_type'])], $queryParams),
|
||||
'active' => strtolower($account_type['account_type']) == strtolower($current_type)
|
||||
];
|
||||
}
|
||||
|
||||
$vars = [
|
||||
'tabs' => $tabs,
|
||||
];
|
||||
|
||||
return $this->renderer->fetch('widget/accounttypetabs.phtml', $vars);
|
||||
}
|
||||
}
|
||||
44
src/classes/Views/Widget/PopularCountries.php
Normal file
44
src/classes/Views/Widget/PopularCountries.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Views\Widget;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class PopularCountries
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $connection;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\PhpRenderer
|
||||
*/
|
||||
private $renderer;
|
||||
|
||||
public function __construct(\Atlas\Pdo\Connection $connection, \Friendica\Directory\Views\PhpRenderer $renderer)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$stmt = '
|
||||
SELECT `country`, COUNT(`country`) AS `total`
|
||||
FROM `profile`
|
||||
WHERE `country` != ""
|
||||
AND `available`
|
||||
GROUP BY `country`
|
||||
ORDER BY COUNT(`country`) DESC
|
||||
LIMIT 20';
|
||||
$countries = $this->connection->fetchAll($stmt);
|
||||
|
||||
$vars = [
|
||||
'title' => 'Popular Countries',
|
||||
'countries' => $countries
|
||||
];
|
||||
|
||||
return $this->renderer->fetch('widget/popularcountries.phtml', $vars);
|
||||
}
|
||||
}
|
||||
37
src/classes/Views/Widget/PopularTags.php
Normal file
37
src/classes/Views/Widget/PopularTags.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Directory\Views\Widget;
|
||||
|
||||
/**
|
||||
* @author Hypolite Petovan <mrpetovan@gmail.com>
|
||||
*/
|
||||
class PopularTags
|
||||
{
|
||||
/**
|
||||
* @var \Atlas\Pdo\Connection
|
||||
*/
|
||||
private $connection;
|
||||
/**
|
||||
* @var \Friendica\Directory\Views\PhpRenderer
|
||||
*/
|
||||
private $renderer;
|
||||
|
||||
public function __construct(\Atlas\Pdo\Connection $connection, \Friendica\Directory\Views\PhpRenderer $renderer)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$stmt = 'SELECT `term`, COUNT(*) AS `total` FROM `tag` GROUP BY `term` ORDER BY COUNT(`term`) DESC LIMIT 20';
|
||||
$tags = $this->connection->fetchAll($stmt);
|
||||
|
||||
$vars = [
|
||||
'title' => 'Popular Tags',
|
||||
'tags' => $tags
|
||||
];
|
||||
|
||||
return $this->renderer->fetch('widget/populartags.phtml', $vars);
|
||||
}
|
||||
}
|
||||
140
src/dependencies.php
Normal file
140
src/dependencies.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
// DIC configuration
|
||||
|
||||
// l10n
|
||||
$container['l10n'] = function (ContainerInterface $c): Friendica\Directory\Content\L10n {
|
||||
$settings = $c->get('settings')['l10n'];
|
||||
return new Friendica\Directory\Content\L10n($settings['language'] ?: 'en', $settings['lang_path'] ?: '');
|
||||
};
|
||||
|
||||
// simple cache
|
||||
$container['simplecache'] = function (ContainerInterface $c): Sarahman\SimpleCache\FileSystemCache {
|
||||
$settings = $c->get('settings')['simplecache'];
|
||||
return new Sarahman\SimpleCache\FileSystemCache($settings['directory']);
|
||||
};
|
||||
|
||||
// zend escaper
|
||||
$container['escaper'] = function (ContainerInterface $c): Zend\Escaper\Escaper {
|
||||
$settings = $c->get('settings')['escaper'];
|
||||
return new Zend\Escaper\Escaper($settings['encoding']);
|
||||
};
|
||||
|
||||
// view renderer
|
||||
$container['renderer'] = function (ContainerInterface $c): Friendica\Directory\Views\PhpRenderer {
|
||||
$settings = $c->get('settings')['renderer'];
|
||||
return new Friendica\Directory\Views\PhpRenderer($c->get('escaper'), $c->get('l10n'), $settings['template_path']);
|
||||
};
|
||||
|
||||
// monolog
|
||||
$container['logger'] = function (ContainerInterface $c): Monolog\Logger {
|
||||
$settings = $c->get('settings')['logger'];
|
||||
$logger = new Monolog\Logger($settings['name']);
|
||||
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
|
||||
|
||||
switch ($settings['formatter']) {
|
||||
case 'console':
|
||||
$formatter = new Monolog\Formatter\LineFormatter("[%level_name%] %message% %context%\n");
|
||||
break;
|
||||
case 'logfile':
|
||||
default:
|
||||
$formatter = new Monolog\Formatter\LineFormatter("%datetime% - %level_name% %extra%: %message% %context%\n");
|
||||
break;
|
||||
}
|
||||
|
||||
$handler = new Monolog\Handler\StreamHandler($settings['path'], $settings['level']);
|
||||
$handler->setFormatter($formatter);
|
||||
$logger->pushHandler($handler);
|
||||
return $logger;
|
||||
};
|
||||
|
||||
// PDO wrapper
|
||||
$container['atlas'] = function (ContainerInterface $c): Atlas\Pdo\Connection {
|
||||
$args = $c->get('settings')['database'];
|
||||
|
||||
$dsn = "{$args['driver']}:dbname={$args['database']};host={$args['hostname']}";
|
||||
|
||||
$atlasConnection = Atlas\Pdo\Connection::new($dsn, $args['username'], $args['password']);
|
||||
|
||||
return $atlasConnection;
|
||||
};
|
||||
|
||||
// Database migration manager
|
||||
$container['migration'] = function (ContainerInterface $c): ByJG\DbMigration\Migration {
|
||||
$args = $c->get('settings')['database'];
|
||||
|
||||
$connectionUri = new ByJG\Util\Uri("{$args['driver']}://{$args['username']}:{$args['password']}@{$args['hostname']}/{$args['database']}");
|
||||
|
||||
$migration = new ByJG\DbMigration\Migration($connectionUri, __DIR__ . '/sql');
|
||||
|
||||
$migration->addCallbackProgress(function (string $action, int $currentVersion) use ($c): void {
|
||||
switch($action) {
|
||||
case 'reset': $c->get('logger')->notice('Resetting database schema'); break;
|
||||
case 'migrate': $c->get('logger')->notice('Migrating database schema to version ' . $currentVersion); break;
|
||||
default:
|
||||
$c->get('logger')->notice('Migration action: ' . $action . ' Current Version: ' . $currentVersion);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$migration->registerDatabase('mysql', ByJG\DbMigration\Database\MySqlDatabase::class);
|
||||
|
||||
return $migration;
|
||||
};
|
||||
|
||||
// Internal Dependency Injection
|
||||
|
||||
$container['\Friendica\Directory\Models\Profile'] = function (ContainerInterface $c): Friendica\Directory\Models\Profile {
|
||||
return new Friendica\Directory\Models\Profile($c->get('atlas'));
|
||||
};
|
||||
|
||||
$container['\Friendica\Directory\Models\ProfilePollQueue'] = function (ContainerInterface $c): Friendica\Directory\Models\ProfilePollQueue {
|
||||
return new Friendica\Directory\Models\ProfilePollQueue($c->get('atlas'));
|
||||
};
|
||||
|
||||
$container['\Friendica\Directory\Models\Server'] = function (ContainerInterface $c): Friendica\Directory\Models\Server {
|
||||
return new Friendica\Directory\Models\Server($c->get('atlas'));
|
||||
};
|
||||
|
||||
$container['\Friendica\Directory\Pollers\Directory'] = function (ContainerInterface $c): Friendica\Directory\Pollers\Directory {
|
||||
$settings = $c->get('settings')['poller'];
|
||||
return new Friendica\Directory\Pollers\Directory(
|
||||
$c->get('atlas'),
|
||||
$c->get('\Friendica\Directory\Models\ProfilePollQueue'),
|
||||
$c->get('logger'),
|
||||
$settings ?: []
|
||||
);
|
||||
};
|
||||
|
||||
$container['\Friendica\Directory\Pollers\Profile'] = function (ContainerInterface $c): Friendica\Directory\Pollers\Profile {
|
||||
$settings = $c->get('settings')['poller'];
|
||||
return new Friendica\Directory\Pollers\Profile(
|
||||
$c->get('atlas'),
|
||||
$c->get('\Friendica\Directory\Models\Server'),
|
||||
$c->get('\Friendica\Directory\Models\Profile'),
|
||||
$c->get('logger'),
|
||||
$settings ?: []
|
||||
);
|
||||
};
|
||||
|
||||
$container['\Friendica\Directory\Pollers\Server'] = function (ContainerInterface $c): Friendica\Directory\Pollers\Server {
|
||||
$settings = $c->get('settings')['poller'];
|
||||
return new Friendica\Directory\Pollers\Server(
|
||||
$c->get('atlas'),
|
||||
$c->get('\Friendica\Directory\Models\ProfilePollQueue'),
|
||||
$c->get('\Friendica\Directory\Models\Server'),
|
||||
$c->get('simplecache'),
|
||||
$c->get('logger'),
|
||||
$settings ?: []
|
||||
);
|
||||
};
|
||||
|
||||
$container['\Friendica\Directory\Views\Widget\AccountTypeTabs'] = function (ContainerInterface $c): Friendica\Directory\Views\Widget\AccountTypeTabs {
|
||||
return new Friendica\Directory\Views\Widget\AccountTypeTabs(
|
||||
$c->get('atlas'),
|
||||
$c->get('renderer'),
|
||||
$c->get('router')
|
||||
);
|
||||
};
|
||||
9
src/middleware.php
Normal file
9
src/middleware.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
// Application middleware
|
||||
|
||||
// e.g: $app->add(new \Slim\Csrf\Guard);
|
||||
// configure middleware
|
||||
|
||||
$app->add(new \Gofabian\Negotiation\NegotiationMiddleware([
|
||||
'accept' => ['text/html', 'application/json']
|
||||
]));
|
||||
68
src/routes.php
Normal file
68
src/routes.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
// Routes
|
||||
|
||||
/**
|
||||
* @var $app \Slim\App
|
||||
*/
|
||||
|
||||
$app->get('/servers', \Friendica\Directory\Routes\Http\Servers::class);
|
||||
|
||||
$app->get('/search[/{account_type}]', \Friendica\Directory\Routes\Http\Search::class)->setName('search');
|
||||
|
||||
$app->get('/submit', \Friendica\Directory\Routes\Http\Submit::class);
|
||||
|
||||
$app->get('/photo/{profile_id:[0-9]+}.jpg', \Friendica\Directory\Routes\Http\Photo::class)->setName('photo');
|
||||
|
||||
|
||||
$app->get('/tag/{term}', function (Request $request, Response $response, $args) {
|
||||
|
||||
$pager = new \Friendica\Directory\Content\Pager($this->l10n, $request, 20);
|
||||
|
||||
$term = $args['term'];
|
||||
|
||||
$sql_where = 'FROM `profile` p
|
||||
JOIN `tag` t ON p.`nurl` = t.`nurl`
|
||||
WHERE `term` = :term
|
||||
AND NOT `hidden`
|
||||
AND `available`';
|
||||
|
||||
$stmt = 'SELECT *
|
||||
' . $sql_where . '
|
||||
ORDER BY `filled_fields` DESC, `last_activity` DESC, `updated` DESC LIMIT :start, :limit';
|
||||
$profiles = $this->atlas->fetchAll($stmt, [
|
||||
'term' => $term,
|
||||
'start' => [$pager->getStart(), PDO::PARAM_INT],
|
||||
'limit' => [$pager->getItemsPerPage(), PDO::PARAM_INT]
|
||||
]);
|
||||
|
||||
$stmt = 'SELECT COUNT(*) AS `total`
|
||||
' . $sql_where;
|
||||
$count = $this->atlas->fetchValue($stmt, ['term' => $term]);
|
||||
|
||||
$vars = [
|
||||
'term' => $term,
|
||||
'count' => $count,
|
||||
'profiles' => $profiles,
|
||||
'pager' => $pager->renderFull($count),
|
||||
];
|
||||
|
||||
$content = $this->renderer->fetch('tag.phtml', $vars);
|
||||
|
||||
// Render index view
|
||||
return $this->renderer->render($response, 'layout.phtml', ['baseUrl' => $request->getUri()->getBaseUrl(), 'content' => $content]);
|
||||
});
|
||||
|
||||
$app->get('/sync/pull/all', \Friendica\Directory\Routes\Http\SyncPull::class);
|
||||
$app->get('/sync/pull/since/{since}', \Friendica\Directory\Routes\Http\SyncPull::class);
|
||||
|
||||
$app->get('/VERSION', function (Request $request, Response $response) {
|
||||
$response->getBody()->write(file_get_contents(__DIR__ . '/../VERSION'));
|
||||
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->get('/[{account_type}]', \Friendica\Directory\Routes\Http\Directory::class)->setName('directory');
|
||||
44
src/scss/friendica-directory.scss
Normal file
44
src/scss/friendica-directory.scss
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*!
|
||||
* Bootstrap v4.1.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2018 The Bootstrap Authors
|
||||
* Copyright 2011-2018 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
$blue: #1872A2;
|
||||
|
||||
@import "../public/assets/vendor/bootstrap/scss/functions";
|
||||
@import "../public/assets/vendor/bootstrap/scss/variables";
|
||||
@import "../public/assets/vendor/bootstrap/scss/mixins";
|
||||
@import "../public/assets/vendor/bootstrap/scss/root";
|
||||
@import "../public/assets/vendor/bootstrap/scss/reboot";
|
||||
@import "../public/assets/vendor/bootstrap/scss/type";
|
||||
@import "../public/assets/vendor/bootstrap/scss/images";
|
||||
@import "../public/assets/vendor/bootstrap/scss/code";
|
||||
@import "../public/assets/vendor/bootstrap/scss/grid";
|
||||
@import "../public/assets/vendor/bootstrap/scss/tables";
|
||||
@import "../public/assets/vendor/bootstrap/scss/forms";
|
||||
@import "../public/assets/vendor/bootstrap/scss/buttons";
|
||||
@import "../public/assets/vendor/bootstrap/scss/transitions";
|
||||
@import "../public/assets/vendor/bootstrap/scss/dropdown";
|
||||
@import "../public/assets/vendor/bootstrap/scss/button-group";
|
||||
@import "../public/assets/vendor/bootstrap/scss/input-group";
|
||||
@import "../public/assets/vendor/bootstrap/scss/custom-forms";
|
||||
@import "../public/assets/vendor/bootstrap/scss/nav";
|
||||
@import "../public/assets/vendor/bootstrap/scss/navbar";
|
||||
@import "../public/assets/vendor/bootstrap/scss/card";
|
||||
@import "../public/assets/vendor/bootstrap/scss/breadcrumb";
|
||||
@import "../public/assets/vendor/bootstrap/scss/pagination";
|
||||
@import "../public/assets/vendor/bootstrap/scss/badge";
|
||||
@import "../public/assets/vendor/bootstrap/scss/jumbotron";
|
||||
@import "../public/assets/vendor/bootstrap/scss/alert";
|
||||
@import "../public/assets/vendor/bootstrap/scss/progress";
|
||||
@import "../public/assets/vendor/bootstrap/scss/media";
|
||||
@import "../public/assets/vendor/bootstrap/scss/list-group";
|
||||
@import "../public/assets/vendor/bootstrap/scss/close";
|
||||
@import "../public/assets/vendor/bootstrap/scss/modal";
|
||||
@import "../public/assets/vendor/bootstrap/scss/tooltip";
|
||||
@import "../public/assets/vendor/bootstrap/scss/popover";
|
||||
@import "../public/assets/vendor/bootstrap/scss/carousel";
|
||||
@import "../public/assets/vendor/bootstrap/scss/utilities";
|
||||
@import "../public/assets/vendor/bootstrap/scss/print";
|
||||
298
src/sql/base.sql
Normal file
298
src/sql/base.sql
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET AUTOCOMMIT = 0;
|
||||
START TRANSACTION;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `directory_poll_queue`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `directory_poll_queue`;
|
||||
CREATE TABLE `directory_poll_queue` (
|
||||
`directory_url` varchar(190) NOT NULL,
|
||||
`added` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`last_polled` datetime DEFAULT NULL,
|
||||
`next_poll` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`retries_count` int(11) NOT NULL DEFAULT '0'
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `photo`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `photo`;
|
||||
CREATE TABLE `photo` (
|
||||
`profile_id` int(11) NOT NULL,
|
||||
`data` mediumblob NOT NULL
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `profile`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `profile`;
|
||||
CREATE TABLE `profile` (
|
||||
`id` int(11) NOT NULL,
|
||||
`name` char(255) NOT NULL,
|
||||
`server_id` int(11) NOT NULL,
|
||||
`username` varchar(100) NOT NULL,
|
||||
`addr` varchar(150) NOT NULL,
|
||||
`account_type` varchar(20) NOT NULL DEFAULT 'People',
|
||||
`pdesc` char(255) NOT NULL,
|
||||
`locality` char(255) NOT NULL,
|
||||
`region` char(255) NOT NULL,
|
||||
`country` char(255) NOT NULL,
|
||||
`profile_url` char(255) NOT NULL,
|
||||
`dfrn_request` varchar(250) DEFAULT NULL,
|
||||
`photo` char(255) NOT NULL,
|
||||
`tags` longtext NOT NULL,
|
||||
`filled_fields` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`last_activity` varchar(7) DEFAULT NULL,
|
||||
`available` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
ON UPDATE CURRENT_TIMESTAMP,
|
||||
`hidden` tinyint(4) NOT NULL DEFAULT '0'
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `profile_poll_queue`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `profile_poll_queue`;
|
||||
CREATE TABLE `profile_poll_queue` (
|
||||
`profile_url` varchar(190) NOT NULL,
|
||||
`added` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`last_polled` datetime DEFAULT NULL
|
||||
ON UPDATE CURRENT_TIMESTAMP,
|
||||
`next_poll` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`retries_count` int(11) NOT NULL DEFAULT '0'
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `server`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `server`;
|
||||
CREATE TABLE `server` (
|
||||
`id` int(10) UNSIGNED NOT NULL,
|
||||
`base_url` varchar(190) NOT NULL,
|
||||
`path` varchar(190) NOT NULL,
|
||||
`health_score` int(11) NOT NULL DEFAULT '0',
|
||||
`noscrape_url` varchar(255) DEFAULT NULL,
|
||||
`first_noticed` datetime NOT NULL,
|
||||
`last_seen` datetime DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`version` varchar(255) DEFAULT NULL,
|
||||
`addons` mediumtext,
|
||||
`reg_policy` char(32) DEFAULT NULL,
|
||||
`info` text,
|
||||
`admin_name` varchar(255) DEFAULT NULL,
|
||||
`admin_profile` varchar(255) DEFAULT NULL,
|
||||
`ssl_state` bit(1) DEFAULT NULL,
|
||||
`ssl_grade` varchar(3) DEFAULT NULL,
|
||||
`available` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`hidden` tinyint(1) NOT NULL DEFAULT '0'
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `server_alias`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `server_alias`;
|
||||
CREATE TABLE `server_alias` (
|
||||
`server_id` int(11) NOT NULL,
|
||||
`alias` varchar(190) NOT NULL,
|
||||
`timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
ON UPDATE CURRENT_TIMESTAMP
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `server_poll_queue`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `server_poll_queue`;
|
||||
CREATE TABLE `server_poll_queue` (
|
||||
`base_url` varchar(190) NOT NULL,
|
||||
`added` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`request_count` int(11) NOT NULL DEFAULT '1',
|
||||
`last_polled` datetime DEFAULT NULL
|
||||
ON UPDATE CURRENT_TIMESTAMP,
|
||||
`next_poll` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`retries_count` int(11) NOT NULL DEFAULT '0'
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `site_probe`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `site_probe`;
|
||||
CREATE TABLE `site_probe` (
|
||||
`server_id` int(10) UNSIGNED NOT NULL,
|
||||
`timestamp` datetime NOT NULL,
|
||||
`request_time` int(10) UNSIGNED NOT NULL,
|
||||
`avg_ping` int(11) DEFAULT NULL,
|
||||
`speed_score` int(11) DEFAULT NULL
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `site_scrape`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `site_scrape`;
|
||||
CREATE TABLE `site_scrape` (
|
||||
`id` int(10) UNSIGNED NOT NULL,
|
||||
`server_id` int(10) UNSIGNED NOT NULL,
|
||||
`performed` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`request_time` int(10) UNSIGNED NOT NULL,
|
||||
`scrape_time` int(10) UNSIGNED NOT NULL,
|
||||
`photo_time` int(10) UNSIGNED NOT NULL,
|
||||
`total_time` int(10) UNSIGNED NOT NULL
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `tag`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `tag`;
|
||||
CREATE TABLE `tag` (
|
||||
`profile_id` int(11) NOT NULL,
|
||||
`term` char(255) NOT NULL
|
||||
)
|
||||
ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
--
|
||||
-- Indexes for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexes for table `directory_poll_queue`
|
||||
--
|
||||
ALTER TABLE `directory_poll_queue`
|
||||
ADD PRIMARY KEY (`directory_url`);
|
||||
|
||||
--
|
||||
-- Indexes for table `photo`
|
||||
--
|
||||
ALTER TABLE `photo`
|
||||
ADD UNIQUE KEY `profile_id` (`profile_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `profile`
|
||||
--
|
||||
ALTER TABLE `profile`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `addr` (`addr`),
|
||||
ADD UNIQUE KEY `profile_url` (`profile_url`(190)),
|
||||
ADD KEY `profile_sorting` (`filled_fields`, `last_activity`, `updated`),
|
||||
ADD KEY `site_id` (`server_id`);
|
||||
ALTER TABLE `profile`
|
||||
ADD FULLTEXT KEY `profile-ft` (`name`, `pdesc`, `profile_url`, `locality`, `region`, `country`);
|
||||
|
||||
--
|
||||
-- Indexes for table `profile_poll_queue`
|
||||
--
|
||||
ALTER TABLE `profile_poll_queue`
|
||||
ADD PRIMARY KEY (`profile_url`);
|
||||
|
||||
--
|
||||
-- Indexes for table `server`
|
||||
--
|
||||
ALTER TABLE `server`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `base_url` (`base_url`) USING BTREE,
|
||||
ADD KEY `health_score` (`health_score`),
|
||||
ADD KEY `last_seen` (`last_seen`) USING BTREE;
|
||||
|
||||
--
|
||||
-- Indexes for table `server_alias`
|
||||
--
|
||||
ALTER TABLE `server_alias`
|
||||
ADD PRIMARY KEY (`alias`, `server_id`);
|
||||
|
||||
--
|
||||
-- Indexes for table `server_poll_queue`
|
||||
--
|
||||
ALTER TABLE `server_poll_queue`
|
||||
ADD PRIMARY KEY (`base_url`);
|
||||
|
||||
--
|
||||
-- Indexes for table `site_probe`
|
||||
--
|
||||
ALTER TABLE `site_probe`
|
||||
ADD PRIMARY KEY (`server_id`, `timestamp`);
|
||||
|
||||
--
|
||||
-- Indexes for table `site_scrape`
|
||||
--
|
||||
ALTER TABLE `site_scrape`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `performed` (`performed`) USING BTREE,
|
||||
ADD KEY `server_id` (`server_id`) USING BTREE;
|
||||
|
||||
--
|
||||
-- Indexes for table `tag`
|
||||
--
|
||||
ALTER TABLE `tag`
|
||||
ADD PRIMARY KEY (`profile_id`, `term`(190)) USING BTREE;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for dumped tables
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `profile`
|
||||
--
|
||||
ALTER TABLE `profile`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `server`
|
||||
--
|
||||
ALTER TABLE `server`
|
||||
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for table `site_scrape`
|
||||
--
|
||||
ALTER TABLE `site_scrape`
|
||||
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
|
||||
COMMIT;
|
||||
3
src/sql/migrations/down/0000.sql
Normal file
3
src/sql/migrations/down/0000.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE `server` DROP `language`;
|
||||
|
||||
ALTER TABLE `profile` DROP `language`;
|
||||
3
src/sql/migrations/up/0001.sql
Normal file
3
src/sql/migrations/up/0001.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE `server` ADD `language` VARCHAR(30) NULL AFTER `name`;
|
||||
|
||||
ALTER TABLE `profile` ADD `language` VARCHAR(30) NULL AFTER `account_type`;
|
||||
19
src/templates/directory.phtml
Normal file
19
src/templates/directory.phtml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!-- <div class="row">
|
||||
<h1><?php echo $this->e($title) ?></h1>
|
||||
</div>-->
|
||||
<div class="row">
|
||||
<div class="col-xl-9 col-lg-8">
|
||||
<?php echo $this->fetch('sub/profiles.phtml', [
|
||||
'page' => 'directory',
|
||||
'profiles' => $profiles,
|
||||
'accountTypeTabs' => $accountTypeTabs,
|
||||
'pager_full' => $pager_full,
|
||||
'pager_minimal' => $pager_minimal
|
||||
]) ?>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-4">
|
||||
<?php echo $popularTags ?>
|
||||
<?php echo $popularCountries ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
74
src/templates/layout.phtml
Normal file
74
src/templates/layout.phtml
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Friendica Directory</title>
|
||||
<base href="<?php echo $baseUrl ?>">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="assets/css/friendica-directory.min.css">
|
||||
<link rel="stylesheet" href="assets/vendor/fontawesome/web-fonts-with-css/css/solid.css">
|
||||
<link rel="stylesheet" href="assets/vendor/fontawesome/web-fonts-with-css/css/fontawesome.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light static-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="">
|
||||
<img src="assets/images/friendica-32.png" width="32" height="32" class="d-inline-block align-top" alt="">
|
||||
Friendica Directory
|
||||
</a>
|
||||
<?php if (empty($noNavSearch)): ?>
|
||||
<form class="form-inline my-2 my-lg-0 d-none d-md-flex" action="search">
|
||||
<div class="input-group">
|
||||
<label class="sr-only" for="header_search">Search terms</label>
|
||||
<input name="q" class="form-control" type="search" id="header_search" placeholder="Search..."
|
||||
aria-label="Search terms">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="submit">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
|
||||
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse pt-2" id="navbarResponsive">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<?php if (empty($noNavSearch)): ?>
|
||||
<li class="nav-item d-md-none">
|
||||
<form action="search">
|
||||
<div class="input-group">
|
||||
<label class="sr-only" for="nav_search">Search terms</label>
|
||||
<input name="q" class="form-control form-control-sm" type="search" id="nav_search"
|
||||
placeholder="Search..." aria-label="Search terms">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary btn-sm" type="submit">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href=""><i class="fa fa-address-card"></i> Directory
|
||||
<!--<span class="sr-only">(current)</span>-->
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="servers"><i class="fa fa-hotel"></i> Public Servers</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<div class="container pt-3">
|
||||
<?php echo $content ?>
|
||||
</div>
|
||||
<script type="text/javascript" src="assets/vendor/bootstrap.native/dist/bootstrap-native-v4.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
20
src/templates/search.phtml
Normal file
20
src/templates/search.phtml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!--<h1>Search</h1>-->
|
||||
<form action="search">
|
||||
<div class="input-group">
|
||||
<label class="sr-only" for="search_search">Search terms</label>
|
||||
<input name="q" class="form-control" type="search" id="search_search" placeholder="Search..."
|
||||
aria-label="Search terms" value="<?php echo $this->escapeHtmlAttr($query) ?>">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="submit">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2><?php echo $count ?> results for "<?php echo $this->e($query) ?>"</h2>
|
||||
<?php echo $this->fetch('sub/profiles.phtml', [
|
||||
'page' => 'search',
|
||||
'profiles' => $profiles,
|
||||
'accountTypeTabs' => $accountTypeTabs,
|
||||
'pager_full' => $pager_full,
|
||||
'pager_minimal' => $pager_minimal
|
||||
]) ?>
|
||||
14
src/templates/servers.phtml
Normal file
14
src/templates/servers.phtml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<h1><?php echo $this->e($title) ?></h1>
|
||||
<nav aria-label="Top servers pagination">
|
||||
<?php echo $this->fetch('sub/pager_full.phtml', $pager) ?>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<?php foreach ($servers as $server) : ?>
|
||||
<div class="col-xl-6">
|
||||
<?php echo $this->fetch('sub/server.phtml', ['server' => $server, 'stable_version' => $stable_version, 'dev_version' => $dev_version]) ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<nav aria-label="Bottom servers pagination">
|
||||
<?php echo $this->fetch('sub/pager_full.phtml', $pager) ?>
|
||||
</nav>
|
||||
41
src/templates/sub/pager_full.phtml
Normal file
41
src/templates/sub/pager_full.phtml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php if (!empty($prev) || !empty($next)): ?>
|
||||
<ul class="pagination justify-content-center">
|
||||
<?php if (!empty($first)): ?>
|
||||
<li class="page-item <?php echo $first['class'] ?>">
|
||||
<a class="page-link" href="<?php echo $first['url'] ?>" tabindex="-1">
|
||||
<span aria-hidden="true">«</span>
|
||||
<?php echo $first['text'] ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($prev)): ?>
|
||||
<li class="page-item <?php echo $prev['class'] ?>">
|
||||
<a class="page-link" href="<?php echo $prev['url'] ?>" tabindex="-1">
|
||||
<span aria-hidden="true">‹</span>
|
||||
<?php echo $prev['text'] ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($pages as $page): ?>
|
||||
<li class="page-item <?php echo $page['class'] ?>">
|
||||
<a class="page-link" href="<?php echo $page['url'] ?>" tabindex="-1"><?php echo $page['text'] ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
<?php if (!empty($next)): ?>
|
||||
<li class="page-item <?php echo $next['class'] ?>">
|
||||
<a class="page-link" href="<?php echo $next['url'] ?>" tabindex="-1">
|
||||
<?php echo $next['text'] ?>
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($last)): ?>
|
||||
<li class="page-item <?php echo $last['class'] ?>">
|
||||
<a class="page-link" href="<?php echo $last['url'] ?>" tabindex="-1">
|
||||
<?php echo $last['text'] ?>
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
20
src/templates/sub/pager_minimal.phtml
Normal file
20
src/templates/sub/pager_minimal.phtml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php if (!empty($prev) || !empty($next)): ?>
|
||||
<ul class="pagination justify-content-between">
|
||||
<?php if (!empty($prev)): ?>
|
||||
<li class="page-item <?php echo $prev['class'] ?>">
|
||||
<a class="page-link" href="<?php echo $prev['url'] ?>" tabindex="-1">
|
||||
<span aria-hidden="true">‹</span>
|
||||
<?php echo $prev['text'] ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($next)): ?>
|
||||
<li class="page-item <?php echo $next['class'] ?>">
|
||||
<a class="page-link" href="<?php echo $next['url'] ?>" tabindex="-1">
|
||||
<?php echo $next['text'] ?>
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
57
src/templates/sub/profile.phtml
Normal file
57
src/templates/sub/profile.phtml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
$parts = [];
|
||||
if (!empty($profile['locality'])) {
|
||||
$parts[] = $this->escapeHtml($profile['locality']) . ' <a href="search?field=locality&q=' . $this->escapeUrl($profile['locality']) . '"><span class="fa fa-filter" title="Search" aria-hidden="true"></span></a>';
|
||||
}
|
||||
if (!empty($profile['region'])
|
||||
&& strtolower($profile['locality']) != strtolower($profile['region'])) {
|
||||
$parts[] = $this->escapeHtml($profile['region']) . ' <a href="search?field=region&q=' . $this->escapeUrl($profile['region']) . '"><span class="fa fa-filter" title="Search" aria-hidden="true"></span></a>';
|
||||
}
|
||||
|
||||
if (!empty($profile['country'])) {
|
||||
$parts[] = $this->escapeHtml($profile['country']) . ' <a href="search?field=country&q=' . $this->escapeUrl($profile['country']) . '"><span class="fa fa-filter" title="Search" aria-hidden="true"></span></a>';
|
||||
}
|
||||
?>
|
||||
|
||||
<figure id="profile-<?php echo $this->escapeHtmlAttr($profile['id']) ?>" class="bg-light p-3 rounded">
|
||||
<div class="media">
|
||||
<a href="<?php echo $this->escapeHtmlAttr($profile['profile_url']) ?>"><img class="mr-3 rounded"
|
||||
src="photo/<?php echo $profile['id'] ?>.jpg"></a>
|
||||
<div class="media-body">
|
||||
<h5 class="name">
|
||||
<?php if ($profile['dfrn_request']): ?>
|
||||
<a href="<?php echo $profile['dfrn_request']; ?>" class="card-link btn btn-primary float-right"><i
|
||||
class="fa fa-external-link-alt"></i> Follow</a>
|
||||
<?php endif; ?>
|
||||
<?php echo $this->escapeHtml($profile['name']) ?>
|
||||
</h5>
|
||||
<p class="url"><a
|
||||
href="<?php echo $this->escapeHtmlAttr($profile['profile_url']) ?>"><?php echo $this->escapeHtml($profile['addr']) ?></a>
|
||||
</p>
|
||||
<p class="description d-none d-md-block"><?php echo $this->escapeHtml($profile['pdesc']) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="description d-md-none"><?php echo $this->escapeHtml($profile['pdesc']) ?></p>
|
||||
<div class="location">
|
||||
<?php if (count($parts)): ?>
|
||||
<i class="fa fa-globe"></i>
|
||||
<?php echo implode(', ', $parts); ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?php if ($profile['tags']): ?>
|
||||
<div class="tags">
|
||||
|
||||
<?php
|
||||
$tags = array_map('trim', explode(' ', $profile['tags']));
|
||||
|
||||
foreach ($tags as $tag):?>
|
||||
|
||||
<span class="badge"><?php echo $this->escapeHtml($tag) ?> <a
|
||||
href="/search?q=<?php echo $this->escapeUrl($tag) ?>"><i class="fa fa-tag"
|
||||
title="Search tag"></i></a></span>
|
||||
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</figure>
|
||||
18
src/templates/sub/profiles.phtml
Normal file
18
src/templates/sub/profiles.phtml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<nav aria-label="Account type tabs" class="mb-3">
|
||||
<?php echo $accountTypeTabs ?>
|
||||
</nav>
|
||||
<nav aria-label="Top <?php echo $page ?> pagination" class="d-none d-md-block">
|
||||
<?php echo $this->fetch('sub/pager_full.phtml', $pager_full) ?>
|
||||
</nav>
|
||||
<nav aria-label="Top <?php echo $page ?> pagination" class="d-md-none">
|
||||
<?php echo $this->fetch('sub/pager_minimal.phtml', $pager_minimal) ?>
|
||||
</nav>
|
||||
<?php foreach ($profiles as $profile) : ?>
|
||||
<?php echo $this->fetch('sub/profile.phtml', ['profile' => $profile]) ?>
|
||||
<?php endforeach; ?>
|
||||
<nav aria-label="Bottom <?php echo $page ?> pagination" class="d-none d-md-block">
|
||||
<?php echo $this->fetch('sub/pager_full.phtml', $pager_full) ?>
|
||||
</nav>
|
||||
<nav aria-label="Bottom <?php echo $page ?> pagination" class="d-md-none">
|
||||
<?php echo $this->fetch('sub/pager_minimal.phtml', $pager_minimal) ?>
|
||||
</nav>
|
||||
84
src/templates/sub/server.phtml
Normal file
84
src/templates/sub/server.phtml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
|
||||
if ($server['health_score'] <= 0) {
|
||||
$badge_class = 'badge-dark';
|
||||
} elseif ($server['health_score'] <= 50) {
|
||||
$badge_class = 'badge-danger';
|
||||
} elseif ($server['health_score'] <= 80) {
|
||||
$badge_class = 'badge-warning';
|
||||
} else {
|
||||
$badge_class = 'badge-success';
|
||||
}
|
||||
|
||||
if ($server['version'] == $stable_version) {
|
||||
$version_badge = '<span class="badge badge-success"><i class="fa fa-smile"></i> Stable Version</span>';
|
||||
} elseif ($server['version'] == $dev_version) {
|
||||
$version_badge = '<span class="badge badge-secondary"><i class="fa fa-poo"></i> Develop Version</span>';
|
||||
} else {
|
||||
$version_badge = '<span class="badge badge-warning"><i class="fa fa-frown"></i> Outdated Version</span>';
|
||||
}
|
||||
|
||||
$base_url = $server['base_url'];
|
||||
|
||||
$base_url_display = substr($base_url, strpos($base_url, '/') + 2);
|
||||
?>
|
||||
<div class="card mr-2 mb-2 bg-light" id="server-card-<?php echo $server['id'] ?>">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<?php echo $this->e($server['name']); ?>
|
||||
</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">
|
||||
<?php if ($server['ssl_state']): ?>
|
||||
<span class="badge badge-success"><i class="fa fa-lock"></i> HTTPS</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-secondary"><i class="fa fa-lock-open"></i> HTTP</span>
|
||||
<?php endif; ?>
|
||||
<a href="<?php echo $base_url; ?>"><?php echo $this->e($base_url_display); ?></a>
|
||||
</h6>
|
||||
<p class="card-text">
|
||||
<span class="badge <?php echo $badge_class ?>"><i
|
||||
class="fa fa-heartbeat"></i> <?php echo $server['health_score'] ?></span>
|
||||
<span class="badge badge-secondary"><i
|
||||
class="fa fa-user"></i> <?php echo $this->e($server['user_count'] ?: '~'); ?> Users</span>
|
||||
<?php echo $version_badge; ?>
|
||||
<?php if ($server['admin_profile'] && $server['admin_name']): ?>
|
||||
<a href="<?php echo $server['admin_profile']; ?>" class="badge badge-primary">
|
||||
<i class="fa fa-star"></i> Admin: <?php echo $this->e($server['admin_name']); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<?php if ($server['info']) : ?>
|
||||
<p class="card-text"><?php echo $this->e($server['info']); ?></p>
|
||||
<?php else: ?>
|
||||
<p class="card-text text-muted"><No description provided></p>
|
||||
<?php endif; ?>
|
||||
<a href="<?php echo $base_url; ?>" class="card-link btn btn-primary"><i class="fa fa-external-link-alt"></i>
|
||||
Visit server</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php /*
|
||||
<div class="site">
|
||||
<div class="site-supports">
|
||||
<em>Features</em>
|
||||
<?php foreach ($server['popular_supports'] as $key => $value): if (!$value) continue; ?>
|
||||
|
||||
<div class="supports <?php echo strtolower($key); ?>">
|
||||
<?php echo $key; ?><?php if ($key == 'HTTPS' && $server['ssl_grade'] != null): ?>, Grade: <?php echo $server['ssl_grade']; ?><?php endif ?> √
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
<?php if ($server['supports_more'] > 0): ?>
|
||||
|
||||
<?php
|
||||
$more = '';
|
||||
foreach ($server['less_popular_supports'] as $key => $value) {
|
||||
if (!$value)
|
||||
continue;
|
||||
$more .= $key . PHP_EOL;
|
||||
}
|
||||
?>
|
||||
<abbr class="more" title="<?php echo $more ?>">+<?php echo $server['supports_more']; ?> more</abbr>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
*/
|
||||
17
src/templates/tag.phtml
Normal file
17
src/templates/tag.phtml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<h1>Tag</h1>
|
||||
<div class="row">
|
||||
<h2><?php echo $count ?> results for "<?php echo $this->e($term) ?>"</h2>
|
||||
</div>
|
||||
<nav aria-label="Bottom search pagination">
|
||||
<?php echo $this->fetch('pager.phtml', $pager) ?>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<?php foreach ($profiles as $profile) : ?>
|
||||
<div class="col-xl-6">
|
||||
<?php echo $this->fetch('sub/profile.phtml', ['profile' => $profile]) ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<nav aria-label="Bottom search pagination">
|
||||
<?php echo $this->fetch('pager.phtml', $pager) ?>
|
||||
</nav>
|
||||
8
src/templates/widget/accounttypetabs.phtml
Normal file
8
src/templates/widget/accounttypetabs.phtml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<ul class="nav nav-tabs justify-content-center">
|
||||
<?php foreach ($tabs as $tab): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link<?php echo $tab['active'] ? ' active' : '' ?>"
|
||||
href="<?php echo $this->escapeHtmlAttr($tab['link']) ?>"><?php echo $this->e($tab['title']) ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
12
src/templates/widget/popularcountries.phtml
Normal file
12
src/templates/widget/popularcountries.phtml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div>
|
||||
<h3><?php echo $this->e($title) ?></h3>
|
||||
<ul>
|
||||
<?php foreach ($countries as $country): ?>
|
||||
<li>
|
||||
<a href="search?field=country&q=<?php echo $this->escapeUrl($country['country']) ?>">
|
||||
<?php echo $this->e($country['country']) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
12
src/templates/widget/populartags.phtml
Normal file
12
src/templates/widget/populartags.phtml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div>
|
||||
<h3><?php echo $this->e($title) ?></h3>
|
||||
<ul>
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<li>
|
||||
<a href="search?q=<?php echo $this->escapeUrl($tag['term']) ?>">
|
||||
<?php echo $this->e($tag['term']) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue