Merge branch 'openvk:master' into master

This commit is contained in:
Dmitry 2021-12-24 19:29:56 +07:00 committed by GitHub
commit 8ea6204e4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 540 additions and 231 deletions

0
.gitmodules vendored
View file

View file

@ -10,7 +10,7 @@ To be honest, we don't even know whether it even works. However, this version is
Please use the master branch, as it has the most changes.
Updating the source code is done with this command: `git pull --recurse-submodules`
Updating the source code is done with this command: `git pull`
## Instances
@ -29,7 +29,7 @@ If you want, you can add your instance to the list above so that people can regi
* PHP 8 has **not** yet been tested, so you should not expect it to work.
2. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
```
git clone --recursive https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk
git clone https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk
git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha
```
3. And enable them:
@ -44,8 +44,6 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
8. Move to `Web/static/js` and execute `yarn install`
9. Set `openvk` as your root app in `chandler.yml`
**Note**: If OVK submodules were not downloaded beforehand (i.e. `--recursive` was not used during cloning), this command *must be* executed in the `openvk` folder: `git submodule update --init`
Once you are done, you can login as a system administrator on the network itself (no registration required):
* **Login**: `admin@localhost.localdomain6`
* **Password**: `admin`

View file

@ -135,6 +135,11 @@ class Club extends RowModel
return (bool) $this->getRecord()->everyone_can_create_topics;
}
function isDisplayTopicsAboveWallEnabled(): bool
{
return (bool) $this->getRecord()->display_topics_above_wall;
}
function getType(): int
{
return $this->getRecord()->type;

View file

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\{User, Gift};
final class CoinsTransferNotification extends Notification
{
protected $actionCode = 9602;
function __construct(User $receiver, User $sender, int $value, string $message)
{
parent::__construct($receiver, $receiver, $sender, time(), $value . " " . $message);
}
}

View file

@ -60,7 +60,7 @@ trait TRichText
$rel = $this->isAd() ? "sponsored" : "ugc";
$text = $this->formatLinks($text);
$text = preg_replace("%@([A-Za-z0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1|$2]", $text);
$text = preg_replace("%@([A-Za-z0-9]++)%Xu", "[$1|@$1]", $text);
$text = preg_replace("%([\n\r\s]|^)(@([A-Za-z0-9]++))%Xu", "$1[$3|@$3]", $text);
$text = preg_replace("%\[([A-Za-z0-9]++)\|([\p{L} 0-9@]+)\]%Xu", "<a href='/$1'>$2</a>", $text);
$text = preg_replace("%(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "<a href='/feed/hashtag/$2'>$1</a>", $text);
$text = $this->formatEmojis($text);

View file

@ -53,5 +53,22 @@ class Users
];
}
function getByAddress(string $address): ?User
{
if(substr_compare($address, "/", -1) === 0)
$address = substr($address, 0, iconv_strlen($address) - 1);
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
if(strpos($address, $serverUrl . "/") === 0)
$address = substr($address, iconv_strlen($serverUrl) + 1);
if(strpos($address, "id") === 0) {
$user = $this->get((int) substr($address, 2));
if($user) return $user;
}
return $this->getByShortUrl($address);
}
use \Nette\SmartObject;
}

View file

@ -208,6 +208,7 @@ final class GroupPresenter extends OpenVKPresenter
$club->setWall(empty($this->postParam("wall")) ? 0 : 1);
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
$club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1);
$club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1);;
$website = $this->postParam("website") ?? "";
if(empty($website))

View file

@ -138,12 +138,13 @@ final class PhotosPresenter extends OpenVKPresenter
}
$this->template->album = $album;
$this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1) ) );
$this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1), 20) );
$this->template->paginatorConf = (object) [
"count" => $album->getPhotosCount(),
"page" => $this->queryParam("p") ?? 1,
"amount" => sizeof($this->template->photos),
"perPage" => OPENVK_DEFAULT_PER_PAGE,
"perPage" => 20,
"atBottom" => true
];
}

View file

@ -10,6 +10,7 @@ use openvk\Web\Models\Repositories\Videos;
use openvk\Web\Models\Repositories\Notes;
use openvk\Web\Models\Repositories\Vouchers;
use openvk\Web\Util\Validator;
use openvk\Web\Models\Entities\Notifications\CoinsTransferNotification;
use Chandler\Security\Authenticator;
use lfkeitel\phptotp\{Base32, Totp};
use chillerlan\QRCode\{QRCode, QROptions};
@ -465,4 +466,42 @@ final class UserPresenter extends OpenVKPresenter
$this->user->identity->save();
$this->flashFail("succ", tr("information_-1"), tr("two_factor_authentication_disabled_message"));
}
function renderCoinsTransfer(): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$receiverAddress = $this->postParam("receiver");
$value = (int) $this->postParam("value");
$message = $this->postParam("message");
if(!$receiverAddress || !$value)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("not_all_information_has_been_entered"));
if($value < 0)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("negative_transfer_value"));
if(iconv_strlen($message) > 255)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("message_is_too_long"));
$receiver = $this->users->getByAddress($receiverAddress);
if(!$receiver)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("receiver_not_found"));
if($this->user->identity->getCoins() < $value)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("you_dont_have_enough_points"));
if($this->user->id !== $receiver->getId()) {
$this->user->identity->setCoins($this->user->identity->getCoins() - $value);
$this->user->identity->save();
$receiver->setCoins($receiver->getCoins() + $value);
$receiver->save();
(new CoinsTransferNotification($receiver, $this->user->identity, $value, $message))->emit();
}
$this->flashFail("succ", tr("information_-1"), tr("points_transfer_successful", tr("points_amount", $value), $receiver->getURL(), htmlentities($receiver->getCanonicalName())));
}
}

View file

@ -29,6 +29,9 @@
{include description, x => $dat}
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px">
{include actions, x => $dat}
</td>
</tr>
</tbody>
</table>

View file

@ -1,9 +1,9 @@
{extends "../@layout.xml"}
{block title}Об OpenVK{/block}
{block title}{_about_openvk}{/block}
{block header}
Об OpenVK
{_about_openvk}
{/block}
{block content}
@ -343,7 +343,7 @@
<tbody>
<tr class="h">
<th>Name</th>
<th>Status</th>
<th style="width: 50px;">Status</th>
<th>Version</th>
<th>Description</th>
<th>Author</th>

View file

@ -24,7 +24,7 @@
{/block}
{block description}
<table class="ugc-table" n:if="$hideInfo ? !$x->anon : true">
<table class="ugc-table" n:if="$hideInfo ? (!$x->anon || $x->sender->getId() === $thisUser->getId()) : true">
<tbody>
<tr>
<td><span class="nobold">{_sender}: </span></td>

View file

@ -82,7 +82,8 @@
<span class="nobold">{_discussions}: </span>
</td>
<td>
<input type="checkbox" name="everyone_can_create_topics" value="1" n:attr="checked => $club->isEveryoneCanCreateTopics()" /> {_everyone_can_create_topics}
<input type="checkbox" name="everyone_can_create_topics" value="1" n:attr="checked => $club->isEveryoneCanCreateTopics()" /> {_everyone_can_create_topics}<br>
<input type="checkbox" name="display_topics_above_wall" value="1" n:attr="checked => $club->isDisplayTopicsAboveWallEnabled()" /> {_display_list_of_topics_above_wall}
</td>
</tr>
<tr>

View file

@ -14,10 +14,6 @@
<a n:if="$onlyShowManagers" href="/club{$club->getId()}/followers" style="float: right;">{_only_administrators}</a>
{/block}
{block actions}
{/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block tabs}
@ -81,50 +77,13 @@
{/if}
</td>
</tr>
<tr n:if="$club->canBeModifiedBy($thisUser ?? NULL)">
<td width="120" valign="top"><span class="nobold">{_actions}: </span></td>
<td>
<a href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hash={rawurlencode($csrfToken)}" n:if="$club->getOwner()->getId() !== $user->getId()">
{if $manager}
{_devote}
{else}
{_promote_to_admin}
{/if}
</a>
{if $club->getOwner()->getId() != $user->getId() && $manager && $thisUser->getId() == $club->getOwner()->getId()}
|
<a href="javascript:changeOwner({$club->getId()}, {$user->getId()})">
{_promote_to_owner}
</a>
{/if}
{if $manager}
|
<a href="javascript:setClubAdminComment('{$club->getId()}', '{$manager->getUserId()}', '{rawurlencode($csrfToken)}')">
{_set_comment}
</a>
{/if}
<a n:if="$club->getOwner()->getId() === $user->getId()" href="javascript:setClubAdminComment('{$club->getId()}', '{$club->getOwner()->getId()}', '{rawurlencode($csrfToken)}')">
{_set_comment}
</a>
{if $manager}
|
<a href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$manager->isHidden()}&hash={rawurlencode($csrfToken)}">
{if $manager->isHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
</a>
{/if}
{if $club->getOwner()->getId() == $user->getId()}
|
<a href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$club->isOwnerHidden()}&hash={rawurlencode($csrfToken)}">
{if $club->isOwnerHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
</a>
{/if}
</td>
</tr>
</tbody>
</table>
<script n:if="$club->getOwner()->getId() != $user->getId() && $manager && $thisUser->getId() == $club->getOwner()->getId()">
console.log("gayshit")
console.log("gayshit");
console.log("сам такой");
function changeOwner(club, newOwner) {
const action = "/groups/" + club + "/setNewOwner/" + newOwner;
@ -145,3 +104,40 @@
}
</script>
{/block}
{block actions}
{var user = $x instanceof $Manager ? $x->getUser() : $x}
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
{if $club->canBeModifiedBy($thisUser ?? NULL)}
<a class="profile_link" href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hash={rawurlencode($csrfToken)}" n:if="$club->getOwner()->getId() !== $user->getId()">
{if $manager}
{_devote}
{else}
{_promote_to_admin}
{/if}
</a>
{if $club->getOwner()->getId() != $user->getId() && $manager && $thisUser->getId() == $club->getOwner()->getId()}
<a class="profile_link" href="javascript:changeOwner({$club->getId()}, {$user->getId()})">
{_promote_to_owner}
</a>
{/if}
{if $manager}
<a class="profile_link" href="javascript:setClubAdminComment('{$club->getId()}', '{$manager->getUserId()}', '{rawurlencode($csrfToken)}')">
{_set_comment}
</a>
{/if}
<a class="profile_link" n:if="$club->getOwner()->getId() === $user->getId()" href="javascript:setClubAdminComment('{$club->getId()}', '{$club->getOwner()->getId()}', '{rawurlencode($csrfToken)}')">
{_set_comment}
</a>
{if $manager}
<a class="profile_link" href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$manager->isHidden()}&hash={rawurlencode($csrfToken)}">
{if $manager->isHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
</a>
{/if}
{if $club->getOwner()->getId() == $user->getId()}
<a class="profile_link" href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$club->isOwnerHidden()}&hash={rawurlencode($csrfToken)}">
{if $club->isOwnerHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
</a>
{/if}
{/if}
{/block}

View file

@ -41,45 +41,54 @@
</table>
</div>
<div n:if="$club->getFollowersCount() > 0">
{var followersCount = $club->getFollowersCount()}
{var followersCount = $club->getFollowersCount()}
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
{_participants}
</div>
<div>
<div class="content_subtitle">
{tr("participants", $followersCount)}
<div style="float:right;">
<a href="/club{$club->getId()}/followers">{_all_title}</a>
</div>
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
{_participants}
</div>
<div>
<div class="content_subtitle">
{tr("participants", $followersCount)}
<div style="float:right;">
<a href="/club{$club->getId()}/followers">{_all_title}</a>
</div>
<div style="padding-left: 5px;">
<table
n:foreach="$club->getFollowers(1) as $follower"
n:class="User"
style="text-align:center;display:inline-block;width:62px"
cellspacing=4>
<tbody>
<tr>
<td>
<a href="{$follower->getURL()}">
<img src="{$follower->getAvatarUrl()}" width="50" />
</a>
</td>
</tr>
<tr>
<td>
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
</td>
</tr>
</tbody>
</table>
</div>
<div style="padding-left: 5px;" class="content_list long">
<div class="cl_element" n:foreach="$club->getFollowers(1) as $follower">
<div class="cl_avatar">
<a href="{$follower->getURL()}">
<img class="ava" src="{$follower->getAvatarUrl()}" />
</a>
</div>
<a href="{$follower->getURL()}" class="cl_name">
<text class="cl_fname">{$follower->getFirstName()}</text>
<text class="cl_lname">{$follower->getLastName()}</text>
</a>
</div>
</div>
</div>
</div>
<div n:if="($topicsCount > 0 || $club->isEveryoneCanCreateTopics() || ($thisUser && $club->canBeModifiedBy($thisUser))) && $club->isDisplayTopicsAboveWallEnabled()">
<div class="content_title_expanded" onclick="hidePanel(this, {$topicsCount});">
{_discussions}
</div>
<div>
<div class="content_subtitle">
{tr("topics", $topicsCount)}
<div style="float: right;">
<a href="/board{$club->getId()}">{_"all_title"}</a>
</div>
</div>
<div>
<div n:foreach="$topics as $topic" class="topic-list-item" style="padding: 8px;">
<b><a href="/topic{$topic->getPrettyId()}">{$topic->getTitle()}</a></b><br>
<span class="nobold">{tr("updated_at", $topic->getUpdateTime())}</span>
</div>
</div>
</div>
</div>
{presenter "openvk!Wall->wallEmbedded", -$club->getId()}
</div>
<div class="right_small_block">
<a href="{$club->getAvatarLink()|nocheck}">
@ -203,7 +212,7 @@
</div>
</div>
</div>
<div n:if="$topicsCount > 0 || $club->isEveryoneCanCreateTopics() || $club->canBeModifiedBy($thisUser)">
<div n:if="($topicsCount > 0 || $club->isEveryoneCanCreateTopics() || ($thisUser && $club->canBeModifiedBy($thisUser))) && !$club->isDisplayTopicsAboveWallEnabled()">
<div class="content_title_expanded" onclick="hidePanel(this, {$topicsCount});">
{_discussions}
</div>

View file

@ -19,13 +19,6 @@
</div>
{/block}
{block actions}
<div class="tile">
<a class="profile_link" href="?">{_unreaded}</a>
<a class="profile_link" href="?act=archived">{_archive}</a>
</div>
{/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block link|strip|stripHtml}

View file

@ -27,23 +27,24 @@
&nbsp;|&nbsp;
<a href="/album{$album->getPrettyId()}/edit">{_"edit_album"}</a>
{/if}
<br/>
<br/><br/>
{if $album->getPhotosCount() > 0}
{foreach $photos as $photo}
{php if($photo->isDeleted()) continue; }
<div class="album-photo" style="display: inline-table;">
<a
n:if="!is_null($thisUser) && $album->canBeModifiedBy($thisUser)"
href="/album{$album->getPrettyId()}/remove_photo.pl/{$photo->getId()}" class="album-photo--delete">
&times;
</a>
<a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}">
<img class="album-photo--image" src="{$photo->getURL()}" alt="{$photo->getDescription()}" style="width:unset;max-height:unset;max-width: 188px;" />
</a>
</div>
{/foreach}
<div class="container_gray album-flex">
{foreach $photos as $photo}
{php if($photo->isDeleted()) continue; }
<div class="album-photo">
<a
n:if="!is_null($thisUser) && $album->canBeModifiedBy($thisUser)"
href="/album{$album->getPrettyId()}/remove_photo.pl/{$photo->getId()}" class="album-photo--delete">
&times;
</a>
<a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}">
<img class="album-photo--image" src="{$photo->getURL()}" alt="{$photo->getDescription()}" />
</a>
</div>
{/foreach}
</div>
{include "../components/paginator.xml", conf => $paginatorConf}
{else}
{include "../components/nothing.xml"}

View file

@ -110,7 +110,7 @@
</div>
{/if}
{if $comment->getUType() === 1}
{if $comment->getUType() === 1 && !is_null($comment->isLikedByUser())}
<div class="post-menu">
<strong>
{if $comment->isLikedByUser()}

View file

@ -79,3 +79,39 @@
</tbody>
</table>
{/block}
{block actions}
{if $x->getId() !== $thisUser->getId()}
{var subStatus = $x->getSubscriptionStatus($thisUser)}
{if $subStatus === 0}
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_add"}" />
</form>
{elseif $subStatus === 1}
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_accept"}" />
</form>
{elseif $subStatus === 2}
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_reject"}" />
</form>
{elseif $subStatus === 3}
<a href="/im?sel={$x->getId()}" class="profile_link">{_"send_message"}</a>
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_delete"}" />
</form>
{/if}
{/if}
{/block}

View file

@ -49,23 +49,17 @@
{block description}
{$x->getDescription()}
{if $x->canBeModifiedBy($thisUser ?? NULL)}
{var clubPinned = $thisUser->isClubPinned($x)}
<table n:if="$clubPinned || $thisUser->getPinnedClubCount() <= 10">
<tbody>
<tr>
<td width="120" valign="top"><span class="nobold">{_actions}: </span></td>
<td>
<a href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
{if $clubPinned}
{_remove_from_left_menu}
{else}
{_add_to_left_menu}
{/if}
</a>
</td>
</tr>
</tbody>
</table>
{/if}
{/block}
{var clubPinned = $thisUser->isClubPinned($x)}
{if $x->canBeModifiedBy($thisUser ?? NULL) || $clubPinned || $thisUser->getPinnedClubCount() <= 10}
{block actions}
<a class="profile_link" href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
{if $clubPinned}
{_remove_from_left_menu}
{else}
{_add_to_left_menu}
{/if}
</a>
{/block}
{/if}

View file

@ -332,6 +332,7 @@
<div style="width: 75%; display: inline-block;">
{presenter "openvk!Support->knowledgeBaseArticle", "points"}
<center>{tr("also_you_can_transfer_points", $thisUser->getCoins(), rawurlencode($csrfToken))|noescape}</center>
</div>
<div style="width: 22%; float: right;">
<p style="margin: 0; font-size: medium; text-align: center;">

View file

@ -91,25 +91,25 @@
</a>
{/if}
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce']" href="/gifts?act=pick&user={$user->getId()}" class="profile_link">{_send_gift}</a>
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && $user->getGiftCount() == 0" href="/gifts?act=pick&user={$user->getId()}" class="profile_link">{_send_gift}</a>
{var subStatus = $user->getSubscriptionStatus($thisUser)}
{if $subStatus === 0}
<form action="/setSub/user" method="post">
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_add"}" />
</form>
{elseif $subStatus === 1}
<form action="/setSub/user" method="post">
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_accept"}" />
</form>
{elseif $subStatus === 2}
<form action="/setSub/user" method="post">
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
@ -117,7 +117,7 @@
</form>
{elseif $subStatus === 3}
<a href="/im?sel={$user->getId()}" class="profile_link">{_"send_message"}</a>
<form action="/setSub/user" method="post">
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
@ -125,6 +125,7 @@
</form>
{/if}
{/if}
<a n:if="$user->getFollowersCount() > 0" href="/friends{$user->getId()}?act=incoming" class="profile_link">{tr("followers", $user->getFollowersCount())}</a>
</div>
<div n:if="isset($thisUser) && !$thisUser->prefersNotToSeeRating()" class="profile-hints">
{var completeness = $user->getProfileCompletenessReport()}
@ -150,39 +151,15 @@
</a>
<a n:if="in_array('telegram', $completeness->unfilled)" href="/edit?act=contacts">
<img src="/assets/packages/static/openvk/img/icon2.gif" />
Telegram (+10%)
Telegram (+15%)
</a>
<a n:if="in_array('status', $completeness->unfilled)" href="/edit">
<img src="/assets/packages/static/openvk/img/icon3.gif" />
{_status} (+10%)
{_status} (+15%)
</a>
{/if}
</div>
<br />
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && ($giftCount = $user->getGiftCount()) > 0">
<div class="content_title_expanded" onclick="hidePanel(this, {$giftCount});">
{_gifts}
</div>
<div>
<div class="content_subtitle">
{tr("gifts", $giftCount)}
<div style="float:right;">
<a href="/gifts{$user->getId()}">{_all_title}</a>
</div>
</div>
<div class="ovk-avView">
<div class="ovk-avView--el" n:foreach="$user->getGifts(1, 3) as $giftDescriptor">
{var hideInfo = !is_null($thisUser) ? ($giftDescriptor->anon ? $thisUser->getId() !== $user->getId() : false) : false}
<a href="{$hideInfo ? 'javascript:false' : $giftDescriptor->sender->getURL()}">
<img class="ava"
src="{$giftDescriptor->gift->getImage(2)}"
alt="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}" />
</a>
</div>
</div>
</div>
</div>
<div n:if="$user->getFriendsCount() > 0 && $user->getPrivacyPermission('friends.read', $thisUser ?? NULL)">
{var friendCount = $user->getFriendsCount()}
@ -196,37 +173,17 @@
<a href="/friends{$user->getId()}">{_"all_title"}</a>
</div>
</div>
<div class="ovk-avView">
<div class="ovk-avView--el" n:foreach="$user->getFriends(1) as $friend">
<a href="{$friend->getURL()}">
<img class="ava" src="{$friend->getAvatarUrl()}" />
<div class="content_list">
<div class="cl_element" n:foreach="$user->getFriends(1) as $friend">
<div class="cl_avatar">
<a href="{$friend->getURL()}">
<img class="ava" src="{$friend->getAvatarUrl()}" />
</a>
</div>
<a href="{$friend->getURL()}" class="cl_name">
<text class="cl_fname">{$friend->getFirstName()}</text>
<text class="cl_lname">{$friend->getLastName()}</text>
</a>
<br/>
<a href="{$friend->getURL()}">{$friend->getFirstName()}</a>
</div>
</div>
</div>
</div>
<div n:if="$user->getFollowersCount() > 0">
{var followersCount = $user->getFollowersCount()}
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
{_followers}
</div>
<div>
<div class="content_subtitle">
{tr("followers", $followersCount)}
<div style="float:right;">
<a href="/friends{$user->getId()}?act=incoming">{_"all_title"}</a>
</div>
</div>
<div class="ovk-avView">
<div class="ovk-avView--el" n:foreach="$user->getFollowers(1) as $follower">
<a href="{$follower->getURL()}">
<img class="ava" src="{$follower->getAvatarUrl()}" />
</a>
<br/>
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
</div>
</div>
</div>
@ -509,6 +466,35 @@
</div>
</div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && ($giftCount = $user->getGiftCount()) > 0">
<div class="content_title_expanded" onclick="hidePanel(this, {$giftCount});">
{_gifts}
</div>
<div>
<div class="content_subtitle">
{tr("gifts", $giftCount)}
<div style="float:right;">
{if OPENVK_ROOT_CONF['openvk']['preferences']['commerce']}
<a href="/gifts?act=pick&user={$user->getId()}">{_send_gift}</a> |
{/if}
<a href="/gifts{$user->getId()}">{_all_title}</a>
</div>
</div>
<div class="content_list long">
<div class="cl_element" style="width: 25%;" n:foreach="$user->getGifts(1, 4) as $giftDescriptor">
{var hideInfo = !is_null($thisUser) ? ($giftDescriptor->anon ? $thisUser->getId() !== $user->getId() : false) : false}
<div class="cl_avatar">
<a href="{$hideInfo ? 'javascript:false' : $giftDescriptor->sender->getURL()}">
<img style="width: 70px; max-height: 70px;"
src="{$giftDescriptor->gift->getImage(2)}"
alt="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}" />
</a>
</div>
</div>
</div>
</div>
</div>
{presenter "openvk!Wall->wallEmbedded", $user->getId()}
<script n:if="isset($thisUser) && $thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)">

View file

@ -33,7 +33,7 @@
{if $comment->canBeDeletedBy($thisUser)}
<a href="/comment{$comment->getId()}/delete">{_"delete"}</a>&nbsp;|
{/if}
<a class="comment-reply">Ответить</a>
<a class="comment-reply">{_"reply"}</a>
<div style="float: right; font-size: .7rem;">
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
<div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div>

View file

@ -0,0 +1,8 @@
{var sender = $notification->getModel(1)}
{var value = (int) explode(" ", $notification->getData(), 2)[0]}
{var message = explode(" ", $notification->getData(), 2)[1]}
<a href="{$sender->getURL()}"><b>{$sender->getCanonicalName()}</b></a> {_transferred_to_you} {tr("points_amount", $value)}.
{if !empty($message)}
{_message}: "{$message}".
{/if}

View file

@ -10,7 +10,8 @@
<td width="54" valign="top">
<img
src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}" />
width="{ifset $compact}25{else}50{/ifset}"
{ifset $compact}class="cCompactAvatars"{/ifset} />
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
<span n:if="$author->isOnline()" class="post-online">
{_online}

View file

@ -57,7 +57,7 @@ class Localizator
$lang = is_null($lang) ? static::DEFAULT_LANG : $lang;
$array = @self::parse(dirname(__FILE__) . "/../../locales/$lang.strings");
return $array[$id] ?? (!empty($array["__fallback"]) ? $this->_($id, $array["__fallback"]) : "@$id");
return $array[$id] ?? "@$id";
}
function export($lang = NULL): ?array

View file

@ -59,6 +59,8 @@ routes:
handler: "User->twoFactorAuthSettings"
- url: "/settings/2fa/disable"
handler: "User->disableTwoFactorAuth"
- url: "/coins_transfer"
handler: "User->coinsTransfer"
- url: "/id{num}"
handler: "User->view"
- url: "/friends{num}"

View file

@ -257,23 +257,29 @@ a {
.album-photo {
position: relative;
background-color: darkgrey;
margin: 4pt;
width: calc(33% - 10pt);
height: 82px;
width: 25%;
max-height: 140px;
margin-bottom: 8px;
text-align: center;
vertical-align: text-top;
display: flex;
align-items: center;
justify-content: center;
}
.album-photo img {
width: 100%;
max-height: 82px;
width: unset;
max-height: 120px !important;
max-width: 83%;
vertical-align: top;
border: 1px #ccc solid;
padding: 8px;
background-color: #fff;
}
.album-photo > .album-photo--delete {
position: absolute;
right: 0;
top: 0;
padding: 5px;
margin: 4px;
color: #fff;
@ -289,6 +295,11 @@ a {
opacity: 1;
}
.album-flex {
display: flex;
flex-wrap: wrap;
}
.name-checkmark {
margin-left: 2pt;
}
@ -307,6 +318,10 @@ a {
cursor: pointer;
}
.profile_link_form {
margin-bottom: 0;
}
#profile_links {
margin: 10px 0;
}
@ -315,6 +330,10 @@ a {
background: #ECECEC;
}
.action_links > .profile_link, .action_links > .profile_link_form > .profile_link {
width: 150px;
}
.page_footer {
margin-left: 95px;
padding-top: 5px;
@ -683,6 +702,44 @@ span {
max-height: 63px;
}
.content_list {
display: flex;
width: 200px;
flex-wrap: wrap;
}
.content_list.long {
width: 397px;
}
.content_list .cl_element {
width: 33%;
}
.content_list.long .cl_element {
width: 16.5%;
}
.content_list .cl_element .cl_avatar {
padding: 7px 7px 0 7px;
text-align: center;
}
.content_list .cl_element .cl_name {
padding: 0 3px;
text-align: center;
display: flex;
flex-direction: column;
}
.content_list .cl_element .cl_name .cl_lname {
font-size: 7pt;
}
.ava {
width: 45px;
}
table.User {
vertical-align: text-top;
}
@ -696,6 +753,10 @@ table.User {
margin-bottom: -12px;
}
.container_gray.bottom {
border-bottom: #ebebeb solid 1px;
}
#auth .container_gray {
margin-left: -10px;
margin-bottom: -10px;
@ -1687,3 +1748,9 @@ body.scrolled .toTop:hover {
border-bottom: #e6e6e6 solid 1px;
padding: 4px;
}
.messagebox-content-header {
background: #F7F7F7;
margin: -20px;
padding: 10px;
}

View file

@ -1,36 +1,37 @@
function tr(string, ...arg) {
function tr(string, ...args) {
let output = window.lang[string];
if(arg.length > 0) {
if(typeof arg[0] == 'number') {
let numberedStringId;
let cardinal = arg[0];
if(args.length > 0) {
if(typeof args[0] === "number") {
const cardinal = args[0];
let numberedString;
switch(cardinal) {
case 0:
numberedString = string + '_zero';
numberedString = string + "_zero";
break;
case 1:
numberedString = string + '_one';
numberedString = string + "_one";
break;
default:
numberedString = string + (cardinal < 5 ? '_few' : '_other');
numberedString = string + (cardinal < 5 ? "_few" : "_other");
}
let newoutput = window.lang[numberedString];
if(newoutput === null) {
newoutput = window.lang[string + '_other'];
if(newoutput === null) {
newoutput = output;
}
}
let newOutput = window.lang[numberedString];
if(newOutput === null)
newOutput = window.lang[string + "_other"];
output = newoutput;
if(newOutput === null)
newOutput = output;
output = newOutput;
}
}
let i = 1;
arg.forEach(element => {
output = output.replace(RegExp('(\\$' + i + ')'), element);
i++;
});
if(output == null)
return "@" + string;
for(const [ i, element ] of Object.entries(args))
output = output.replace(RegExp("(\\$" + (Number(i) + 1) + ")"), element);
return output;
}

View file

@ -171,3 +171,49 @@ function setClubAdminComment(clubId, adminId, hash) {
Function.noop
]);
}
function showCoinsTransferDialog(coinsCount, hash) {
MessageBox(tr("transfer_poins"), `
<div class="messagebox-content-header">
${tr("points_transfer_dialog_header_1")}
${tr("points_transfer_dialog_header_2")} <b>${tr("points_amount", coinsCount)}</b>
</div>
<form action="/coins_transfer" method="post" id="coins_transfer_form" style="margin-top: 30px">
<table cellspacing="7" cellpadding="0" border="0" align="center">
<tbody>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr("receiver_address")}:</span>
</td>
<td>
<input type="text" name="receiver" style="width: 100%;" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr("coins_count")}:</span>
</td>
<td>
<input type="text" name="value" style="width: 100%;" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr("message")}:</span>
</td>
<td>
<textarea name="message" style="width: 100%;"></textarea>
</td>
</tr>
</tbody>
</table>
<input type="hidden" name="hash" value="${hash}" />
</form>
`, [tr("transfer_poins_button"), tr("cancel")], [
() => {
document.querySelector("#coins_transfer_form").submit();
},
Function.noop
]);
}

View file

@ -154,7 +154,7 @@ composer2 install
```bash
cd ..
git clone --recursive https://github.com/openvk/openvk.git
git clone https://github.com/openvk/openvk.git
cd openvk/
composer2 install
cd Web/static/js

View file

@ -0,0 +1 @@
ALTER TABLE `groups` ADD COLUMN `display_topics_above_wall` BOOLEAN NOT NULL DEFAULT FALSE AFTER `everyone_can_create_topics`;

View file

@ -1,3 +1,5 @@
#include <en>
"__locale" = "hy_AM.utf8;hy_AM.UTF-8;Arm";
"__WinEncoding" = "Windows-1251";

View file

@ -1,3 +1,5 @@
#include <ru>
"__locale" = "be_BY.UTF-8;Bel";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -1,3 +1,5 @@
#include <ru>
"__locale" = "be_BY@latin;Bel_Lat";
"__WinEncoding" = "Windows-1251";

View file

@ -1,3 +1,5 @@
#include <en>
"__locale" = "de_DE.UTF-8;Deu";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -179,6 +179,8 @@
"open_post" = "Open post";
"version_incompatibility" = "This attachment could not be displayed. Probably the database is incompatible with the current version of OpenVK.";
"reply" = "Reply";
/* Friends */
"friends" = "Friends";
@ -522,6 +524,31 @@
"usages_total" = "Number of uses";
"usages_left" = "Uses left";
"points_transfer_dialog_header_1" = "You can send as a gift or transfer part of the votes to another person.";
"points_transfer_dialog_header_2" = "Your current balance:";
"points_amount_one" = "1 vote";
"points_amount_other" = "$1 votes";
"transfer_poins" = "Transfer votes";
"transfer_poins_button" = "Transfer votes";
"also_you_can_transfer_points" = "You can also <a href=\"javascript:showCoinsTransferDialog($1, '$2')\">transfer votes</a> to another person.";
"transferred_to_you" = "transferred to you";
"receiver_address" = "Receiver address";
"coins_count" = "Number of votes";
"message" = "Message";
"failed_to_tranfer_points" = "Failed to transfer votes";
"points_transfer_successful" = "You have successfully transferred <b>$1</b> to <b><a href=\"$2\">$3</a></b>.";
"not_all_information_has_been_entered" = "Not all information has been entered.";
"negative_transfer_value" = "We cannot steal votes from another person, sorry.";
"message_is_too_long" = "The message is too long.";
"receiver_not_found" = "The receiver was not found.";
"you_dont_have_enough_points" = "You don't have enough votes.";
/* Gifts */
"gift" = "Gift";
@ -641,6 +668,7 @@
"created" = "Created";
"everyone_can_create_topics" = "Everyone can create topics";
"display_list_of_topics_above_wall" = "Display a list of topics above the wall";
"topic_changes_saved_comment" = "The updated title and settings will appear on the topic page.";
@ -724,6 +752,10 @@
"paginator_page" = "Page $1";
"paginator_next" = "Next";
/* About */
"about_openvk" = "About OpenVK";
/* Dialogs */
"ok" = "OK";

View file

@ -1,3 +1,5 @@
#include <en>
"__locale" = "eo.utf8";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -1,3 +1,5 @@
#include <ru>
"__locale" = "kk_KZ.UTF-8;Kaz";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -1,3 +1,5 @@
#include <en>
"__locale" = "pl_PL.utf8;pl_PL.UTF-8;Pol";
"__WinEncoding" = "Windows-1251";

View file

@ -182,6 +182,8 @@
"open_post" = "Открыть запись";
"version_incompatibility" = "Не удалось отобразить это вложение. Возможно, база данных несовместима с текущей версией OpenVK.";
"reply" = "Ответить";
/* Friends */
"friends" = "Друзья";
@ -544,6 +546,33 @@
"usages_total" = "Количество использований";
"usages_left" = "Осталось использований";
"points_transfer_dialog_header_1" = "Вы можете отправить в подарок или передать часть голосов другому человеку.";
"points_transfer_dialog_header_2" = "Ваш текущий баланс:";
"points_amount_one" = "1 голос";
"points_amount_few" = "$1 голоса";
"points_amount_many" = "$1 голосов";
"points_amount_other" = "$1 голосов";
"transfer_poins" = "Передача голосов";
"transfer_poins_button" = "Передать голоса";
"also_you_can_transfer_points" = "Также вы можете <a href=\"javascript:showCoinsTransferDialog($1, '$2')\">передать голоса</a> другому человеку.";
"transferred_to_you" = "передал вам";
"receiver_address" = "Адрес получателя";
"coins_count" = "Количество голосов";
"message" = "Сообщение";
"failed_to_tranfer_points" = "Не удалось передать голоса";
"points_transfer_successful" = "Вы успешно передали <b>$1 <a href=\"$2\">$3</a></b>.";
"not_all_information_has_been_entered" = "Введена не вся информация.";
"negative_transfer_value" = "Мы не можем украсть голоса у другого человека, извините.";
"message_is_too_long" = "Сообщение слишком длинное.";
"receiver_not_found" = "Получатель не найден.";
"you_dont_have_enough_points" = "У вас недостаточно голосов.";
/* Gifts */
"gift" = "Подарок";
@ -674,6 +703,7 @@
"created" = "Создано";
"everyone_can_create_topics" = "Все могут создавать темы";
"display_list_of_topics_above_wall" = "Отображать список тем над стеной";
"topic_changes_saved_comment" = "Обновлённый заголовок и настройки появятся на странице с темой.";
@ -757,6 +787,10 @@
"paginator_page" = "Страница $1";
"paginator_next" = "Дальше";
/* About */
"about_openvk" = "Об OpenVK";
/* Dialogs */
"ok" = "ОК";

View file

@ -1,6 +1,7 @@
#include <ru>
"__locale" = "ru_UA.utf8;ru_RU.UTF-8;Rus";
"__WinEncoding" = "Windows-1251";
"__fallback" = "ru";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -1,3 +1,5 @@
#include <ru>
"__locale" = "sr_CS.UTF-8;Srb";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -1,3 +1,5 @@
#include <en>
"__locale" = "sr_CS.UTF-8;Srb_Latin";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -1,3 +1,5 @@
#include <en>
"__locale" = "tr_TR.UTF-8;Tur";
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */

View file

@ -1,3 +1,5 @@
#include <ru>
"__locale" = "uk_UA.utf8;Ukr";
"__WinEncoding" = "Windows-1251";