3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Module\Admin;
24 use Friendica\Core\Renderer;
25 use Friendica\Database\DBA;
27 use Friendica\Model\GServer;
28 use Friendica\Module\BaseAdmin;
30 class Federation extends BaseAdmin
32 protected function content(array $request = []): string
36 // get counts on active federation systems this node is knowing
37 // We list the more common systems by name. The rest is counted as "other"
39 'friendica' => ['name' => 'Friendica', 'color' => '#ffc018'], // orange from the logo
40 'birdsitelive' => ['name' => 'BirdsiteLIVE', 'color' => '#1b6ec2'], // Color from the page
41 'bookwyrm' => ['name' => 'BookWyrm', 'color' => '#00d1b2'], // Color from the page
42 'diaspora' => ['name' => 'Diaspora', 'color' => '#a1a1a1'], // logo is black and white, makes a gray
43 'funkwhale' => ['name' => 'Funkwhale', 'color' => '#4082B4'], // From the homepage
44 'gnusocial' => ['name' => 'GNU Social/Statusnet', 'color' => '#a22430'], // dark red from the logo
45 'hubzilla' => ['name' => 'Hubzilla/Red Matrix', 'color' => '#43488a'], // blue from the logo
46 'lemmy' => ['name' => 'Lemmy', 'color' => '#00c853'], // Green from the page
47 'mastodon' => ['name' => 'Mastodon', 'color' => '#1a9df9'], // blue from the Mastodon logo
48 'misskey' => ['name' => 'Misskey', 'color' => '#ccfefd'], // Font color of the homepage
49 'mobilizon' => ['name' => 'Mobilizon', 'color' => '#ffd599'], // Background color of parts of the homepage
50 'nextcloud' => ['name' => 'Nextcloud', 'color' => '#1cafff'], // Logo color
51 'mistpark' => ['name' => 'Nomad projects (Mistpark, Osada, Roadhouse, Zap)', 'color' => '#348a4a'], // Green like the Mistpark green
52 'peertube' => ['name' => 'Peertube', 'color' => '#ffad5c'], // One of the logo colors
53 'pixelfed' => ['name' => 'Pixelfed', 'color' => '#11da47'], // One of the logo colors
54 'pleroma' => ['name' => 'Pleroma', 'color' => '#E46F0F'], // Orange from the text that is used on Pleroma instances
55 'plume' => ['name' => 'Plume', 'color' => '#7765e3'], // From the homepage
56 'relay' => ['name' => 'ActivityPub Relay', 'color' => '#888888'], // Grey like the second color of the ActivityPub logo
57 'socialhome' => ['name' => 'SocialHome', 'color' => '#52056b'], // lilac from the Django Image used at the Socialhome homepage
58 'wordpress' => ['name' => 'WordPress', 'color' => '#016087'], // Background color of the homepage
59 'writefreely' => ['name' => 'WriteFreely', 'color' => '#292929'], // Font color of the homepage
60 'other' => ['name' => DI::l10n()->t('Other'), 'color' => '#F1007E'], // ActivityPub main color
63 $platforms = array_keys($systems);
66 foreach ($platforms as $platform) {
67 $counts[$platform] = [];
73 $gservers = DBA::p("SELECT COUNT(*) AS `total`, SUM(`registered-users`) AS `users`, `platform`,
74 ANY_VALUE(`network`) AS `network`, MAX(`version`) AS `version`
75 FROM `gserver` WHERE NOT `failed` AND `detection-method` != ? GROUP BY `platform`", GServer::DETECT_MANUAL);
76 while ($gserver = DBA::fetch($gservers)) {
77 $total += $gserver['total'];
78 $users += $gserver['users'];
81 $versions = DBA::p("SELECT COUNT(*) AS `total`, `version` FROM `gserver`
82 WHERE NOT `failed` AND `platform` = ? AND `detection-method` != ?
83 GROUP BY `version` ORDER BY `version`", $gserver['platform'], GServer::DETECT_MANUAL);
84 while ($version = DBA::fetch($versions)) {
85 $version['version'] = str_replace(["\n", "\r", "\t"], " ", $version['version']);
87 if (in_array($gserver['platform'], ['Red Matrix', 'redmatrix', 'red'])) {
88 $version['version'] = 'Red ' . $version['version'];
89 } elseif (in_array($gserver['platform'], ['osada', 'mistpark', 'roadhouse', 'zap'])) {
90 $version['version'] = $gserver['platform'] . ' ' . $version['version'];
91 } elseif (in_array($gserver['platform'], ['activityrelay', 'pub-relay', 'selective-relay', 'aoderelay'])) {
92 $version['version'] = $gserver['platform'] . '-' . $version['version'];
95 $versionCounts[] = $version;
97 DBA::close($versions);
99 $platform = $gserver['platform'] = strtolower($gserver['platform']);
101 if ($platform == 'friendika') {
102 $platform = 'friendica';
103 } elseif (in_array($platform, ['red matrix', 'redmatrix', 'red'])) {
104 $platform = 'hubzilla';
105 } elseif (in_array($platform, ['mistpark', 'osada', 'roadhouse', 'zap'])) {
106 $platform = 'mistpark';
107 } elseif(stristr($platform, 'pleroma')) {
108 $platform = 'pleroma';
109 } elseif(stristr($platform, 'statusnet')) {
110 $platform = 'gnusocial';
111 } elseif(stristr($platform, 'wordpress')) {
112 $platform = 'wordpress';
113 } elseif (in_array($platform, ['activityrelay', 'pub-relay', 'selective-relay', 'aoderelay'])) {
115 } elseif (!in_array($platform, $platforms)) {
119 if ($platform != $gserver['platform']) {
120 if ($platform == 'other') {
121 $versionCounts = $counts[$platform][1] ?? [];
122 $versionCounts[] = ['version' => $gserver['platform'] ?: DI::l10n()->t('unknown'), 'total' => $gserver['total']];
123 $gserver['version'] = '';
125 $versionCounts = array_merge($versionCounts, $counts[$platform][1] ?? []);
128 $gserver['platform'] = $platform;
129 $gserver['total'] += $counts[$platform][0]['total'] ?? 0;
130 $gserver['users'] += $counts[$platform][0]['users'] ?? 0;
133 if ($platform == 'friendica') {
134 $versionCounts = self::reformaFriendicaVersions($versionCounts);
135 } elseif ($platform == 'pleroma') {
136 $versionCounts = self::reformaPleromaVersions($versionCounts);
137 } elseif ($platform == 'diaspora') {
138 $versionCounts = self::reformaDiasporaVersions($versionCounts);
139 } elseif ($platform == 'relay') {
140 $versionCounts = self::reformatRelayVersions($versionCounts);
141 } elseif (in_array($platform, ['funkwhale', 'mastodon', 'mobilizon', 'misskey'])) {
142 $versionCounts = self::removeVersionSuffixes($versionCounts);
145 if (!in_array($platform, ['other', 'relay', 'mistpark'])) {
146 $versionCounts = self::sortVersion($versionCounts);
148 ksort($versionCounts);
151 $gserver['platform'] = $systems[$platform]['name'];
153 $counts[$platform] = [$gserver, $versionCounts, str_replace([' ', '%'], '', $platform), $systems[$platform]['color']];
155 DBA::close($gserver);
158 $intro = DI::l10n()->t('This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of.');
160 // load the template, replace the macros and return the page content
161 $t = Renderer::getMarkupTemplate('admin/federation.tpl');
162 return Renderer::replaceMacros($t, [
163 '$title' => DI::l10n()->t('Administration'),
164 '$page' => DI::l10n()->t('Federation Statistics'),
166 '$counts' => $counts,
167 '$version' => FRIENDICA_VERSION,
168 '$legendtext' => DI::l10n()->t('Currently this node is aware of %d nodes with %d registered users from the following platforms:', $total, $users),
173 * early friendica versions have the format x.x.xxxx where xxxx is the
174 * DB version stamp; those should be operated out and versions be combined
176 * @param array $versionCounts list of version numbers
177 * @return array with cleaned version numbers
179 private static function reformaFriendicaVersions(array $versionCounts)
183 foreach ($versionCounts as $vv) {
184 $newVC = $vv['total'];
185 $newVV = $vv['version'];
186 $lastDot = strrpos($newVV, '.');
187 $firstDash = strpos($newVV, '-');
188 $len = strlen($newVV) - 1;
189 if (($lastDot == $len - 4) && (!strrpos($newVV, '-rc') == $len - 3) && (!$firstDash == $len - 1)) {
190 $newVV = substr($newVV, 0, $lastDot);
192 if (isset($newV[$newVV])) {
193 $newV[$newVV] += $newVC;
195 $newV[$newVV] = $newVC;
198 foreach ($newV as $key => $value) {
199 array_push($newVv, ['total' => $value, 'version' => $key]);
201 $versionCounts = $newVv;
203 return $versionCounts;
207 * in the DB the Diaspora versions have the format x.x.x.x-xx the last
208 * part (-xx) should be removed to clean up the versions from the "head
209 * commit" information and combined into a single entry for x.x.x.x
211 * @param array $versionCounts list of version numbers
212 * @return array with cleaned version numbers
214 private static function reformaDiasporaVersions(array $versionCounts)
218 foreach ($versionCounts as $vv) {
219 $newVC = $vv['total'];
220 $newVV = $vv['version'];
221 $posDash = strpos($newVV, '-');
223 $newVV = substr($newVV, 0, $posDash);
225 if (isset($newV[$newVV])) {
226 $newV[$newVV] += $newVC;
228 $newV[$newVV] = $newVC;
231 foreach ($newV as $key => $value) {
232 array_push($newVv, ['total' => $value, 'version' => $key]);
234 $versionCounts = $newVv;
236 return $versionCounts;
240 * Clean up Pleroma version numbers
242 * @param array $versionCounts list of version numbers
243 * @return array with cleaned version numbers
245 private static function reformaPleromaVersions(array $versionCounts)
248 foreach ($versionCounts as $key => $value) {
249 $version = $versionCounts[$key]['version'];
250 $parts = explode(' ', trim($version));
252 $part = array_pop($parts);
253 } while (!empty($parts) && ((strlen($part) >= 40) || (strlen($part) <= 3)));
254 // only take the x.x.x part of the version, not the "release" after the dash
255 if (!empty($part) && strpos($part, '-')) {
256 $part = explode('-', $part)[0];
259 if (empty($compacted[$part])) {
260 $compacted[$part] = $versionCounts[$key]['total'];
262 $compacted[$part] += $versionCounts[$key]['total'];
268 foreach ($compacted as $version => $pl_total) {
269 $versionCounts[] = ['version' => $version, 'total' => $pl_total];
272 return $versionCounts;
276 * Clean up version numbers
278 * @param array $versionCounts list of version numbers
279 * @return array with cleaned version numbers
281 private static function removeVersionSuffixes(array $versionCounts)
284 foreach ($versionCounts as $key => $value) {
285 $version = $versionCounts[$key]['version'];
287 foreach ([' ', '+', '-', '#', '_', '~'] as $delimiter) {
288 $parts = explode($delimiter, trim($version));
289 $version = array_shift($parts);
292 if (empty($compacted[$version])) {
293 $compacted[$version] = $versionCounts[$key]['total'];
295 $compacted[$version] += $versionCounts[$key]['total'];
300 foreach ($compacted as $version => $pl_total) {
301 $versionCounts[] = ['version' => $version, 'total' => $pl_total];
304 return $versionCounts;
308 * Clean up relay version numbers
310 * @param array $versionCounts list of version numbers
311 * @return array with cleaned version numbers
313 private static function reformatRelayVersions(array $versionCounts)
316 foreach ($versionCounts as $key => $value) {
317 $version = $versionCounts[$key]['version'];
319 $parts = explode(' ', trim($version));
320 $version = array_shift($parts);
322 if (empty($compacted[$version])) {
323 $compacted[$version] = $versionCounts[$key]['total'];
325 $compacted[$version] += $versionCounts[$key]['total'];
330 foreach ($compacted as $version => $pl_total) {
331 $versionCounts[] = ['version' => $version, 'total' => $pl_total];
334 return $versionCounts;
338 * Reformat, sort and compact version numbers
340 * @param array $versionCounts list of version numbers
341 * @return array with reformatted version numbers
343 private static function sortVersion(array $versionCounts)
346 // clean up version numbers
348 // some platforms do not provide version information, add a unkown there
349 // to the version string for the displayed list.
350 foreach ($versionCounts as $key => $value) {
351 if ($versionCounts[$key]['version'] == '') {
352 $versionCounts[$key] = ['total' => $versionCounts[$key]['total'], 'version' => DI::l10n()->t('unknown')];
356 // Assure that the versions are sorted correctly
359 foreach ($versionCounts as $vv) {
360 $version = trim(strip_tags($vv["version"]));
362 $versions[] = $version;
365 usort($versions, 'version_compare');
368 foreach ($versions as $version) {
369 $versionCounts[] = $v2[$version];
372 return $versionCounts;