diff --git a/src/Core/System.php b/src/Core/System.php index feed85e21..67ee3a803 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -136,12 +136,13 @@ class System * and adds an application/json HTTP header to the output. * After finishing the process is getting killed. * - * @param mixed $x The input content. - * @param string $content_type Type of the input (Default: 'application/json'). + * @param mixed $x The input content. + * @param string $content_type Type of the input (Default: 'application/json'). + * @param integer $options JSON options */ - public static function jsonExit($x, $content_type = 'application/json') { + public static function jsonExit($x, $content_type = 'application/json', int $options = 0) { header("Content-type: $content_type"); - echo json_encode($x); + echo json_encode($x, $options); exit(); } diff --git a/src/Model/Nodeinfo.php b/src/Model/Nodeinfo.php index d2e168fa5..44181ac94 100644 --- a/src/Model/Nodeinfo.php +++ b/src/Model/Nodeinfo.php @@ -24,6 +24,7 @@ namespace Friendica\Model; use Friendica\Core\Addon; use Friendica\Database\DBA; use Friendica\DI; +use stdClass; /** * Model interaction for the nodeinfo @@ -55,6 +56,7 @@ class Nodeinfo $config->set('nodeinfo', 'total_users', $userStats['total_users']); $config->set('nodeinfo', 'active_users_halfyear', $userStats['active_users_halfyear']); $config->set('nodeinfo', 'active_users_monthly', $userStats['active_users_monthly']); + $config->set('nodeinfo', 'active_users_weekly', $userStats['active_users_weekly']); $logger->debug('user statistics', $userStats); @@ -69,4 +71,109 @@ class Nodeinfo } DBA::close($items); } + + /** + * Return the supported services + * + * @return Object with supported services + */ + public static function getUsage(bool $version2 = false) + { + $config = DI::config(); + + $usage = new stdClass(); + + if (!empty($config->get('system', 'nodeinfo'))) { + $usage->users = [ + 'total' => intval($config->get('nodeinfo', 'total_users')), + 'activeHalfyear' => intval($config->get('nodeinfo', 'active_users_halfyear')), + 'activeMonth' => intval($config->get('nodeinfo', 'active_users_monthly')) + ]; + $usage->localPosts = intval($config->get('nodeinfo', 'local_posts')); + $usage->localComments = intval($config->get('nodeinfo', 'local_comments')); + + if ($version2) { + $usage->users['activeWeek'] = intval($config->get('nodeinfo', 'active_users_weekly')); + } + } + + return $usage; + } + + /** + * Return the supported services + * + * @return array with supported services + */ + public static function getServices() + { + $services = [ + 'inbound' => [], + 'outbound' => [], + ]; + + if (Addon::isEnabled('blogger')) { + $services['outbound'][] = 'blogger'; + } + if (Addon::isEnabled('dwpost')) { + $services['outbound'][] = 'dreamwidth'; + } + if (Addon::isEnabled('statusnet')) { + $services['inbound'][] = 'gnusocial'; + $services['outbound'][] = 'gnusocial'; + } + if (Addon::isEnabled('ijpost')) { + $services['outbound'][] = 'insanejournal'; + } + if (Addon::isEnabled('libertree')) { + $services['outbound'][] = 'libertree'; + } + if (Addon::isEnabled('buffer')) { + $services['outbound'][] = 'linkedin'; + } + if (Addon::isEnabled('ljpost')) { + $services['outbound'][] = 'livejournal'; + } + if (Addon::isEnabled('buffer')) { + $services['outbound'][] = 'pinterest'; + } + if (Addon::isEnabled('posterous')) { + $services['outbound'][] = 'posterous'; + } + if (Addon::isEnabled('pumpio')) { + $services['inbound'][] = 'pumpio'; + $services['outbound'][] = 'pumpio'; + } + + $services['outbound'][] = 'smtp'; + + if (Addon::isEnabled('tumblr')) { + $services['outbound'][] = 'tumblr'; + } + if (Addon::isEnabled('twitter') || Addon::isEnabled('buffer')) { + $services['outbound'][] = 'twitter'; + } + if (Addon::isEnabled('wppost')) { + $services['outbound'][] = 'wordpress'; + } + + return $services; + } + + public static function getOrganization($config) + { + $organization = ['name' => null, 'contact' => null, 'account' => null]; + + if (!empty($config->get('config', 'admin_email'))) { + $adminList = explode(',', str_replace(' ', '', $config->get('config', 'admin_email'))); + $organization['contact'] = $adminList[0]; + $administrator = User::getByEmail($adminList[0], ['username', 'nickname']); + if (!empty($administrator)) { + $organization['name'] = $administrator['username']; + $organization['account'] = DI::baseUrl()->get() . '/profile/' . $administrator['nickname']; + } + } + + return $organization; + } } diff --git a/src/Model/User.php b/src/Model/User.php index 16dfb5122..b4ada344e 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -1272,6 +1272,7 @@ class User 'total_users' => 0, 'active_users_halfyear' => 0, 'active_users_monthly' => 0, + 'active_users_weekly' => 0, ]; $userStmt = DBA::select('owner-view', ['uid', 'login_date', 'last-item'], @@ -1284,6 +1285,7 @@ class User $halfyear = time() - (180 * 24 * 60 * 60); $month = time() - (30 * 24 * 60 * 60); + $week = time() - (7 * 24 * 60 * 60); while ($user = DBA::fetch($userStmt)) { $statistics['total_users']++; @@ -1297,6 +1299,11 @@ class User ) { $statistics['active_users_monthly']++; } + + if ((strtotime($user['login_date']) > $week) || (strtotime($user['last-item']) > $week) + ) { + $statistics['active_users_weekly']++; + } } DBA::close($userStmt); diff --git a/src/Module/NodeInfo.php b/src/Module/NodeInfo.php deleted file mode 100644 index 87321489f..000000000 --- a/src/Module/NodeInfo.php +++ /dev/null @@ -1,245 +0,0 @@ -. - * - */ - -namespace Friendica\Module; - -use Friendica\BaseModule; -use Friendica\Core\Addon; -use Friendica\DI; -use stdClass; - -/** - * Standardized way of exposing metadata about a server running one of the distributed social networks. - * @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md - */ -class NodeInfo extends BaseModule -{ - public static function rawContent(array $parameters = []) - { - if ($parameters['version'] == '1.0') { - self::printNodeInfo1(); - } elseif ($parameters['version'] == '2.0') { - self::printNodeInfo2(); - } else { - throw new \Friendica\Network\HTTPException\NotFoundException(); - } - } - - /** - * Return the supported services - * - * @return Object with supported services - */ - private static function getUsage() - { - $config = DI::config(); - - $usage = new stdClass(); - - if (!empty($config->get('system', 'nodeinfo'))) { - $usage->users = [ - 'total' => intval($config->get('nodeinfo', 'total_users')), - 'activeHalfyear' => intval($config->get('nodeinfo', 'active_users_halfyear')), - 'activeMonth' => intval($config->get('nodeinfo', 'active_users_monthly')) - ]; - $usage->localPosts = intval($config->get('nodeinfo', 'local_posts')); - $usage->localComments = intval($config->get('nodeinfo', 'local_comments')); - } - - return $usage; - } - - /** - * Return the supported services - * - * @return array with supported services - */ - private static function getServices() - { - $services = [ - 'inbound' => [], - 'outbound' => [], - ]; - - if (Addon::isEnabled('blogger')) { - $services['outbound'][] = 'blogger'; - } - if (Addon::isEnabled('dwpost')) { - $services['outbound'][] = 'dreamwidth'; - } - if (Addon::isEnabled('statusnet')) { - $services['inbound'][] = 'gnusocial'; - $services['outbound'][] = 'gnusocial'; - } - if (Addon::isEnabled('ijpost')) { - $services['outbound'][] = 'insanejournal'; - } - if (Addon::isEnabled('libertree')) { - $services['outbound'][] = 'libertree'; - } - if (Addon::isEnabled('buffer')) { - $services['outbound'][] = 'linkedin'; - } - if (Addon::isEnabled('ljpost')) { - $services['outbound'][] = 'livejournal'; - } - if (Addon::isEnabled('buffer')) { - $services['outbound'][] = 'pinterest'; - } - if (Addon::isEnabled('posterous')) { - $services['outbound'][] = 'posterous'; - } - if (Addon::isEnabled('pumpio')) { - $services['inbound'][] = 'pumpio'; - $services['outbound'][] = 'pumpio'; - } - - $services['outbound'][] = 'smtp'; - - if (Addon::isEnabled('tumblr')) { - $services['outbound'][] = 'tumblr'; - } - if (Addon::isEnabled('twitter') || Addon::isEnabled('buffer')) { - $services['outbound'][] = 'twitter'; - } - if (Addon::isEnabled('wppost')) { - $services['outbound'][] = 'wordpress'; - } - - return $services; - } - - /** - * Print the nodeinfo version 1 - */ - private static function printNodeInfo1() - { - $config = DI::config(); - - $nodeinfo = [ - 'version' => '1.0', - 'software' => [ - 'name' => 'friendica', - 'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION, - ], - 'protocols' => [ - 'inbound' => [ - 'friendica' - ], - 'outbound' => [ - 'friendica' - ], - ], - 'services' => [], - 'usage' => [], - 'openRegistrations' => intval($config->get('config', 'register_policy')) !== Register::CLOSED, - 'metadata' => [ - 'nodeName' => $config->get('config', 'sitename'), - ], - ]; - - if (!empty($config->get('system', 'diaspora_enabled'))) { - $nodeinfo['protocols']['inbound'][] = 'diaspora'; - $nodeinfo['protocols']['outbound'][] = 'diaspora'; - } - - if (empty($config->get('system', 'ostatus_disabled'))) { - $nodeinfo['protocols']['inbound'][] = 'gnusocial'; - $nodeinfo['protocols']['outbound'][] = 'gnusocial'; - } - - $nodeinfo['usage'] = self::getUsage(); - - $nodeinfo['services'] = self::getServices(); - - $nodeinfo['metadata']['protocols'] = $nodeinfo['protocols']; - $nodeinfo['metadata']['protocols']['outbound'][] = 'atom1.0'; - $nodeinfo['metadata']['protocols']['inbound'][] = 'atom1.0'; - $nodeinfo['metadata']['protocols']['inbound'][] = 'rss2.0'; - - $nodeinfo['metadata']['services'] = $nodeinfo['services']; - - if (Addon::isEnabled('twitter')) { - $nodeinfo['metadata']['services']['inbound'][] = 'twitter'; - } - - $nodeinfo['metadata']['explicitContent'] = $config->get('system', 'explicit_content', false) == true; - - header('Content-type: application/json; charset=utf-8'); - echo json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - exit; - } - - /** - * Print the nodeinfo version 2 - */ - private static function printNodeInfo2() - { - $config = DI::config(); - - $imap = (function_exists('imap_open') && !$config->get('system', 'imap_disabled') && !$config->get('system', 'dfrn_only')); - - $nodeinfo = [ - 'version' => '2.0', - 'software' => [ - 'name' => 'friendica', - 'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION, - ], - 'protocols' => ['dfrn', 'activitypub'], - 'services' => [], - 'usage' => [], - 'openRegistrations' => intval($config->get('config', 'register_policy')) !== Register::CLOSED, - 'metadata' => [ - 'nodeName' => $config->get('config', 'sitename'), - ], - ]; - - if (!empty($config->get('system', 'diaspora_enabled'))) { - $nodeinfo['protocols'][] = 'diaspora'; - } - - if (empty($config->get('system', 'ostatus_disabled'))) { - $nodeinfo['protocols'][] = 'ostatus'; - } - - $nodeinfo['usage'] = self::getUsage(); - - $nodeinfo['services'] = self::getServices(); - - if (Addon::isEnabled('twitter')) { - $nodeinfo['services']['inbound'][] = 'twitter'; - } - - $nodeinfo['services']['inbound'][] = 'atom1.0'; - $nodeinfo['services']['inbound'][] = 'rss2.0'; - $nodeinfo['services']['outbound'][] = 'atom1.0'; - - if ($imap) { - $nodeinfo['services']['inbound'][] = 'imap'; - } - - $nodeinfo['metadata']['explicitContent'] = $config->get('system', 'explicit_content', false) == true; - - header('Content-type: application/json; charset=utf-8'); - echo json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - exit; - } -} diff --git a/src/Module/NodeInfo110.php b/src/Module/NodeInfo110.php new file mode 100644 index 000000000..79e215e4f --- /dev/null +++ b/src/Module/NodeInfo110.php @@ -0,0 +1,91 @@ +. + * + */ + +namespace Friendica\Module; + +use Friendica\BaseModule; +use Friendica\Core\Addon; +use Friendica\Core\System; +use Friendica\DI; +use Friendica\Model\Nodeinfo; + +/** + * Version 1.0 of Nodeinfo, a standardized way of exposing metadata about a server running one of the distributed social networks. + * @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md + */ +class NodeInfo110 extends BaseModule +{ + public static function rawContent(array $parameters = []) + { + $config = DI::config(); + + $nodeinfo = [ + 'version' => '1.0', + 'software' => [ + 'name' => 'friendica', + 'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION, + ], + 'protocols' => [ + 'inbound' => [ + 'friendica' + ], + 'outbound' => [ + 'friendica' + ], + ], + 'services' => [], + 'usage' => [], + 'openRegistrations' => intval($config->get('config', 'register_policy')) !== Register::CLOSED, + 'metadata' => [ + 'nodeName' => $config->get('config', 'sitename'), + ], + ]; + + if (!empty($config->get('system', 'diaspora_enabled'))) { + $nodeinfo['protocols']['inbound'][] = 'diaspora'; + $nodeinfo['protocols']['outbound'][] = 'diaspora'; + } + + if (empty($config->get('system', 'ostatus_disabled'))) { + $nodeinfo['protocols']['inbound'][] = 'gnusocial'; + $nodeinfo['protocols']['outbound'][] = 'gnusocial'; + } + + $nodeinfo['usage'] = Nodeinfo::getUsage(); + + $nodeinfo['services'] = Nodeinfo::getServices(); + + $nodeinfo['metadata']['protocols'] = $nodeinfo['protocols']; + $nodeinfo['metadata']['protocols']['outbound'][] = 'atom1.0'; + $nodeinfo['metadata']['protocols']['inbound'][] = 'atom1.0'; + $nodeinfo['metadata']['protocols']['inbound'][] = 'rss2.0'; + + $nodeinfo['metadata']['services'] = $nodeinfo['services']; + + if (Addon::isEnabled('twitter')) { + $nodeinfo['metadata']['services']['inbound'][] = 'twitter'; + } + + $nodeinfo['metadata']['explicitContent'] = $config->get('system', 'explicit_content', false) == true; + + System::jsonExit($nodeinfo, 'application/json; charset=utf-8', JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } +} diff --git a/src/Module/NodeInfo120.php b/src/Module/NodeInfo120.php new file mode 100644 index 000000000..9d02a4b54 --- /dev/null +++ b/src/Module/NodeInfo120.php @@ -0,0 +1,83 @@ +. + * + */ + +namespace Friendica\Module; + +use Friendica\BaseModule; +use Friendica\Core\Addon; +use Friendica\Core\System; +use Friendica\DI; +use Friendica\Model\Nodeinfo; + +/** + * Version 2.0 of Nodeinfo, a standardized way of exposing metadata about a server running one of the distributed social networks. + * @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md + */ +class NodeInfo120 extends BaseModule +{ + public static function rawContent(array $parameters = []) + { + $config = DI::config(); + + $nodeinfo = [ + 'version' => '2.0', + 'software' => [ + 'name' => 'friendica', + 'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION, + ], + 'protocols' => ['dfrn', 'activitypub'], + 'services' => [], + 'usage' => [], + 'openRegistrations' => intval($config->get('config', 'register_policy')) !== Register::CLOSED, + 'metadata' => [ + 'nodeName' => $config->get('config', 'sitename'), + ], + ]; + + if (!empty($config->get('system', 'diaspora_enabled'))) { + $nodeinfo['protocols'][] = 'diaspora'; + } + + if (empty($config->get('system', 'ostatus_disabled'))) { + $nodeinfo['protocols'][] = 'ostatus'; + } + + $nodeinfo['usage'] = Nodeinfo::getUsage(); + + $nodeinfo['services'] = Nodeinfo::getServices(); + + if (Addon::isEnabled('twitter')) { + $nodeinfo['services']['inbound'][] = 'twitter'; + } + + $nodeinfo['services']['inbound'][] = 'atom1.0'; + $nodeinfo['services']['inbound'][] = 'rss2.0'; + $nodeinfo['services']['outbound'][] = 'atom1.0'; + + if (function_exists('imap_open') && !$config->get('system', 'imap_disabled') && !$config->get('system', 'dfrn_only')) { + $nodeinfo['services']['inbound'][] = 'imap'; + } + + $nodeinfo['metadata']['explicitContent'] = $config->get('system', 'explicit_content', false) == true; + + System::jsonExit($nodeinfo, 'application/json; charset=utf-8', JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } +} diff --git a/src/Module/NodeInfo210.php b/src/Module/NodeInfo210.php new file mode 100644 index 000000000..8512f6d07 --- /dev/null +++ b/src/Module/NodeInfo210.php @@ -0,0 +1,81 @@ +. + * + */ + +namespace Friendica\Module; + +use Friendica\BaseModule; +use Friendica\Core\Addon; +use Friendica\Core\System; +use Friendica\DI; +use Friendica\Model\Nodeinfo; + +/** + * Version 1.0 of Nodeinfo 2, a sStandardized way of exposing metadata about a server running one of the distributed social networks. + * @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md + */ +class NodeInfo210 extends BaseModule +{ + public static function rawContent(array $parameters = []) + { + $config = DI::config(); + + $nodeinfo = [ + 'version' => '1.0', + 'server' => [ + 'baseUrl' => DI::baseUrl()->get(), + 'name' => $config->get('config', 'sitename'), + 'software' => 'friendica', + 'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION, + ], + 'organization' => Nodeinfo::getOrganization($config), + 'protocols' => ['dfrn', 'activitypub'], + 'services' => [], + 'openRegistrations' => intval($config->get('config', 'register_policy')) !== Register::CLOSED, + 'usage' => [], + ]; + + if (!empty($config->get('system', 'diaspora_enabled'))) { + $nodeinfo['protocols'][] = 'diaspora'; + } + + if (empty($config->get('system', 'ostatus_disabled'))) { + $nodeinfo['protocols'][] = 'ostatus'; + } + + $nodeinfo['usage'] = Nodeinfo::getUsage(true); + + $nodeinfo['services'] = Nodeinfo::getServices(); + + if (Addon::isEnabled('twitter')) { + $nodeinfo['services']['inbound'][] = 'twitter'; + } + + $nodeinfo['services']['inbound'][] = 'atom1.0'; + $nodeinfo['services']['inbound'][] = 'rss2.0'; + $nodeinfo['services']['outbound'][] = 'atom1.0'; + + if (function_exists('imap_open') && !$config->get('system', 'imap_disabled') && !$config->get('system', 'dfrn_only')) { + $nodeinfo['services']['inbound'][] = 'imap'; + } + + System::jsonExit($nodeinfo, 'application/json; charset=utf-8', JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 074c1f571..ac7882693 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -37,6 +37,7 @@ return [ '/host-meta' => [Module\WellKnown\HostMeta::class, [R::GET]], '/nodeinfo' => [Module\WellKnown\NodeInfo::class, [R::GET]], '/webfinger' => [Module\Xrd::class, [R::GET]], + '/x-nodeinfo2' => [Module\NodeInfo210::class, [R::GET]], '/x-social-relay' => [Module\WellKnown\XSocialRelay::class, [R::GET]], ], @@ -199,7 +200,8 @@ return [ '/manifest' => [Module\Manifest::class, [R::GET]], '/modexp/{nick}' => [Module\PublicRSAKey::class, [R::GET]], '/newmember' => [Module\Welcome::class, [R::GET]], - '/nodeinfo/{version}' => [Module\NodeInfo::class, [R::GET]], + '/nodeinfo/1.0' => [Module\NodeInfo110::class, [R::GET]], + '/nodeinfo/2.0' => [Module\NodeInfo120::class, [R::GET]], '/nogroup' => [Module\Group::class, [R::GET]], '/noscrape' => [