Merge pull request #9050 from annando/system-account
We now fetch data with an automatically generated system user
This commit is contained in:
commit
d31010ca0e
|
@ -99,6 +99,105 @@ class User
|
|||
|
||||
private static $owner;
|
||||
|
||||
/**
|
||||
* Fetch the system account
|
||||
*
|
||||
* @return array system account
|
||||
*/
|
||||
public static function getSystemAccount()
|
||||
{
|
||||
$system = Contact::selectFirst([], ['self' => true, 'uid' => 0]);
|
||||
if (!DBA::isResult($system)) {
|
||||
self::createSystemAccount();
|
||||
$system = Contact::selectFirst([], ['self' => true, 'uid' => 0]);
|
||||
if (!DBA::isResult($system)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
$system['spubkey'] = $system['uprvkey'] = $system['prvkey'];
|
||||
$system['username'] = $system['name'];
|
||||
$system['nickname'] = $system['nick'];
|
||||
return $system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the system account
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function createSystemAccount()
|
||||
{
|
||||
$system_actor_name = self::getActorName();
|
||||
if (empty($system_actor_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keys = Crypto::newKeypair(4096);
|
||||
if ($keys === false) {
|
||||
throw new Exception(DI::l10n()->t('SERIOUS ERROR: Generation of security keys failed.'));
|
||||
}
|
||||
|
||||
$system = [];
|
||||
$system['uid'] = 0;
|
||||
$system['created'] = DateTimeFormat::utcNow();
|
||||
$system['self'] = true;
|
||||
$system['network'] = Protocol::ACTIVITYPUB;
|
||||
$system['name'] = 'System Account';
|
||||
$system['addr'] = $system_actor_name . '@' . DI::baseUrl()->getHostname();
|
||||
$system['nick'] = $system_actor_name;
|
||||
$system['avatar'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO;
|
||||
$system['photo'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO;
|
||||
$system['thumb'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_THUMB;
|
||||
$system['micro'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_MICRO;
|
||||
$system['url'] = DI::baseUrl() . '/friendica';
|
||||
$system['nurl'] = Strings::normaliseLink($system['url']);
|
||||
$system['pubkey'] = $keys['pubkey'];
|
||||
$system['prvkey'] = $keys['prvkey'];
|
||||
$system['blocked'] = 0;
|
||||
$system['pending'] = 0;
|
||||
$system['contact-type'] = Contact::TYPE_RELAY; // In AP this is translated to 'Application'
|
||||
$system['name-date'] = DateTimeFormat::utcNow();
|
||||
$system['uri-date'] = DateTimeFormat::utcNow();
|
||||
$system['avatar-date'] = DateTimeFormat::utcNow();
|
||||
$system['closeness'] = 0;
|
||||
$system['baseurl'] = DI::baseUrl();
|
||||
$system['gsid'] = GServer::getID($system['baseurl']);
|
||||
DBA::insert('contact', $system);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a usable actor name
|
||||
*
|
||||
* @return string actor account name
|
||||
*/
|
||||
public static function getActorName()
|
||||
{
|
||||
$system_actor_name = DI::config()->get('system', 'actor_name');
|
||||
if (!empty($system_actor_name)) {
|
||||
$self = Contact::selectFirst(['nick'], ['uid' => 0, 'self' => true]);
|
||||
if (!empty($self['nick'])) {
|
||||
if ($self['nick'] != $system_actor_name) {
|
||||
// Reset the actor name to the already used name
|
||||
DI::config()->set('system', 'actor_name', $self['nick']);
|
||||
$system_actor_name = $self['nick'];
|
||||
}
|
||||
}
|
||||
return $system_actor_name;
|
||||
}
|
||||
|
||||
// List of possible actor names
|
||||
$possible_accounts = ['friendica', 'actor', 'system', 'internal'];
|
||||
foreach ($possible_accounts as $name) {
|
||||
if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'expire']) &&
|
||||
!DBA::exists('userd', ['username' => $name])) {
|
||||
DI::config()->set('system', 'actor_name', $name);
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a user record exists with the provided id
|
||||
*
|
||||
|
@ -588,15 +687,24 @@ class User
|
|||
public static function isNicknameBlocked($nickname)
|
||||
{
|
||||
$forbidden_nicknames = DI::config()->get('system', 'forbidden_nicknames', '');
|
||||
if (!empty($forbidden_nicknames)) {
|
||||
$forbidden = explode(',', $forbidden_nicknames);
|
||||
$forbidden = array_map('trim', $forbidden);
|
||||
} else {
|
||||
$forbidden = [];
|
||||
}
|
||||
|
||||
// if the config variable is empty return false
|
||||
if (empty($forbidden_nicknames)) {
|
||||
// Add the name of the internal actor to the "forbidden" list
|
||||
$actor_name = self::getActorName();
|
||||
if (!empty($actor_name)) {
|
||||
$forbidden[] = $actor_name;
|
||||
}
|
||||
|
||||
if (empty($forbidden)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the nickname is in the list of blocked nicknames
|
||||
$forbidden = explode(',', $forbidden_nicknames);
|
||||
$forbidden = array_map('trim', $forbidden);
|
||||
if (in_array(strtolower($nickname), $forbidden)) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ use Friendica\Core\Worker;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseAdmin;
|
||||
use Friendica\Module\Register;
|
||||
use Friendica\Util\BasePath;
|
||||
|
@ -148,6 +149,7 @@ class Site extends BaseAdmin
|
|||
$allowed_sites = (!empty($_POST['allowed_sites']) ? Strings::escapeTags(trim($_POST['allowed_sites'])) : '');
|
||||
$allowed_email = (!empty($_POST['allowed_email']) ? Strings::escapeTags(trim($_POST['allowed_email'])) : '');
|
||||
$forbidden_nicknames = (!empty($_POST['forbidden_nicknames']) ? strtolower(Strings::escapeTags(trim($_POST['forbidden_nicknames']))) : '');
|
||||
$system_actor_name = (!empty($_POST['system_actor_name']) ? Strings::escapeTags(trim($_POST['system_actor_name'])) : '');
|
||||
$no_oembed_rich_content = !empty($_POST['no_oembed_rich_content']);
|
||||
$allowed_oembed = (!empty($_POST['allowed_oembed']) ? Strings::escapeTags(trim($_POST['allowed_oembed'])) : '');
|
||||
$block_public = !empty($_POST['block_public']);
|
||||
|
@ -355,6 +357,7 @@ class Site extends BaseAdmin
|
|||
DI::config()->set('system', 'allowed_sites' , $allowed_sites);
|
||||
DI::config()->set('system', 'allowed_email' , $allowed_email);
|
||||
DI::config()->set('system', 'forbidden_nicknames' , $forbidden_nicknames);
|
||||
DI::config()->set('system', 'system_actor_name' , $system_actor_name);
|
||||
DI::config()->set('system', 'no_oembed_rich_content' , $no_oembed_rich_content);
|
||||
DI::config()->set('system', 'allowed_oembed' , $allowed_oembed);
|
||||
DI::config()->set('system', 'block_public' , $block_public);
|
||||
|
@ -600,6 +603,7 @@ class Site extends BaseAdmin
|
|||
// name, label, value, help string, extra data...
|
||||
'$sitename' => ['sitename', DI::l10n()->t('Site name'), DI::config()->get('config', 'sitename'), ''],
|
||||
'$sender_email' => ['sender_email', DI::l10n()->t('Sender Email'), DI::config()->get('config', 'sender_email'), DI::l10n()->t('The email address your server shall use to send notification emails from.'), '', '', 'email'],
|
||||
'$system_actor_name' => ['system_actor_name', DI::l10n()->t('Name of the system actor'), User::getActorName(), DI::l10n()->t("Name of the internal system account that is used to perform ActivityPub requests. This must be an unused username. If set, this can't be changed again.")],
|
||||
'$banner' => ['banner', DI::l10n()->t('Banner/Logo'), $banner, ''],
|
||||
'$email_banner' => ['email_banner', DI::l10n()->t('Email Banner/Logo'), $email_banner, ''],
|
||||
'$shortcut_icon' => ['shortcut_icon', DI::l10n()->t('Shortcut icon'), DI::config()->get('system', 'shortcut_icon'), DI::l10n()->t('Link to an icon that will be used for browsers.')],
|
||||
|
|
|
@ -25,8 +25,10 @@ use Friendica\BaseModule;
|
|||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
||||
/**
|
||||
* Prints information about the current node
|
||||
|
@ -108,6 +110,15 @@ class Friendica extends BaseModule
|
|||
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
if (ActivityPub::isRequest()) {
|
||||
$data = ActivityPub\Transmitter::getProfile(0);
|
||||
if (!empty($data)) {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Cache-Control: max-age=23200, stale-while-revalidate=23200');
|
||||
System::jsonExit($data, 'application/activity+json');
|
||||
}
|
||||
}
|
||||
|
||||
$app = DI::app();
|
||||
|
||||
// @TODO: Replace with parameter from router
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Friendica\Module;
|
|||
use Friendica\BaseModule;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Photo;
|
||||
|
@ -77,24 +78,30 @@ class Xrd extends BaseModule
|
|||
$name = substr($local, 0, strpos($local, '@'));
|
||||
}
|
||||
|
||||
$user = User::getByNickname($name);
|
||||
if ($name == User::getActorName()) {
|
||||
$owner = User::getSystemAccount();
|
||||
if (empty($owner)) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
}
|
||||
self::printSystemJSON($owner);
|
||||
} else {
|
||||
$user = User::getByNickname($name);
|
||||
if (empty($user)) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
if (empty($user)) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
$owner = User::getOwnerDataById($user['uid']);
|
||||
if (empty($owner)) {
|
||||
DI::logger()->warning('No owner data for user id', ['uri' => $uri, 'name' => $name, 'user' => $user]);
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$alias = str_replace('/profile/', '/~', $owner['url']);
|
||||
|
||||
$avatar = Photo::selectFirst(['type'], ['uid' => $owner['uid'], 'profile' => true]);
|
||||
}
|
||||
|
||||
$owner = User::getOwnerDataById($user['uid']);
|
||||
|
||||
if (empty($owner)) {
|
||||
DI::logger()->warning('No owner data for user id', ['uri' => $uri, 'name' => $name, 'user' => $user]);
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$alias = str_replace('/profile/', '/~', $owner['url']);
|
||||
|
||||
$avatar = Photo::selectFirst(['type'], ['uid' => $owner['uid'], 'profile' => true]);
|
||||
|
||||
if (!DBA::isResult($avatar)) {
|
||||
if (empty($avatar)) {
|
||||
$avatar = ['type' => 'image/jpeg'];
|
||||
}
|
||||
|
||||
|
@ -105,6 +112,32 @@ class Xrd extends BaseModule
|
|||
}
|
||||
}
|
||||
|
||||
private static function printSystemJSON(array $owner)
|
||||
{
|
||||
$json = [
|
||||
'subject' => 'acct:' . $owner['addr'],
|
||||
'aliases' => [$owner['url']],
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'http://webfinger.net/rel/profile-page',
|
||||
'type' => 'text/html',
|
||||
'href' => $owner['url'],
|
||||
],
|
||||
[
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => $owner['url'],
|
||||
],
|
||||
[
|
||||
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
|
||||
'template' => DI::baseUrl()->get() . '/follow?url={uri}',
|
||||
],
|
||||
]
|
||||
];
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
System::jsonExit($json, 'application/jrd+json; charset=utf-8');
|
||||
}
|
||||
|
||||
private static function printJSON($alias, $baseURL, $owner, $avatar)
|
||||
{
|
||||
$salmon_key = Salmon::salmonKey($owner['spubkey']);
|
||||
|
|
|
@ -2200,7 +2200,7 @@ class Probe
|
|||
$profile['gsid'] = GServer::getID($approfile['generator']['url']);
|
||||
}
|
||||
|
||||
$data = ['name' => $profile['name'], 'nick' => $profile['nick'], 'guid' => $approfile['diaspora:guid'],
|
||||
$data = ['name' => $profile['name'], 'nick' => $profile['nick'], 'guid' => $approfile['diaspora:guid'] ?? '',
|
||||
'url' => $profile['url'], 'addr' => $profile['addr'], 'alias' => $profile['alias'],
|
||||
'photo' => $profile['photo'], 'account-type' => $profile['contact-type'],
|
||||
'community' => ($profile['contact-type'] == User::ACCOUNT_TYPE_COMMUNITY),
|
||||
|
|
|
@ -90,20 +90,6 @@ class ActivityPub
|
|||
*/
|
||||
public static function fetchContent(string $url, int $uid = 0)
|
||||
{
|
||||
if (empty($uid)) {
|
||||
$user = User::getFirstAdmin(['uid']);
|
||||
|
||||
if (empty($user['uid'])) {
|
||||
// When the system setup is missing an admin we just take the first user
|
||||
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
|
||||
$user = DBA::selectFirst('user', ['uid'], $condition);
|
||||
}
|
||||
|
||||
if (!empty($user['uid'])) {
|
||||
$uid = $user['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
return HTTPSignature::fetch($url, $uid);
|
||||
}
|
||||
|
||||
|
|
|
@ -774,6 +774,9 @@ class Processor
|
|||
}
|
||||
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
if (empty($owner)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cid = Contact::getIdForURL($activity['actor'], $uid);
|
||||
if (!empty($cid)) {
|
||||
|
@ -956,6 +959,9 @@ class Processor
|
|||
}
|
||||
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
if (empty($owner)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cid = Contact::getIdForURL($activity['actor'], $uid);
|
||||
if (empty($cid)) {
|
||||
|
|
|
@ -214,39 +214,63 @@ class Transmitter
|
|||
*/
|
||||
public static function getProfile($uid)
|
||||
{
|
||||
$condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
|
||||
'account_removed' => false, 'verified' => true];
|
||||
$fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
|
||||
$user = DBA::selectFirst('user', $fields, $condition);
|
||||
if (!DBA::isResult($user)) {
|
||||
return [];
|
||||
}
|
||||
if ($uid != 0) {
|
||||
$condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
|
||||
'account_removed' => false, 'verified' => true];
|
||||
$fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
|
||||
$user = DBA::selectFirst('user', $fields, $condition);
|
||||
if (!DBA::isResult($user)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = ['locality', 'region', 'country-name'];
|
||||
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
|
||||
if (!DBA::isResult($profile)) {
|
||||
return [];
|
||||
}
|
||||
$fields = ['locality', 'region', 'country-name'];
|
||||
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
|
||||
if (!DBA::isResult($profile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = ['name', 'url', 'location', 'about', 'avatar', 'photo'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return [];
|
||||
$fields = ['name', 'url', 'location', 'about', 'avatar', 'photo'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
$contact = User::getSystemAccount();
|
||||
$user = ['guid' => '', 'nickname' => $contact['nick'], 'pubkey' => $contact['pubkey'],
|
||||
'account-type' => $contact['contact-type'], 'page-flags' => User::PAGE_FLAGS_NORMAL];
|
||||
$profile = ['locality' => '', 'region' => '', 'country-name' => ''];
|
||||
}
|
||||
|
||||
$data = ['@context' => ActivityPub::CONTEXT];
|
||||
$data['id'] = $contact['url'];
|
||||
$data['diaspora:guid'] = $user['guid'];
|
||||
|
||||
if (!empty($user['guid'])) {
|
||||
$data['diaspora:guid'] = $user['guid'];
|
||||
}
|
||||
|
||||
$data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']];
|
||||
$data['following'] = DI::baseUrl() . '/following/' . $user['nickname'];
|
||||
$data['followers'] = DI::baseUrl() . '/followers/' . $user['nickname'];
|
||||
$data['inbox'] = DI::baseUrl() . '/inbox/' . $user['nickname'];
|
||||
$data['outbox'] = DI::baseUrl() . '/outbox/' . $user['nickname'];
|
||||
|
||||
if ($uid != 0) {
|
||||
$data['following'] = DI::baseUrl() . '/following/' . $user['nickname'];
|
||||
$data['followers'] = DI::baseUrl() . '/followers/' . $user['nickname'];
|
||||
$data['inbox'] = DI::baseUrl() . '/inbox/' . $user['nickname'];
|
||||
$data['outbox'] = DI::baseUrl() . '/outbox/' . $user['nickname'];
|
||||
} else {
|
||||
$data['inbox'] = DI::baseUrl() . '/friendica/inbox';
|
||||
}
|
||||
|
||||
$data['preferredUsername'] = $user['nickname'];
|
||||
$data['name'] = $contact['name'];
|
||||
$data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
|
||||
'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
|
||||
$data['summary'] = BBCode::convert($contact['about'], false);
|
||||
|
||||
if (!empty($profile['country-name'] . $profile['region'] . $profile['locality'])) {
|
||||
$data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
|
||||
'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
|
||||
}
|
||||
|
||||
if (!empty($contact['about'])) {
|
||||
$data['summary'] = BBCode::convert($contact['about'], false);
|
||||
}
|
||||
|
||||
$data['url'] = $contact['url'];
|
||||
$data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
|
||||
$data['publicKey'] = ['id' => $contact['url'] . '#main-key',
|
||||
|
@ -652,6 +676,10 @@ class Transmitter
|
|||
$item_profile = APContact::getByURL($item['owner-link'], false);
|
||||
}
|
||||
|
||||
if (empty($item_profile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$profile_uid = User::getIdForURL($item_profile['url']);
|
||||
|
||||
foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
|
||||
|
|
|
@ -420,23 +420,28 @@ class HTTPSignature
|
|||
if (!$owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($owner['uprvkey'])) {
|
||||
// Header data that is about to be signed.
|
||||
$host = parse_url($request, PHP_URL_HOST);
|
||||
$path = parse_url($request, PHP_URL_PATH);
|
||||
$date = DateTimeFormat::utcNow(DateTimeFormat::HTTP);
|
||||
|
||||
$headers = ['Date: ' . $date, 'Host: ' . $host];
|
||||
|
||||
$signed_data = "(request-target): get " . $path . "\ndate: ". $date . "\nhost: " . $host;
|
||||
|
||||
$signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256'));
|
||||
|
||||
$headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date host",signature="' . $signature . '"';
|
||||
} else {
|
||||
$owner = User::getSystemAccount();
|
||||
if (!$owner) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($owner['uprvkey'])) {
|
||||
// Header data that is about to be signed.
|
||||
$host = parse_url($request, PHP_URL_HOST);
|
||||
$path = parse_url($request, PHP_URL_PATH);
|
||||
$date = DateTimeFormat::utcNow(DateTimeFormat::HTTP);
|
||||
|
||||
$headers = ['Date: ' . $date, 'Host: ' . $host];
|
||||
|
||||
$signed_data = "(request-target): get " . $path . "\ndate: ". $date . "\nhost: " . $host;
|
||||
|
||||
$signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256'));
|
||||
|
||||
$headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) date host",signature="' . $signature . '"';
|
||||
}
|
||||
|
||||
if (!empty($opts['accept_content'])) {
|
||||
$headers[] = 'Accept: ' . $opts['accept_content'];
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
{{include file="field_input.tpl" field=$sitename}}
|
||||
{{include file="field_input.tpl" field=$sender_email}}
|
||||
{{include file="field_input.tpl" field=$system_actor_name}}
|
||||
{{include file="field_textarea.tpl" field=$banner}}
|
||||
{{include file="field_input.tpl" field=$email_banner}}
|
||||
{{include file="field_input.tpl" field=$shortcut_icon}}
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<div class="panel-body">
|
||||
{{include file="field_input.tpl" field=$sitename}}
|
||||
{{include file="field_input.tpl" field=$sender_email}}
|
||||
{{include file="field_input.tpl" field=$system_actor_name}}
|
||||
{{include file="field_textarea.tpl" field=$banner}}
|
||||
{{include file="field_input.tpl" field=$shortcut_icon}}
|
||||
{{include file="field_input.tpl" field=$touch_icon}}
|
||||
|
|
Loading…
Reference in a new issue