2009-05-16 00:16:23 +01:00
|
|
|
<?php
|
|
|
|
/**
|
2009-08-25 23:29:56 +01:00
|
|
|
* StatusNet, the distributed open-source microblogging tool
|
2009-05-16 00:16:23 +01:00
|
|
|
*
|
|
|
|
* Plugin to enable Facebook Connect
|
|
|
|
*
|
|
|
|
* PHP version 5
|
|
|
|
*
|
|
|
|
* LICENCE: This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* @category Plugin
|
2009-08-25 23:29:56 +01:00
|
|
|
* @package StatusNet
|
|
|
|
* @author Zach Copley <zach@status.net>
|
|
|
|
* @copyright 2009 StatusNet, Inc.
|
2009-05-16 00:16:23 +01:00
|
|
|
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
2009-08-25 23:29:56 +01:00
|
|
|
* @link http://status.net/
|
2009-05-16 00:16:23 +01:00
|
|
|
*/
|
|
|
|
|
2009-08-26 15:41:36 +01:00
|
|
|
if (!defined('STATUSNET') && !defined('LACONICA')) {
|
2009-05-16 00:16:23 +01:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2009-05-21 06:43:11 +01:00
|
|
|
define("FACEBOOK_CONNECT_SERVICE", 3);
|
|
|
|
|
2009-05-16 00:16:23 +01:00
|
|
|
require_once INSTALLDIR . '/lib/facebookutil.php';
|
2009-05-20 07:46:11 +01:00
|
|
|
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php';
|
|
|
|
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
|
2009-05-21 06:43:11 +01:00
|
|
|
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
|
2009-05-20 07:46:11 +01:00
|
|
|
require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
|
2009-05-21 06:43:11 +01:00
|
|
|
require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
|
2009-06-04 22:33:04 +01:00
|
|
|
require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-05-16 00:16:23 +01:00
|
|
|
/**
|
|
|
|
* Plugin to enable Facebook Connect
|
|
|
|
*
|
|
|
|
* @category Plugin
|
2009-08-25 23:29:56 +01:00
|
|
|
* @package StatusNet
|
|
|
|
* @author Zach Copley <zach@status.net>
|
2009-05-16 00:16:23 +01:00
|
|
|
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
2009-08-25 23:29:56 +01:00
|
|
|
* @link http://status.net/
|
2009-05-16 00:16:23 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
class FBConnectPlugin extends Plugin
|
|
|
|
{
|
|
|
|
function __construct()
|
|
|
|
{
|
|
|
|
parent::__construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hook in new actions
|
|
|
|
function onRouterInitialized(&$m) {
|
2009-05-20 07:46:11 +01:00
|
|
|
$m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
|
|
|
|
$m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
|
2009-05-21 06:43:11 +01:00
|
|
|
$m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
|
2009-06-04 22:33:04 +01:00
|
|
|
$m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
|
2009-05-16 00:16:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add in xmlns:fb
|
|
|
|
function onStartShowHTML($action)
|
|
|
|
{
|
|
|
|
|
2009-07-01 23:47:21 +01:00
|
|
|
if ($this->reqFbScripts($action)) {
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-06-30 05:57:59 +01:00
|
|
|
// XXX: Horrible hack to make Safari, FF2, and Chrome work with
|
|
|
|
// Facebook Connect. These browser cannot use Facebook's
|
|
|
|
// DOM parsing routines unless the mime type of the page is
|
|
|
|
// text/html even though Facebook Connect uses XHTML. This is
|
|
|
|
// A bug in Facebook Connect, and this is a temporary solution
|
|
|
|
// until they fix their JavaScript libs.
|
|
|
|
header('Content-Type: text/html');
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-06-30 05:57:59 +01:00
|
|
|
$action->extraHeaders();
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-08-05 23:27:27 +01:00
|
|
|
$action->startXML('html');
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-06-30 05:57:59 +01:00
|
|
|
$language = $action->getLanguage();
|
|
|
|
|
|
|
|
$action->elementStart('html',
|
|
|
|
array('xmlns' => 'http://www.w3.org/1999/xhtml',
|
|
|
|
'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
|
|
|
|
'xml:lang' => $language,
|
|
|
|
'lang' => $language));
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2009-05-16 00:16:23 +01:00
|
|
|
}
|
|
|
|
|
2009-06-04 22:33:04 +01:00
|
|
|
// Note: this script needs to appear in the <body>
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-10-14 10:53:07 +01:00
|
|
|
function onEndShowScripts($action)
|
2009-06-04 22:33:04 +01:00
|
|
|
{
|
2009-07-01 23:47:21 +01:00
|
|
|
if ($this->reqFbScripts($action)) {
|
2009-06-30 05:57:59 +01:00
|
|
|
|
|
|
|
$apikey = common_config('facebook', 'apikey');
|
|
|
|
$plugin_path = common_path('plugins/FBConnect');
|
|
|
|
|
|
|
|
$login_url = common_local_url('FBConnectAuth');
|
|
|
|
$logout_url = common_local_url('logout');
|
|
|
|
|
|
|
|
// XXX: Facebook says we don't need this FB_RequireFeatures(),
|
|
|
|
// but we actually do, for IE and Safari. Gar.
|
|
|
|
|
|
|
|
$html = sprintf('<script type="text/javascript">
|
2009-08-09 03:55:31 +01:00
|
|
|
$(document).ready(function () {
|
2009-06-30 05:57:59 +01:00
|
|
|
FB_RequireFeatures(
|
|
|
|
["XFBML"],
|
|
|
|
function() {
|
2009-07-30 20:34:32 +01:00
|
|
|
FB.init("%s", "../xd_receiver.html");
|
2009-06-30 05:57:59 +01:00
|
|
|
}
|
2009-08-09 03:55:31 +01:00
|
|
|
); });
|
2009-06-30 05:57:59 +01:00
|
|
|
|
|
|
|
function goto_login() {
|
|
|
|
window.location = "%s";
|
|
|
|
}
|
|
|
|
|
|
|
|
function goto_logout() {
|
|
|
|
window.location = "%s";
|
|
|
|
}
|
|
|
|
</script>', $apikey,
|
|
|
|
$login_url, $logout_url);
|
|
|
|
|
|
|
|
$action->raw($html);
|
|
|
|
}
|
|
|
|
|
2009-06-04 22:33:04 +01:00
|
|
|
}
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-06-04 22:33:04 +01:00
|
|
|
// Note: this script needs to appear as close as possible to </body>
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-06-04 22:33:04 +01:00
|
|
|
function onEndShowFooter($action)
|
|
|
|
{
|
2009-07-01 23:47:21 +01:00
|
|
|
if ($this->reqFbScripts($action)) {
|
2009-08-06 01:15:00 +01:00
|
|
|
$action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php');
|
2009-06-30 05:57:59 +01:00
|
|
|
}
|
2009-05-16 00:16:23 +01:00
|
|
|
}
|
|
|
|
|
2009-08-25 23:29:56 +01:00
|
|
|
function onEndShowStatusNetStyles($action)
|
2009-05-22 02:22:07 +01:00
|
|
|
{
|
2009-06-30 05:57:59 +01:00
|
|
|
|
2009-07-01 23:47:21 +01:00
|
|
|
if ($this->reqFbScripts($action)) {
|
2009-08-06 01:16:34 +01:00
|
|
|
$action->cssLink('plugins/FBConnect/FBConnectPlugin.css');
|
2009-06-30 05:57:59 +01:00
|
|
|
}
|
2009-05-22 02:22:07 +01:00
|
|
|
}
|
|
|
|
|
2009-06-30 05:57:59 +01:00
|
|
|
/**
|
|
|
|
* Does the Action we're plugged into require the FB Scripts? We only
|
|
|
|
* want to output FB namespace, scripts, CSS, etc. on the pages that
|
|
|
|
* really need them.
|
|
|
|
*
|
|
|
|
* @param Action the action in question
|
|
|
|
*
|
|
|
|
* @return boolean true
|
|
|
|
*/
|
|
|
|
|
2009-07-01 23:47:21 +01:00
|
|
|
function reqFbScripts($action) {
|
2009-06-30 05:57:59 +01:00
|
|
|
|
|
|
|
// If you're logged in w/FB Connect, you always need the FB stuff
|
|
|
|
|
|
|
|
$fbuid = $this->loggedIn();
|
|
|
|
|
|
|
|
if (!empty($fbuid)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// List of actions that require FB stuff
|
|
|
|
|
|
|
|
$needy = array('FBConnectLoginAction',
|
|
|
|
'FBConnectauthAction',
|
|
|
|
'FBConnectSettingsAction');
|
|
|
|
|
|
|
|
if (in_array(get_class($action), $needy)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the user currently logged in with FB Connect?
|
|
|
|
*
|
|
|
|
* @return mixed $fbuid the Facebook ID of the logged in user, or null
|
|
|
|
*/
|
|
|
|
|
2009-06-30 01:49:59 +01:00
|
|
|
function loggedIn()
|
2009-05-16 00:16:23 +01:00
|
|
|
{
|
|
|
|
$user = common_current_user();
|
|
|
|
|
2009-06-30 01:49:59 +01:00
|
|
|
if (!empty($user)) {
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-06-04 22:33:04 +01:00
|
|
|
$flink = Foreign_link::getByUserId($user->id,
|
|
|
|
FACEBOOK_CONNECT_SERVICE);
|
2009-05-22 01:54:22 +01:00
|
|
|
$fbuid = 0;
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-06-30 01:49:59 +01:00
|
|
|
if (!empty($flink)) {
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-05-22 01:54:22 +01:00
|
|
|
try {
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-05-22 01:54:22 +01:00
|
|
|
$facebook = getFacebook();
|
2009-07-30 20:34:32 +01:00
|
|
|
$fbuid = $facebook->get_loggedin_user();
|
2009-05-22 01:54:22 +01:00
|
|
|
|
|
|
|
} catch (Exception $e) {
|
2009-08-19 00:41:24 +01:00
|
|
|
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
2009-07-16 01:47:48 +01:00
|
|
|
'Problem getting Facebook user: ' .
|
2009-05-22 01:54:22 +01:00
|
|
|
$e->getMessage());
|
|
|
|
}
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-05-22 01:54:22 +01:00
|
|
|
if ($fbuid > 0) {
|
2009-06-30 01:49:59 +01:00
|
|
|
return $fbuid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function onStartPrimaryNav($action)
|
|
|
|
{
|
|
|
|
|
|
|
|
$user = common_current_user();
|
2009-10-09 04:50:04 +01:00
|
|
|
$connect = 'FBConnectSettings';
|
|
|
|
if (common_config('xmpp', 'enabled')) {
|
|
|
|
$connect = 'imsettings';
|
|
|
|
} else if (common_config('sms', 'enabled')) {
|
|
|
|
$connect = 'smssettings';
|
|
|
|
} else if (common_config('twitter', 'enabled')) {
|
|
|
|
$connect = 'twittersettings';
|
|
|
|
}
|
2009-05-22 01:54:22 +01:00
|
|
|
|
2009-06-30 01:49:59 +01:00
|
|
|
if (!empty($user)) {
|
2009-05-22 01:54:22 +01:00
|
|
|
|
2009-06-30 01:49:59 +01:00
|
|
|
$fbuid = $this->loggedIn();
|
2009-05-22 01:54:22 +01:00
|
|
|
|
2009-06-30 01:49:59 +01:00
|
|
|
if (!empty($fbuid)) {
|
|
|
|
|
2009-07-01 23:47:21 +01:00
|
|
|
/* Default FB silhouette pic for FB users who haven't
|
|
|
|
uploaded a profile pic yet. */
|
|
|
|
|
|
|
|
$silhouetteUrl =
|
|
|
|
'http://static.ak.fbcdn.net/pics/q_silhouette.gif';
|
|
|
|
|
|
|
|
$url = $this->getProfilePicURL($fbuid);
|
|
|
|
|
2009-06-30 01:49:59 +01:00
|
|
|
$action->elementStart('li', array('id' => 'nav_fb'));
|
|
|
|
|
2009-07-01 23:47:21 +01:00
|
|
|
$action->element('img', array('id' => 'fbc_profile-pic',
|
|
|
|
'src' => (!empty($url)) ? $url : $silhouetteUrl,
|
|
|
|
'alt' => 'Facebook Connect User',
|
|
|
|
'width' => '16'), '');
|
|
|
|
|
|
|
|
$iconurl = common_path('plugins/FBConnect/fbfavicon.ico');
|
|
|
|
$action->element('img', array('id' => 'fb_favicon',
|
|
|
|
'src' => $iconurl));
|
2009-06-30 01:49:59 +01:00
|
|
|
|
|
|
|
$action->elementEnd('li');
|
2009-05-21 06:43:11 +01:00
|
|
|
|
|
|
|
}
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-05-28 01:08:00 +01:00
|
|
|
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
|
|
|
|
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
|
|
|
|
$action->menuItem(common_local_url('profilesettings'),
|
|
|
|
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
|
2009-10-09 04:50:04 +01:00
|
|
|
$action->menuItem(common_local_url($connect),
|
|
|
|
_('Connect'), _('Connect to services'), false, 'nav_connect');
|
2009-06-14 09:17:22 +01:00
|
|
|
if (common_config('invite', 'enabled')) {
|
|
|
|
$action->menuItem(common_local_url('invite'),
|
|
|
|
_('Invite'),
|
|
|
|
sprintf(_('Invite friends and colleagues to join you on %s'),
|
|
|
|
common_config('site', 'name')),
|
|
|
|
false, 'nav_invitecontact');
|
|
|
|
}
|
2009-05-22 01:54:22 +01:00
|
|
|
|
2009-05-28 01:08:00 +01:00
|
|
|
// Need to override the Logout link to make it do FB stuff
|
2009-06-30 01:49:59 +01:00
|
|
|
if (!empty($fbuid)) {
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-05-22 01:54:22 +01:00
|
|
|
$logout_url = common_local_url('logout');
|
|
|
|
$title = _('Logout from the site');
|
|
|
|
$text = _('Logout');
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-07-16 01:47:48 +01:00
|
|
|
$html = sprintf('<li id="nav_logout"><a href="#" title="%s" ' .
|
|
|
|
'onclick="FB.Connect.logoutAndRedirect(\'%s\');">%s</a></li>',
|
|
|
|
$title, $logout_url, $text);
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-05-22 01:54:22 +01:00
|
|
|
$action->raw($html);
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-05-22 01:54:22 +01:00
|
|
|
} else {
|
|
|
|
$action->menuItem(common_local_url('logout'),
|
|
|
|
_('Logout'), _('Logout from the site'), false, 'nav_logout');
|
|
|
|
}
|
2009-05-16 00:16:23 +01:00
|
|
|
}
|
|
|
|
else {
|
2009-10-09 04:50:04 +01:00
|
|
|
if (!common_config('site', 'openidonly')) {
|
|
|
|
if (!common_config('site', 'closed')) {
|
|
|
|
$action->menuItem(common_local_url('register'),
|
|
|
|
_('Register'), _('Create an account'), false, 'nav_register');
|
|
|
|
}
|
|
|
|
$action->menuItem(common_local_url('login'),
|
|
|
|
_('Login'), _('Login to the site'), false, 'nav_login');
|
|
|
|
} else {
|
|
|
|
$this->menuItem(common_local_url('openidlogin'),
|
|
|
|
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
|
2009-05-16 00:16:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$action->menuItem(common_local_url('doc', array('title' => 'help')),
|
2009-05-22 01:54:22 +01:00
|
|
|
_('Help'), _('Help me!'), false, 'nav_help');
|
2009-10-09 04:52:03 +01:00
|
|
|
if ($user || !common_config('site', 'private')) {
|
|
|
|
$action->menuItem(common_local_url('peoplesearch'),
|
|
|
|
_('Search'), _('Search for people or text'), false, 'nav_search');
|
|
|
|
}
|
2009-05-16 00:16:23 +01:00
|
|
|
|
2009-10-09 19:06:08 +01:00
|
|
|
// We are replacing the primary nav entirely; give other
|
|
|
|
// plugins a chance to handle it here.
|
|
|
|
|
|
|
|
Event::handle('EndPrimaryNav', array($action));
|
|
|
|
|
2009-05-16 00:16:23 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-05-20 07:46:11 +01:00
|
|
|
function onStartShowLocalNavBlock($action)
|
|
|
|
{
|
2009-07-01 23:47:21 +01:00
|
|
|
$action_name = get_class($action);
|
2009-05-21 06:43:11 +01:00
|
|
|
|
|
|
|
$login_actions = array('LoginAction', 'RegisterAction',
|
2009-05-22 01:54:22 +01:00
|
|
|
'OpenidloginAction', 'FBConnectLoginAction');
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-05-20 07:46:11 +01:00
|
|
|
if (in_array($action_name, $login_actions)) {
|
|
|
|
$nav = new FBCLoginGroupNav($action);
|
|
|
|
$nav->show();
|
|
|
|
return false;
|
|
|
|
}
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-06-30 19:55:34 +01:00
|
|
|
$connect_actions = array('SmssettingsAction', 'ImsettingsAction',
|
2009-05-21 06:43:11 +01:00
|
|
|
'TwittersettingsAction', 'FBConnectSettingsAction');
|
|
|
|
|
|
|
|
if (in_array($action_name, $connect_actions)) {
|
|
|
|
$nav = new FBCSettingsNav($action);
|
|
|
|
$nav->show();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-05-20 07:46:11 +01:00
|
|
|
return true;
|
|
|
|
}
|
2009-05-21 06:43:11 +01:00
|
|
|
|
2009-05-28 01:08:00 +01:00
|
|
|
function onStartLogout($action)
|
2009-08-19 00:41:24 +01:00
|
|
|
{
|
2009-05-28 01:08:00 +01:00
|
|
|
$action->logout();
|
2009-06-30 02:54:08 +01:00
|
|
|
$fbuid = $this->loggedIn();
|
2009-05-28 01:08:00 +01:00
|
|
|
|
2009-06-30 02:54:08 +01:00
|
|
|
if (!empty($fbuid)) {
|
2009-05-28 01:08:00 +01:00
|
|
|
try {
|
2009-06-30 02:54:08 +01:00
|
|
|
$facebook = getFacebook();
|
|
|
|
$facebook->expire_session();
|
2009-05-28 01:08:00 +01:00
|
|
|
} catch (Exception $e) {
|
2009-08-19 00:41:24 +01:00
|
|
|
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
|
|
|
'Could\'t logout of Facebook: ' .
|
|
|
|
$e->getMessage());
|
2009-05-28 01:08:00 +01:00
|
|
|
}
|
2009-05-22 01:54:22 +01:00
|
|
|
}
|
2009-05-28 01:08:00 +01:00
|
|
|
|
|
|
|
return true;
|
2009-05-22 01:54:22 +01:00
|
|
|
}
|
|
|
|
|
2009-07-01 23:47:21 +01:00
|
|
|
function getProfilePicURL($fbuid)
|
|
|
|
{
|
|
|
|
|
|
|
|
$facebook = getFacebook();
|
|
|
|
$url = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
$fqry = 'SELECT pic_square FROM user WHERE uid = %s';
|
|
|
|
|
|
|
|
$result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid));
|
|
|
|
|
|
|
|
if (!empty($result)) {
|
|
|
|
$url = $result[0]['pic_square'];
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
2009-08-19 00:41:24 +01:00
|
|
|
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
|
|
|
|
"Facebook client failure requesting profile pic!");
|
2009-07-01 23:47:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $url;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2009-05-22 01:54:22 +01:00
|
|
|
}
|