Merge branch 'master' into textarea-refactor

This commit is contained in:
veselcraft 2022-04-26 10:16:24 +03:00
commit 9f699336f7
No known key found for this signature in database
GPG key ID: AED66BC1AC628A4E
175 changed files with 5882 additions and 1523 deletions

2
.gitignore vendored
View file

@ -5,7 +5,7 @@ update.pid.old
Web/static/js/node_modules
tmp/*
!tmp/.gitkeep
!tmp/api-storage
!tmp/themepack_artifacts/.gitkeep
themepacks/*
!themepacks/.gitkeep

8
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View file

@ -0,0 +1,46 @@
<?xml encoding="UTF-8"?>
<!ELEMENT latte (tags,filters,variables,functions)>
<!ATTLIST latte vendor #REQUIRED>
<!ATTLIST latte version #REQUIRED>
<!ELEMENT tags (tag)+>
<!ELEMENT tag (arguments)?>
<!ATTLIST tag name CDATA #REQUIRED>
<!ATTLIST tag type (PAIR|UNPAIRED|UNPAIRED_ATTR|ATTR_ONLY|AUTO_EMPTY) #REQUIRED>
<!ATTLIST tag allowedFilters (true|false) #IMPLIED>
<!ATTLIST tag arguments CDATA #IMPLIED>
<!ATTLIST tag deprecatedMessage CDATA #IMPLIED>
<!ATTLIST tag multiLine (true|false) #IMPLIED>
<!ELEMENT arguments (argument)+>
<!ELEMENT argument EMPTY>
<!ATTLIST argument name #REQUIRED>
<!ATTLIST argument types CDATA #REQUIRED>
<!ATTLIST argument repeatable (true|false) #IMPLIED>
<!ATTLIST argument required (true|false) #IMPLIED>
<!ATTLIST argument validType #IMPLIED>
<!ELEMENT filters (filter)+>
<!ELEMENT filter EMPTY>
<!ATTLIST filter name #REQUIRED>
<!ATTLIST filter description CDATA #IMPLIED>
<!ATTLIST filter arguments CDATA #IMPLIED>
<!ATTLIST filter insertColons #IMPLIED>
<!ELEMENT variables (variable)+>
<!ELEMENT variable EMPTY>
<!ATTLIST variable name #REQUIRED>
<!ATTLIST variable type CDATA #REQUIRED>
<!ELEMENT functions (function)+>
<!ELEMENT function EMPTY>
<!ATTLIST function name #REQUIRED>
<!ATTLIST function arguments CDATA #REQUIRED>
<!ATTLIST function returnType #REQUIRED>
<!ATTLIST function description CDATA #IMPLIED>

View file

@ -0,0 +1,290 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE latte PUBLIC "-//LATTE//Latte plugin XML V0.0.1//EN" "Latte.dtd">
<latte vendor="latte" version="1">
<tags>
<tag name="_" type="AUTO_EMPTY" allowedFilters="true">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="=" type="UNPAIRED" allowedFilters="true">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="block" type="AUTO_EMPTY" allowedFilters="true" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="breakIf" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="capture" type="PAIR" allowedFilters="true" multiLine="true">
<arguments>
<argument name="variable" types="VARIABLE_DEFINITION" required="true" />
</arguments>
</tag>
<tag name="case" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="catch" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="contentType" type="UNPAIRED">
<arguments>
<argument name="content-type" types="CONTENT_TYPE" validType="string" required="true" />
</arguments>
</tag>
<tag name="continueIf" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="debugbreak" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="default" type="UNPAIRED">
<arguments>
<argument name="variable" types="VARIABLE_DEFINITION_EXPRESSION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="define" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" required="true" />
<argument name="variable" types="VARIABLE_DEFINITION_ITEM" repeatable="true" />
</arguments>
</tag>
<tag name="do" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="dump" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="else" type="UNPAIRED_ATTR" />
<tag name="elseif" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="elseifset" type="UNPAIRED">
<arguments>
<argument name="var" types="VARIABLE,BLOCK" validType="string" required="true" />
</arguments>
</tag>
<tag name="extends" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION,NONE" validType="string" required="true" />
</arguments>
</tag>
<tag name="first" type="PAIR">
<arguments>
<argument name="width" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="int" required="true" />
</arguments>
</tag>
<tag name="for" type="PAIR" arguments="initialization; condition; afterthought" multiLine="true" />
<tag name="foreach" type="PAIR" arguments="expression as [$key =>] $value" allowedFilters="true" multiLine="true" />
<tag name="if" type="PAIR">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="ifset" type="PAIR">
<arguments>
<argument name="var" types="VARIABLE,BLOCK,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="import" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="include" type="UNPAIRED" allowedFilters="true">
<arguments>
<argument name="file" types="BLOCK,IDENTIFIER,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="KEY_VALUE" repeatable="true" />
</arguments>
</tag>
<tag name="includeblock" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="l" type="UNPAIRED" />
<tag name="last" type="PAIR">
<arguments>
<argument name="width" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="int" required="true" />
</arguments>
</tag>
<tag name="layout" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION,NONE" validType="string" required="true" />
</arguments>
</tag>
<tag name="class" type="ATTR_ONLY" arguments="class" />
<tag name="attr" type="ATTR_ONLY" arguments="attr" />
<tag name="ifcontent" type="ATTR_ONLY" />
<tag name="php" type="UNPAIRED">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" />
</arguments>
</tag>
<tag name="r" type="UNPAIRED" />
<tag name="sandbox" type="UNPAIRED">
<arguments>
<argument name="file" types="BLOCK,PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
<argument name="key-value" types="KEY_VALUE" repeatable="true" />
</arguments>
</tag>
<tag name="sep" type="PAIR">
<arguments>
<argument name="width" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="int" />
</arguments>
</tag>
<tag name="snippet" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" />
</arguments>
</tag>
<tag name="snippetArea" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="spaceless" type="PAIR" />
<tag name="switch" type="PAIR" multiLine="true">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" />
</arguments>
</tag>
<tag name="syntax" type="PAIR" arguments="off | double | latte" multiLine="true" />
<tag name="templatePrint" type="UNPAIRED">
<arguments>
<argument name="class-name" types="PHP_CLASS_NAME" />
</arguments>
</tag>
<tag name="templateType" type="UNPAIRED">
<arguments>
<argument name="class-name" types="PHP_CLASS_NAME" required="true" />
</arguments>
</tag>
<tag name="try" type="PAIR" />
<tag name="rollback" type="UNPAIRED" />
<tag name="tag" type="ATTR_ONLY">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" validType="string" repeatable="true" />
</arguments>
</tag>
<tag name="ifchanged" type="PAIR">
<arguments>
<argument name="expression" types="PHP_EXPRESSION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="skipIf" type="UNPAIRED">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="var" type="UNPAIRED">
<arguments>
<argument name="variable" types="VARIABLE_DEFINITION_EXPRESSION" required="true" repeatable="true" />
</arguments>
</tag>
<tag name="trace" type="UNPAIRED" />
<tag name="varPrint" type="UNPAIRED" arguments="all" />
<tag name="varType" type="UNPAIRED">
<arguments>
<argument name="file" types="PHP_TYPE" required="true" />
<argument name="variable" types="VARIABLE_DEFINITION" required="true" />
</arguments>
</tag>
<tag name="while" type="PAIR" multiLine="true">
<arguments>
<argument name="condition" types="PHP_CONDITION" validType="bool" required="true" />
</arguments>
</tag>
<tag name="iterateWhile" type="PAIR" multiLine="true" />
<tag name="embed" type="PAIR" multiLine="true">
<arguments>
<argument name="file" types="BLOCK_USAGE,PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
<argument name="key-value" types="KEY_VALUE" repeatable="true" />
</arguments>
</tag>
<!-- @deprecated - latte -->
<tag name="assign" type="UNPAIRED" arguments="$variable = expr" />
<tag name="truncate" type="UNPAIRED" arguments="expression" deprecatedMessage="Tag {? ...} is deprecated in Latte 2.4. For variable definitions use {var ...} or {php ...} in other cases." />
</tags>
<filters>
<filter name="truncate" arguments=":($length, $append = '…')" description="shortens the length preserving whole words" insertColons=":" />
<filter name="substr" arguments=":($offset [, $length])" description="returns part of the string" insertColons=":" />
<filter name="trim" arguments=":($charset = mezery)" description="strips whitespace or other characters from the beginning and end of the string" />
<filter name="stripHtml" arguments="" description="removes HTML tags and converts HTML entities to text" />
<filter name="strip" arguments="" description="removes whitespace" />
<filter name="indent" arguments=":($level = 1, $char = '\t')" description="indents the text from left with number of tabs" />
<filter name="replace" arguments=":($search, $replace = '')" description="replaces all occurrences of the search string with the replacement" insertColons=":" />
<filter name="replaceRE" arguments=":($pattern, $replace = '')" description="replaces all occurrences according to regular expression" insertColons=":" />
<filter name="padLeft" arguments=":($length, $pad = ' ')" description="completes the string to given length from left" insertColons=":" />
<filter name="padRight" arguments=":($length, $pad = ' ')" description="completes the string to given length from right" insertColons=":" />
<filter name="repeat" arguments=":($count)" description="repeats the string" insertColons=":" />
<filter name="implode" arguments=":($glue = '')" description="joins an array to a string" />
<filter name="webalize" description="adjusts the UTF-8 string to the shape used in the URL" />
<filter name="breaklines" description="inserts HTML line breaks before all newlines" />
<filter name="reverse" description="reverse an UTF-8 string or array" />
<filter name="length" description="returns length of a string or array" />
<filter name="sort" description="simply sorts array" />
<filter name="reverse" description="array sorted in reverse order (used with |sort)" />
<filter name="batch" arguments=":($array, $length [, $item])" description="returns length of a string or array" insertColons="::" />
<filter name="clamp" description="returns value clamped to the inclusive range of min and max." insertColons="::" />
<filter name="lower" description="makes a string lower case" />
<filter name="upper" description="makes a string upper case" />
<filter name="firstUpper" description="makes the first letter upper case" />
<filter name="capitalize" description="lower case, the first letter of each word upper case" />
<filter name="date" arguments=":($format)" description="formats date" insertColons=":" />
<filter name="number" arguments=":($decimals = 0, $decPoint = '.', $thousandsSep = ',')" description="format number" />
<filter name="bytes" arguments=":($precision = 2)" description="formats size in bytes" />
<filter name="dataStream" arguments=":($mimetype = 'detect')" description="Data URI protocol conversion" />
<filter name="noescape" description="prints a variable without escaping" />
<filter name="escapeurl" description="escapes parameter in URL" />
<filter name="nocheck" description="prevents automatic URL sanitization" />
<filter name="checkurl" description="sanitizes string for use inside href attribute" />
<filter name="query" description="generates a query string in the URL" />
<filter name="ceil" arguments=":(int $precision = 0)" description="rounds a number up to a given precision" />
<filter name="explode" arguments=":(string $separator = '')" description="splits a string by the given delimiter" />
<filter name="first" description="returns first element of array or character of string" />
<filter name="floor" arguments=":(int $precision = 0)" description="rounds a number down to a given precision" />
<filter name="join" arguments=":(string $glue = '')" description="joins an array to a string" />
<filter name="last" description="returns last element of array or character of string" />
<filter name="random" description="returns random element of array or character of string" />
<filter name="round" arguments=":(int $precision = 0)" description="rounds a number to a given precision" />
<filter name="slice" arguments=":(int $start, int $length = null, bool $preserveKeys = false)" description="extracts a slice of an array or a string" insertColons=":" />
<filter name="spaceless" description="removes whitespace" />
<filter name="split" arguments=":(string $separator = '')" description="splits a string by the given delimiter" />
</filters>
<functions>
<function name="clamp" returnType="int|float" arguments="(int|float $value, int|float $min, int|float $max)" description="clamps value to the inclusive range of min and max" />
<function name="divisibleBy" returnType="bool" arguments="(int $value)" description="checks if a variable is divisible by a number" />
<function name="even" returnType="bool" arguments="(int $value)" description="checks if the given number is even" />
<function name="first" returnType="mixed" arguments="(string|array $value)" description="returns first element of array or character of string" />
<function name="last" returnType="mixed" arguments="(string|array $value)" description="returns last element of array or character of string" />
<function name="odd" returnType="bool" arguments="(int $value)" description="checks if the given number is odd" />
<function name="slice" returnType="string|array" arguments="(string|array $value, int $start, int $length = null, bool $preserveKeys = false)" description="extracts a slice of an array or a string" />
</functions>
</latte>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE latte PUBLIC "-//LATTE//Latte plugin configuration XML V0.0.1//EN" "Latte.dtd">
<latte version="1" vendor="nette/application">
<tags>
<!-- nette/application tags -->
<tag name="cache" type="PAIR" arguments="if => expr, key, …">
<arguments>
<argument name="name[:part]" types="KEY_VALUE" validType="string" required="true" />
<argument name="arguments" types="PHP_EXPRESSION" repeatable="true" />
</arguments>
</tag>
<tag name="control" type="UNPAIRED">
<arguments>
<argument name="name[:part]" types="PHP_IDENTIFIER,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="PHP_EXPRESSION" repeatable="true" />
</arguments>
</tag>
<tag name="link" type="UNPAIRED">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
<tag name="href" type="ATTR_ONLY">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
<tag name="nonce" type="ATTR_ONLY" />
<tag name="plink" type="UNPAIRED">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
<!-- @deprecated - nette/application -->
<tag name="ifCurrent" type="PAIR" deprecatedMessage="Tag {ifCurrent} is deprecated in Latte 2.6. Use custom function isLinkCurrent() instead.">
<arguments>
<argument name="destination" types="LINK_DESTINATION,PHP_EXPRESSION" validType="string" required="true" />
<argument name="arguments" types="LINK_PARAMETERS" repeatable="true" />
</arguments>
</tag>
</tags>
<variables>
<variable name="control" type="\Nette\Application\UI\Control" />
<variable name="basePath" type="string" />
<variable name="baseUrl" type="string" />
<variable name="flashes" type="mixed[]" />
<variable name="presenter" type="\Nette\Application\UI\Presenter" />
<variable name="iterator" type="\Latte\Runtime\CachingIterator" />
<variable name="form" type="\Nette\Application\UI\Form" />
<variable name="user" type="\Nette\Security\User" />
</variables>
<functions>
<function name="isLinkCurrent" returnType="bool" arguments="(string $destination = null, $args = [])" />
<function name="isModuleCurrent" returnType="bool" arguments="(string $moduleName)" />
</functions>
</latte>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE latte PUBLIC "-//LATTE//Latte plugin configuration XML V0.0.1//EN" "Latte.dtd">
<latte version="1" vendor="nette/forms">
<tags>
<tag name="form" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="formContainer" type="PAIR" multiLine="true">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="formPrint" type="UNPAIRED">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="input" type="UNPAIRED">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="inputError" type="UNPAIRED">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="label" type="AUTO_EMPTY">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
<tag name="name" type="ATTR_ONLY">
<arguments>
<argument name="name" types="PHP_IDENTIFIER,VARIABLE,CONTROL,PHP_EXPRESSION" validType="string" required="true" />
</arguments>
</tag>
</tags>
</latte>

6
.idea/misc.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/openvk.iml" filepath="$PROJECT_DIR$/.idea/openvk.iml" />
</modules>
</component>
</project>

46
.idea/openvk.iml Normal file
View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/vendor/rybakit/msgpack" />
<excludeFolder url="file://$MODULE_DIR$/vendor/chillerlan/php-qrcode" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/chillerlan/php-settings-container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/vearutop/php-obscene-censor-rus" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/scssphp/scssphp" />
<excludeFolder url="file://$MODULE_DIR$/vendor/bhaktaraz/php-rss-generator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/erusev/parsedown" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ezyang/htmlpurifier" />
<excludeFolder url="file://$MODULE_DIR$/vendor/whichbrowser/parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/komeiji-satori/curl" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/james-heinrich/getid3" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
<excludeFolder url="file://$MODULE_DIR$/vendor/wapmorgan/binary-stream" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/al/emoji-detector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/lfkeitel/phptotp" />
<excludeFolder url="file://$MODULE_DIR$/vendor/zadarma/user-api-v1" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/wapmorgan/morphos" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

48
.idea/php.xml Normal file
View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/rybakit/msgpack" />
<path value="$PROJECT_DIR$/vendor/chillerlan/php-qrcode" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/chillerlan/php-settings-container" />
<path value="$PROJECT_DIR$/vendor/vearutop/php-obscene-censor-rus" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
<path value="$PROJECT_DIR$/vendor/scssphp/scssphp" />
<path value="$PROJECT_DIR$/vendor/bhaktaraz/php-rss-generator" />
<path value="$PROJECT_DIR$/vendor/erusev/parsedown" />
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
<path value="$PROJECT_DIR$/vendor/whichbrowser/parser" />
<path value="$PROJECT_DIR$/vendor/komeiji-satori/curl" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/james-heinrich/getid3" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
<path value="$PROJECT_DIR$/vendor/wapmorgan/binary-stream" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
<path value="$PROJECT_DIR$/vendor/al/emoji-detector" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
<path value="$PROJECT_DIR$/vendor/lfkeitel/phptotp" />
<path value="$PROJECT_DIR$/vendor/zadarma/user-api-v1" />
<path value="$PROJECT_DIR$/../../../chandler" />
<path value="$PROJECT_DIR$/../../../vendor" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/symfony/string" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php73" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/wapmorgan/morphos" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
</project>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -0,0 +1,86 @@
<?php declare(strict_types=1);
namespace openvk\CLI;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\Photos;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Nette\Utils\ImageException;
class RebuildImagesCommand extends Command
{
private $images;
protected static $defaultName = "build-images";
function __construct()
{
$this->images = DatabaseConnection::i()->getContext()->table("photos");
parent::__construct();
}
protected function configure(): void
{
$this->setDescription("Create resized versions of images")
->setHelp("This command allows you to resize all your images after configuration change")
->addOption("upgrade-only", "U", InputOption::VALUE_NEGATABLE, "Only upgrade images which aren't resized?");
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$header = $output->section();
$counter = $output->section();
$header->writeln([
"Image Rebuild Utility",
"=====================",
"",
]);
$filter = ["deleted" => false];
if($input->getOption("upgrade-only"))
$filter["sizes"] = NULL;
$selection = $this->images->select("id")->where($filter);
$totalPics = $selection->count();
$header->writeln([
"Total of $totalPics images found.",
"",
]);
$errors = 0;
$count = 0;
$avgTime = NULL;
$begin = new \DateTimeImmutable("now");
foreach($selection as $idHolder) {
$start = microtime(true);
try {
$photo = (new Photos)->get($idHolder->id);
$photo->getSizes(true, true);
$photo->getDimensions();
} catch(ImageException $ex) {
$errors++;
}
$timeConsumed = microtime(true) - $start;
if(!$avgTime)
$avgTime = $timeConsumed;
else
$avgTime = ($avgTime + $timeConsumed) / 2;
$eta = $begin->getTimestamp() + ceil($totalPics * $avgTime);
$int = (new \DateTimeImmutable("now"))->diff(new \DateTimeImmutable("@$eta"));
$int = $int->d . "d" . $int->h . "h" . $int->i . "m" . $int->s . "s";
$pct = floor(100 * ($count / $totalPics));
$counter->overwrite("Processed " . ++$count . " images... ($pct% $int left $errors/$count fail)");
}
$counter->overwrite("Processing finished :3");
return Command::SUCCESS;
}
}

View file

@ -6,25 +6,27 @@ _[Русский](README_RU.md)_
VKontakte belongs to Pavel Durov and VK Group.
To be honest, we don't even know whether it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug-tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OVK account for this).
To be honest, we don't know whether it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug-tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OVK account for this).
## When's the release?
Please use the master branch, as it has the most changes.
Updating the source code is done with this command: `git pull`
We will release OpenVK as soon as it's ready. As for now you can:
* `git clone` this repo's master branch (use `git pull` to update)
* Grab a prebuilt OpenVK distro from [GitHub artifacts](https://nightly.link/openvk/archive/workflows/nightly/master/OpenVK%20Archive.zip)
## Instances
* **[openvk.su](https://openvk.su/)**
* **[openvk.uk](https://openvk.uk)** - official mirror of openvk.su (<https://t.me/openvkch/1609>)
* **[openvk.co](http://openvk.co)** - yet another official mirror of openvk.su without TLS (<https://t.me/openvkch/1654>)
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
* [vepurovk.xyz](http://vepurovk.xyz/)
## Can I create my own OpenVK instance?
Yes! And you're very welcome to.
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. this extensions are available on most of ISPManager hostings).
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. these extensions are available on most of ISPManager hostings).
If you want, you can add your instance to the list above so that people can register there.
@ -32,42 +34,47 @@ If you want, you can add your instance to the list above so that people can regi
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
* PHP 8 has **not** yet been tested, so you should not expect it to work.
* PHP 8 has **not** yet been tested, so you should not expect it to work. (edit: it does not work).
2. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
2. Install MySQL-compatible database.
* We recommend using Percona Server, but any MySQL-compatible server should work
* Server should be compatible with at least MySQL 5.6, MySQL 8.0+ recommended.
* Support for MySQL 4.1+ is WIP, replace `utf8mb4` and `utf8mb4_unicode_520_ci` with `utf8` and `utf8_unicode_ci` in SQLs.
3. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
```bash
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:
4. And enable them:
```bash
ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
```
4. Import `install/init-static-db.sql` to **same database** you installed Chandler to and import all sqls from `install/sqls` to **same database**
5. Import `install/init-event-db.sql` to **separate database**
6. Copy `openvk-example.yml` to `openvk.yml` and change options
7. Run `composer install` in OpenVK directory
8. Move to `Web/static/js` and execute `yarn install`
9. Set `openvk` as your root app in `chandler.yml`
5. Import `install/init-static-db.sql` to the **same database** you installed Chandler to and import all sqls from `install/sqls` to the **same database**
6. Import `install/init-event-db.sql` to a **separate database** (Yandex.Clickhouse can also be used, highly recommended)
7. Copy `openvk-example.yml` to `openvk.yml` and change options to your liking
8. Run `composer install` in OpenVK directory
9. Run `composer install` in commitcaptcha directory
10. Move to `Web/static/js` and execute `yarn install`
11. Set `openvk` as your root app in `chandler.yml`
Once you are done, you can login as a system administrator on the network itself (no registration required):
* **Login**: `admin@localhost.localdomain6`
* **Password**: `admin`
* It is recommended to change the password before using the built-in account.
* It is recommended to change the password of the built-in account or disable it.
Full example installation instruction for CentOS 8 is also available [here](https://docs.openvk.su/openvk_engine/centos8_installation/).
💡Confused? Full installation walkthrough is available [here](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)).
### If my website uses OpenVK, should I publish it's sources?
### If my website uses OpenVK, should I release it's sources?
You are encouraged to do so. We don't enforce this though. You can keep your sources to yourself (unless you distribute your OpenVK distro to other people).
You also not required to publish source texts of your themepacks and plugins.
It depends. You can keep the sources to yourself if you do not plan to distribute your website binaries. If your website software must be distributed, it can stay non-OSS provided the OpenVK is not used as a primary application and is not modified. If you modified OpenVK for your needs or your work is based on it and you're planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc).
## Where can I get assistance?
@ -80,7 +87,7 @@ You may reach out to us via:
* [Discussions](https://github.com/openvk/openvk/discussions)
* Matrix chat: #openvk:matrix.org
**Attention**: bug tracker, telegram and matrix chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
**Attention**: bug tracker, board, telegram and matrix chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
<a href="https://codeberg.org/OpenVK/openvk">
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">

View file

@ -2,23 +2,25 @@
_[English](README.md)_
**OpenVK** это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. Представленный здесь код пока не стабилен.
**OpenVK** - это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент представленный здесь исходный код проекта пока не является стабильным.
ВКонтакте принадлежит Павлу Дурову и VK Group.
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OVK).
## Когда релиз?
## Когда выйдет релизная версия?
Пожалуйста, используйте ветку master, так как в ней больше всего изменений.
Обновление исходного кода выполняется с помощью этой команды: `git pull`.
Мы выпустим OpenVK, как только он будет готов. На данный момент Вы можете:
* Склонировать master ветку репозитория командой `git clone` (используйте `git pull` для обновления)
* Взять готовую сборку OpenVK из [GitHub Actions](https://nightly.link/openvk/archive/workflows/nightly/master/OpenVK%20Archive.zip)
## Инстанции
* **[openvk.su](https://openvk.su/)**
* **[openvk.uk](https://openvk.uk)** - официальное зеркало openvk.su (<https://t.me/openvkch/1609>)
* **[openvk.co](http://openvk.co)** - ещё одно официальное зеркало openvk.su без TLS (<https://t.me/openvkch/1654>)
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
* [vepurovk.xyz](http://vepurovk.xyz/)
## Могу ли я создать свою собственную инстанцию OpenVK?
@ -32,42 +34,47 @@ _[English](README.md)_
1. Установите PHP 7.4, веб-сервер, Composer, Node.js, Yarn и [Chandler](https://github.com/openvk/chandler)
* PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать.
* PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать (UPD: он не работает).
2. Установите [commitcaptcha](https://github.com/openvk/commitcaptcha) и OpenVK в качестве расширений Chandler следующим образом:
2. Установите MySQL-совместимую базу данных.
* Мы рекомендуем использовать Persona Server, но любая MySQL-совместимая база данных должна работать
* Сервер должен поддерживать хотя бы MySQL 5.6, рекомендуется использовать MySQL 8.0+.
* Поддержка для MySQL 4.1+ находится в процессе, а пока замените `utf8mb4` и `utf8mb4_unicode_520_ci` на `utf8` и `utf8_unicode_ci` в SQL-файлах, соответственно.
3. Установите [commitcaptcha](https://github.com/openvk/commitcaptcha) и OpenVK в качестве расширений Chandler:
```bash
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. И включите их:
4. И включите их:
```bash
ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
```
4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных**
5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных**
6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры
7. Запустите `composer install` в директории OpenVK
8. Перейдите в `Web/static/js` и выполните `yarn install`
9. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
5. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных**
6. Импортируйте `install/init-event-db.sql` в **отдельную базу данных** (Яндекс.Clickhouse также может быть использован, настоятельно рекомендуется)
7. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры под свои нужды
8. Запустите `composer install` в директории OpenVK
9. Запустите `composer install` в директории commitcaptcha
10. Перейдите в `Web/static/js` и выполните `yarn install`
11. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
После этого вы можете войти как системный администратор в саму сеть (регистрация не требуется):
* **Логин**: `admin@localhost.localdomain6`
* **Пароль**: `admin`
* Перед использованием встроенной учетной записи рекомендуется сменить пароль.
* Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её.
Полный пример инструкции по установке CentOS 8 также доступен [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/).
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
### Если мой сайт использует OpenVK, должен ли я публиковать его исходные тексты?
Вам рекомендуется это делать. Однако мы не следим за этим. Вы можете держать свои исходные тексты при себе (если только вы не распространяете свой дистрибутив OpenVK среди других людей).
Вы также не обязаны публиковать исходные тексты ваших тематических пакетов и плагинов.
Это зависит от обстоятельств. Вы можете оставить исходные тексты при себе, если не планируете распространять бинарники вашего сайта. Если программное обеспечение вашего сайта должно распространяться, оно может оставаться не-OSS при условии, что OpenVK не используется в качестве основного приложения и не модифицируется. Если вы модифицировали OpenVK для своих нужд или ваша работа основана на нем и вы планируете ее распространять, то вы должны лицензировать ее на условиях любой совместимой с LGPL лицензии (например, OSL, GPL, LGPL и т.д.).
## Где я могу получить помощь?
@ -80,7 +87,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* [Обсуждения](https://github.com/openvk/openvk/discussions)
* Чат в Matrix: #ovk:matrix.org
**Внимание**: баг-трекер, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [at] tutanota [dot] com**.
**Внимание**: баг-трекер, форум, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собака] tutanota [точка] com**.
<a href="https://codeberg.org/OpenVK/openvk">
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">

View file

@ -60,7 +60,7 @@ final class Account extends VKAPIRequestHandler
return 1;
}
function getAppPermissions(): object
function getAppPermissions(): int
{
return 9355263;
}

View file

@ -25,7 +25,7 @@ final class Friends extends VKAPIRequestHandler
$usersApi = new Users($this->getUser());
if (!is_null($fields)) {
$response = $usersApi->get(implode(',', $friends), $fields, 0, $count, true); // FIXME
$response = $usersApi->get(implode(',', $friends), $fields, 0, $count); // FIXME
}
return (object) [

View file

@ -88,4 +88,100 @@ final class Groups extends VKAPIRequestHandler
"items" => $rClubs
];
}
function getById(string $group_ids = "", string $group_id = "", string $fields = ""): ?array
{
$this->requireUser();
$clubs = new ClubsRepo;
if ($group_ids == null && $group_id != null)
$group_ids = $group_id;
if ($group_ids == null && $group_id == null)
$this->fail(100, "One of the parameters specified was missing or invalid: group_ids is undefined");
$clbs = explode(',', $group_ids);
$response;
$ic = sizeof($clbs);
for ($i=0; $i < $ic; $i++) {
if($i > 500)
break;
if($clbs[$i] < 0)
$this->fail(100, "ты ошибся чутка, у айди группы убери минус");
$clb = $clubs->get((int) $clbs[$i]);
if(is_null($clb))
{
$response[$i] = (object)[
"id" => intval($clbs[$i]),
"name" => "DELETED",
"screen_name" => "club".intval($clbs[$i]),
"type" => "group",
"description" => "This group was deleted or it doesn't exist"
];
}else if($clbs[$i] == null){
}else{
$response[$i] = (object)[
"id" => $clb->getId(),
"name" => $clb->getName(),
"screen_name" => $clb->getShortCode() ?? "club".$clb->getId(),
"is_closed" => false,
"type" => "group",
"can_access_closed" => true,
];
$flds = explode(',', $fields);
foreach($flds as $field) {
switch ($field) {
case 'verified':
$response[$i]->verified = intval($clb->isVerified());
break;
case 'has_photo':
$response[$i]->has_photo = is_null($clb->getAvatarPhoto()) ? 0 : 1;
break;
case 'photo_max_orig':
$response[$i]->photo_max_orig = $clb->getAvatarURL();
break;
case 'photo_max':
$response[$i]->photo_max = $clb->getAvatarURL();
break;
case 'members_count':
$response[$i]->members_count = $clb->getFollowersCount();
break;
case 'site':
$response[$i]->site = $clb->getWebsite();
break;
case 'description':
$response[$i]->desctiption = $clb->getDescription();
break;
case 'contacts':
$contacts;
$contactTmp = $clb->getManagers(1, true);
foreach($contactTmp as $contact) {
$contacts[] = array(
'user_id' => $contact->getUser()->getId(),
'desc' => $contact->getComment()
);
}
$response[$i]->contacts = $contacts;
break;
case 'can_post':
if($clb->canBeModifiedBy($this->getUser()))
$response[$i]->can_post = true;
else
$response[$i]->can_post = $clb->canPost();
break;
}
}
}
}
return $response;
}
}

74
VKAPI/Handlers/Likes.php Normal file
View file

@ -0,0 +1,74 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Posts as PostsRepo;
final class Likes extends VKAPIRequestHandler
{
function add(string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
switch ($type) {
case 'post':
$post = (new PostsRepo)->getPostById($owner_id, $item_id);
if (is_null($post)) $this->fail(100, 'One of the parameters specified was missing or invalid: object not found');
$post->setLike(true, $this->getUser());
return (object)[
"likes" => $post->getLikesCount()
];
break;
default:
$this->fail(100, 'One of the parameters specified was missing or invalid: incorrect type');
break;
}
}
function remove(string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
switch ($type) {
case 'post':
$post = (new PostsRepo)->getPostById($owner_id, $item_id);
if (is_null($post)) $this->fail(100, 'One of the parameters specified was missing or invalid: object not found');
$post->setLike(false, $this->getUser());
return (object)[
"likes" => $post->getLikesCount()
];
break;
default:
$this->fail(100, 'One of the parameters specified was missing or invalid: incorrect type');
break;
}
}
function isLiked(int $user_id, string $type, int $owner_id, int $item_id): object
{
$this->requireUser();
switch ($type) {
case 'post':
$user = (new UsersRepo)->get($user_id);
if (is_null($user)) return (object)[
"liked" => 0,
"copied" => 0,
"sex" => 0
];
$post = (new PostsRepo)->getPostById($owner_id, $item_id);
if (is_null($post)) $this->fail(100, 'One of the parameters specified was missing or invalid: object not found');
return (object)[
"liked" => (int) $post->hasLikeFrom($user),
"copied" => 0 // TODO: handle this
];
break;
default:
$this->fail(100, 'One of the parameters specified was missing or invalid: incorrect type');
break;
}
}
}

View file

@ -4,6 +4,7 @@ use openvk\Web\Events\NewMessageEvent;
use openvk\Web\Models\Entities\{Correspondence, Message};
use openvk\Web\Models\Repositories\{Messages as MSGRepo, Users as USRRepo};
use openvk\VKAPI\Structures\{Message as APIMsg, Conversation as APIConvo};
use openvk\VKAPI\Handlers\Users as APIUsers;
use Chandler\Signaling\SignalManager;
final class Messages extends VKAPIRequestHandler
@ -48,10 +49,12 @@ final class Messages extends VKAPIRequestHandler
$rMsg->read_state = 1;
$rMsg->out = (int) ($message->getSender()->getId() === $this->getUser()->getId());
$rMsg->body = $message->getText(false);
$rMsg->text = $message->getText(false);
$rMsg->emoji = true;
if($preview_length > 0)
$rMsg->body = ovk_proc_strtr($rMsg->body, $preview_length);
$rMsg->text = ovk_proc_strtr($rMsg->text, $preview_length);
$items[] = $rMsg;
}
@ -145,12 +148,14 @@ final class Messages extends VKAPIRequestHandler
return 1;
}
function getConversations(int $offset = 0, int $count = 20, string $filter = "all", int $extended = 0): object
function getConversations(int $offset = 0, int $count = 20, string $filter = "all", int $extended = 0, string $fields = ""): object
{
$this->requireUser();
$convos = (new MSGRepo)->getCorrespondencies($this->getUser(), -1, $count, $offset);
$list = [];
$users = [];
foreach($convos as $convo) {
$correspondents = $convo->getCorrespondents();
if($correspondents[0]->getId() === $this->getUser()->getId())
@ -189,7 +194,13 @@ final class Messages extends VKAPIRequestHandler
$lastMessagePreview->read_state = 1;
$lastMessagePreview->out = (int) ($lastMessage->getSender()->getId() === $this->getUser()->getId());
$lastMessagePreview->body = $lastMessage->getText(false);
$lastMessagePreview->text = $lastMessage->getText(false);
$lastMessagePreview->emoji = true;
if($extended == 1) {
$users[] = $lastMessage->getSender()->getId();
$users[] = $author;
}
}
$list[] = [
@ -198,10 +209,20 @@ final class Messages extends VKAPIRequestHandler
];
}
return (object) [
"count" => sizeof($list),
"items" => $list,
];
if($extended == 0){
return (object) [
"count" => sizeof($list),
"items" => $list,
];
} else {
$users = array_unique($users);
return (object) [
"count" => sizeof($list),
"items" => $list,
"profiles" => (new APIUsers)->get(implode(',', $users), $fields, $offset, $count)
];
}
}
function getHistory(int $offset = 0, int $count = 20, int $user_id = -1, int $peer_id = -1, int $start_message_id = 0, int $rev = 0, int $extended = 0): object
@ -230,6 +251,7 @@ final class Messages extends VKAPIRequestHandler
$rMsg->read_state = 1;
$rMsg->out = (int) ($msgU->sender_id === $this->getUser()->getId());
$rMsg->body = $message->getText(false);
$rMsg->text = $message->getText(false);
$rMsg->emoji = true;
$results[] = $rMsg;

View file

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Entities\Postable;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\VKAPI\Handlers\Wall;
final class Newsfeed extends VKAPIRequestHandler
{
function get(string $fields = "", int $start_from = 0, int $offset = 0, int $count = 30, int $extended = 0)
{
$this->requireUser();
if($offset != 0) $start_from = $offset;
$id = $this->getUser()->getId();
$subs = DatabaseConnection::i()
->getContext()
->table("subscriptions")
->where("follower", $id);
$ids = array_map(function($rel) {
return $rel->target * ($rel->model === "openvk\Web\Models\Entities\User" ? 1 : -1);
}, iterator_to_array($subs));
$ids[] = $this->getUser()->getId();
$posts = DatabaseConnection::i()
->getContext()
->table("posts")
->select("id")
->where("wall IN (?)", $ids)
->where("deleted", 0)
->order("created DESC");
$rposts = [];
foreach($posts->page((int) ($offset + 1), $count) as $post)
$rposts[] = (new PostsRepo)->get($post->id)->getPrettyId();
return (new Wall)->getById(implode(',', $rposts), $extended, $fields, $this->getUser());
}
}

230
VKAPI/Handlers/Photos.php Normal file
View file

@ -0,0 +1,230 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use Nette\InvalidStateException;
use Nette\Utils\ImageException;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Repositories\Albums;
use openvk\Web\Models\Repositories\Clubs;
final class Photos extends VKAPIRequestHandler
{
private function getPhotoUploadUrl(string $field, int $group = 0, bool $multifile = false): string
{
$secret = CHANDLER_ROOT_CONF["security"]["secret"];
$uploadInfo = [
1,
$field,
(int) $multifile,
0,
time(),
$this->getUser()->getId(),
$group,
0, # this is unused but stays here base64 reasons (X2 doesn't work, so there's dummy value for short)
];
$uploadInfo = pack("vZ10v2P3S", ...$uploadInfo);
$uploadInfo = base64_encode($uploadInfo);
$uploadHash = hash_hmac("sha3-224", $uploadInfo, $secret);
$uploadInfo = rawurlencode($uploadInfo);
return ovk_scheme(true) . $_SERVER["HTTP_HOST"] . "/upload/photo/$uploadHash?$uploadInfo";
}
private function getImagePath(string $photo, string $hash, ?string& $up = NULL, ?string& $group = NULL): string
{
$secret = CHANDLER_ROOT_CONF["security"]["secret"];
if(!hash_equals(hash_hmac("sha3-224", $photo, $secret), $hash))
$this->fail(121, "Incorrect hash");
[$up, $image, $group] = explode("|", $photo);
$imagePath = __DIR__ . "/../../tmp/api-storage/photos/$up" . "_$image.oct";
if(!file_exists($imagePath))
$this->fail(10, "Invalid image");
return $imagePath;
}
function getOwnerPhotoUploadServer(int $owner_id = 0): object
{
$this->requireUser();
if($owner_id < 0) {
$club = (new Clubs)->get(abs($owner_id));
if(!$club)
$this->fail(0404, "Club not found");
else if(!$club->canBeModifiedBy($this->getUser()))
$this->fail(200, "Access: Club can't be 'written' by user");
}
return (object) [
"upload_url" => $this->getPhotoUploadUrl("photo", isset($club) ? 0 : $club->getId()),
];
}
function saveOwnerPhoto(string $photo, string $hash): object
{
$imagePath = $this->getImagePath($photo, $hash, $uploader, $group);
if($group == 0) {
$user = (new \openvk\Web\Models\Repositories\Users)->get((int) $uploader);
$album = (new Albums)->getUserAvatarAlbum($user);
} else {
$club = (new Clubs)->get((int) $group);
$album = (new Albums)->getClubAvatarAlbum($club);
}
try {
$avatar = new Photo;
$avatar->setOwner((int) $uploader);
$avatar->setDescription("Profile photo");
$avatar->setCreated(time());
$avatar->setFile([
"tmp_name" => $imagePath,
"error" => 0,
]);
$avatar->save();
$album->addPhoto($avatar);
unlink($imagePath);
} catch(ImageException | InvalidStateException $e) {
unlink($imagePath);
$this->fail(129, "Invalid image file");
}
return (object) [
"photo_hash" => NULL,
"photo_src" => $avatar->getURL(),
];
}
function getWallUploadServer(?int $group_id = NULL): object
{
$this->requireUser();
$album = NULL;
if(!is_null($group_id)) {
$club = (new Clubs)->get(abs($group_id));
if(!$club)
$this->fail(0404, "Club not found");
else if(!$club->canBeModifiedBy($this->getUser()))
$this->fail(200, "Access: Club can't be 'written' by user");
} else {
$album = (new Albums)->getUserWallAlbum($this->getUser());
}
return (object) [
"upload_url" => $this->getPhotoUploadUrl("photo", $group_id ?? 0),
"album_id" => $album,
"user_id" => $this->getUser()->getId(),
];
}
function saveWallPhoto(string $photo, string $hash, int $group_id = 0, ?string $caption = NULL): array
{
$imagePath = $this->getImagePath($photo, $hash, $uploader, $group);
if($group_id != $group)
$this->fail(8, "group_id doesn't match");
$album = NULL;
if($group_id != 0) {
$uploader = (new \openvk\Web\Models\Repositories\Users)->get((int) $uploader);
$album = (new Albums)->getUserWallAlbum($uploader);
}
try {
$photo = new Photo;
$photo->setOwner((int) $uploader);
$photo->setCreated(time());
$photo->setFile([
"tmp_name" => $imagePath,
"error" => 0,
]);
if (!is_null($caption))
$photo->setDescription($caption);
$photo->save();
unlink($imagePath);
} catch(ImageException | InvalidStateException $e) {
unlink($imagePath);
$this->fail(129, "Invalid image file");
}
if(!is_null($album))
$album->addPhoto($photo);
return [
$photo->toVkApiStruct(),
];
}
function getUploadServer(?int $album_id = NULL): object
{
$this->requireUser();
# Not checking rights to album because save() method will do so anyways
return (object) [
"upload_url" => $this->getPhotoUploadUrl("photo", 0, true),
"album_id" => $album_id,
"user_id" => $this->getUser()->getId(),
];
}
function save(string $photos_list, string $hash, int $album_id = 0, ?string $caption = NULL): object
{
$this->requireUser();
$secret = CHANDLER_ROOT_CONF["security"]["secret"];
if(!hash_equals(hash_hmac("sha3-224", $photos_list, $secret), $hash))
$this->fail(121, "Incorrect hash");
$album = NULL;
if($album_id != 0) {
$album_ = (new Albums)->get($album_id);
if(!$album_)
$this->fail(0404, "Invalid album");
else if(!$album_->canBeModifiedBy($this->getUser()))
$this->fail(15, "Access: Album can't be 'written' by user");
$album = $album_;
}
$pList = json_decode($photos_list);
$imagePaths = [];
foreach($pList as $pDesc)
$imagePaths[] = __DIR__ . "/../../tmp/api-storage/photos/$pDesc->keyholder" . "_$pDesc->resource.oct";
$images = [];
try {
foreach($imagePaths as $imagePath) {
$photo = new Photo;
$photo->setOwner($this->getUser()->getId());
$photo->setCreated(time());
$photo->setFile([
"tmp_name" => $imagePath,
"error" => 0,
]);
if (!is_null($caption))
$photo->setDescription($caption);
$photo->save();
unlink($imagePath);
if(!is_null($album))
$album->addPhoto($photo);
$images[] = $photo->toVkApiStruct();
}
} catch(ImageException | InvalidStateException $e) {
foreach($imagePaths as $imagePath)
unlink($imagePath);
$this->fail(129, "Invalid image file");
}
return (object) [
"count" => sizeof($images),
"items" => $images,
];
}
}

View file

@ -5,13 +5,15 @@ use openvk\Web\Models\Repositories\Users as UsersRepo;
final class Users extends VKAPIRequestHandler
{
function get(string $user_ids = "0", string $fields = "", int $offset = 0, int $count = 100): array
function get(string $user_ids = "0", string $fields = "", int $offset = 0, int $count = 100, User $authuser = null /* костыль(( */): array
{
$this->requireUser();
// $this->requireUser();
if($authuser == null) $authuser = $this->getUser();
$users = new UsersRepo;
if($user_ids == "0")
$user_ids = (string) $this->getUser()->getId();
$user_ids = (string) $authuser->getId();
$usrs = explode(',', $user_ids);
$response;
@ -51,7 +53,7 @@ final class Users extends VKAPIRequestHandler
$response[$i]->verified = intval($usr->isVerified());
break;
case 'sex':
$response[$i]->sex = $this->getUser()->isFemale() ? 1 : 2;
$response[$i]->sex = $usr->isFemale() ? 1 : 2;
break;
case 'has_photo':
$response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1;
@ -60,8 +62,26 @@ final class Users extends VKAPIRequestHandler
$response[$i]->photo_max_orig = $usr->getAvatarURL();
break;
case 'photo_max':
$response[$i]->photo_max = $usr->getAvatarURL();
$response[$i]->photo_max = $usr->getAvatarURL("original");
break;
case 'photo_50':
$response[$i]->photo_50 = $usr->getAvatarURL();
break;
case 'photo_100':
$response[$i]->photo_50 = $usr->getAvatarURL("tiny");
break;
case 'photo_200':
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
break;
case 'photo_200_orig': // вообще не ебу к чему эта строка ну пусть будет кек
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
break;
case 'photo_400_orig':
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
break;
// Она хочет быть выебанной видя матан
// Покайфу когда ты Виет а вокруг лишь дискриминант
case 'status':
if($usr->getStatus() != null)
$response[$i]->status = $usr->getStatus();
@ -71,10 +91,10 @@ final class Users extends VKAPIRequestHandler
$response[$i]->screen_name = $usr->getShortCode();
break;
case 'friend_status':
switch($usr->getSubscriptionStatus($this->getUser())) {
switch($usr->getSubscriptionStatus($authuser)) {
case 3:
case 0:
$response[$i]->friend_status = $usr->getSubscriptionStatus($this->getUser());
$response[$i]->friend_status = $usr->getSubscriptionStatus($authuser);
break;
case 1:
$response[$i]->friend_status = 2;
@ -158,13 +178,14 @@ final class Users extends VKAPIRequestHandler
$users = new UsersRepo;
$array = [];
$find = $users->find($q);
foreach ($users->find($q) as $user) {
foreach ($find as $user) {
$array[] = $user->getId();
}
return (object)[
"count" => $users->getFoundCount($q),
"count" => $find->size(),
"items" => $this->get(implode(',', $array), $fields, $offset, $count)
];
}

View file

@ -22,6 +22,27 @@ final class Wall extends VKAPIRequestHandler
foreach ($posts->getPostsFromUsersWall((int)$owner_id, 1, $count, $offset) as $post) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments;
foreach($post->getChildren() as $attachment)
{
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
{
$attachments[] = [
"type" => "photo",
"photo" => [
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null,
"date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(),
"owner_id" => $attachment->getOwner()->getId(),
"sizes" => array_values($attachment->getVkApiSizes()),
"text" => "",
"has_tags" => false
]
];
}
}
$items[] = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
@ -35,6 +56,7 @@ final class Wall extends VKAPIRequestHandler
"can_archive" => false, // TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"attachments" => $attachments,
"post_source" => (object)["type" => "vk"],
"comments" => (object)[
"count" => $post->getCommentsCount(),
@ -56,6 +78,8 @@ final class Wall extends VKAPIRequestHandler
$profiles[] = $from_id;
else
$groups[] = $from_id * -1;
$attachments = null; // free attachments so it will not clone everythingg
}
if($extended == 1)
@ -110,6 +134,169 @@ final class Wall extends VKAPIRequestHandler
];
}
function getById(string $posts, int $extended = 0, string $fields = "", User $user = null)
{
if($user == null) $user = $this->getUser(); // костыли костыли крылышки
$items = [];
$profiles = [];
$groups = [];
# $count = $posts->getPostCountOnUserWall((int) $owner_id);
$psts = explode(",", $posts);
foreach($psts as $pst)
{
$id = explode("_", $pst);
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]));
if($post) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments;
foreach($post->getChildren() as $attachment)
{
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
{
$attachments[] = [
"type" => "photo",
"photo" => [
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null,
"date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(),
"owner_id" => $attachment->getOwner()->getId(),
"sizes" => array(
[
"height" => 2560,
"url" => $attachment->getURLBySizeId("normal"),
"type" => "m",
"width" => 2560,
],
[
"height" => 130,
"url" => $attachment->getURLBySizeId("tiny"),
"type" => "o",
"width" => 130,
],
[
"height" => 604,
"url" => $attachment->getURLBySizeId("normal"),
"type" => "p",
"width" => 604,
],
[
"height" => 807,
"url" => $attachment->getURLBySizeId("large"),
"type" => "q",
"width" => 807,
],
[
"height" => 1280,
"url" => $attachment->getURLBySizeId("larger"),
"type" => "r",
"width" => 1280,
],
[
"height" => 75, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так
"url" => $attachment->getURLBySizeId("miniscule"),
"type" => "s",
"width" => 75,
]),
"text" => "",
"has_tags" => false
]
];
}
}
$items[] = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
"owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(),
"post_type" => "post",
"text" => $post->getText(),
"can_edit" => 0, // TODO
"can_delete" => $post->canBeDeletedBy($user),
"can_pin" => $post->canBePinnedBy($user),
"can_archive" => false, // TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"post_source" => (object)["type" => "vk"],
"attachments" => $attachments,
"comments" => (object)[
"count" => $post->getCommentsCount(),
"can_post" => 1
],
"likes" => (object)[
"count" => $post->getLikesCount(),
"user_likes" => (int) $post->hasLikeFrom($user),
"can_like" => 1,
"can_publish" => 1,
],
"reposts" => (object)[
"count" => $post->getRepostCount(),
"user_reposted" => 0
]
];
if ($from_id > 0)
$profiles[] = $from_id;
else
$groups[] = $from_id * -1;
$attachments = null; // free attachments so it will not clone everythingg
}
}
if($extended == 1)
{
$profiles = array_unique($profiles);
$groups = array_unique($groups);
$profilesFormatted = [];
$groupsFormatted = [];
foreach ($profiles as $prof) {
$user = (new UsersRepo)->get($prof);
$profilesFormatted[] = (object)[
"first_name" => $user->getFirstName(),
"id" => $user->getId(),
"last_name" => $user->getLastName(),
"can_access_closed" => false,
"is_closed" => false,
"sex" => $user->isFemale() ? 1 : 2,
"screen_name" => $user->getShortCode(),
"photo_50" => $user->getAvatarUrl(),
"photo_100" => $user->getAvatarUrl(),
"online" => $user->isOnline()
];
}
foreach($groups as $g) {
$group = (new ClubsRepo)->get($g);
$groupsFormatted[] = (object)[
"id" => $group->getId(),
"name" => $group->getName(),
"screen_name" => $group->getShortCode(),
"is_closed" => 0,
"type" => "group",
"photo_50" => $group->getAvatarUrl(),
"photo_100" => $group->getAvatarUrl(),
"photo_200" => $group->getAvatarUrl(),
];
}
return (object)[
"items" => (array)$items,
"profiles" => (array)$profilesFormatted,
"groups" => (array)$groupsFormatted
];
}
else
return (object)[
"items" => (array)$items
];
}
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object
{
$this->requireUser();

View file

@ -11,10 +11,11 @@ final class Message
public $out;
public $title = "";
public $body;
public $text;
public $attachments = [];
public $fwd_messages = [];
public $emoji;
public $important = 1;
public $important = true;
public $deleted = 0;
public $random_id = NULL;
}

16
Vagrantfile vendored
View file

@ -1,16 +1,22 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "freebsd/FreeBSD-12.1-STABLE"
config.vm.box = "freebsd/FreeBSD-13.1-RC2"
config.vm.box_version = "2022.04.07"
config.vm.network "forwarded_port", guest: 80, host: 4000
config.vm.synced_folder ".", "/.ovk_release"
config.vm.provider "virtualbox" do |vb|
vb.gui = true
vb.memory = "1024"
vb.cpus = 4
vb.memory = "1568"
end
config.vm.provision "shell", inline: "/bin/tcsh /.ovk_release/install/automated/freebsd-12/install"
config.vm.provider "vmware_workstation" do |vwx|
vwx.gui = true
vwx.vmx["memsize"] = "1568"
vwx.vmx["numvcpus"] = "4"
end
config.vm.provision "shell", inline: "/bin/tcsh /.ovk_release/install/automated/freebsd-13/install"
end

View file

@ -38,12 +38,12 @@ class Club extends RowModel
return iterator_to_array($avPhotos)[0] ?? NULL;
}
function getAvatarUrl(): string
function getAvatarUrl(string $size = "miniscule"): string
{
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
$avPhoto = $this->getAvatarPhoto();
return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURL();
return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURLBySizeId($size);
}
function getAvatarLink(): string
@ -347,5 +347,10 @@ class Club extends RowModel
return $this->getRecord()->website;
}
function getAlert(): ?string
{
return $this->getRecord()->alert;
}
use Traits\TSubscribable;
}

View file

@ -6,6 +6,8 @@ abstract class Media extends Postable
{
protected $fileExtension = "oct"; #octet stream xddd
protected $upperNodeReferenceColumnName = "owner";
protected $processingPlaceholder = NULL;
protected $processingTime = 30;
function __destruct()
{
@ -23,6 +25,11 @@ abstract class Media extends Postable
return OPENVK_ROOT . "/storage/";
}
protected function checkIfFileIsProcessed(): bool
{
throw new \LogicException("checkIfFileIsProcessed is not implemented");
}
abstract protected function saveFile(string $filename, string $hash): bool;
protected function pathFromHash(string $hash): string
@ -41,6 +48,10 @@ abstract class Media extends Postable
function getURL(): string
{
if(!is_null($this->processingPlaceholder))
if(!$this->isProcessed())
return "/assets/packages/static/openvk/$this->processingPlaceholder.$this->fileExtension";
$hash = $this->getRecord()->hash;
switch(OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["mode"]) {
@ -55,7 +66,7 @@ abstract class Media extends Postable
case "server":
$settings = (object) OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["server"];
return (
$settings->protocol .
$settings->protocol ?? ovk_scheme() .
"://" . $settings->host .
$settings->path .
substr($hash, 0, 2) . "/$hash.$this->fileExtension"
@ -69,6 +80,26 @@ abstract class Media extends Postable
return $this->getRecord()->description;
}
protected function isProcessed(): bool
{
if(is_null($this->processingPlaceholder))
return true;
if($this->getRecord()->processed)
return true;
$timeDiff = time() - $this->getRecord()->last_checked;
if($timeDiff < $this->processingTime)
return false;
$res = $this->checkIfFileIsProcessed();
$this->stateChanges("last_checked", time());
$this->stateChanges("processed", $res);
$this->save();
return $res;
}
function isDeleted(): bool
{
return (bool) $this->getRecord()->deleted;
@ -90,6 +121,16 @@ abstract class Media extends Postable
$this->stateChanges("hash", $hash);
}
function save(): void
{
if(!is_null($this->processingPlaceholder) && is_null($this->getRecord())) {
$this->stateChanges("processed", 0);
$this->stateChanges("last_checked", time());
}
parent::save();
}
function delete(bool $softly = true): void
{
$deleteQuirk = ovkGetQuirk("blobs.erase-upon-deletion");

View file

@ -48,6 +48,7 @@ class Note extends Postable
"acronym",
"blockquote",
"cite",
"span",
]);
$config->set("HTML.AllowedAttributes", [
"table.summary",
@ -59,6 +60,8 @@ class Note extends Postable
"img.style",
"div.style",
"div.title",
"span.class",
"p.class",
]);
$config->set("CSS.AllowedProperties", [
"float",
@ -68,6 +71,9 @@ class Note extends Postable
"max-width",
"font-weight",
]);
$config->set("Attr.AllowedClasses", [
"underline",
]);
$purifier = new HTMLPurifier($config);
return $purifier->purify($this->getRecord()->source);

View file

@ -1,5 +1,8 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use MessagePack\MessagePack;
use openvk\Web\Models\Entities\Album;
use openvk\Web\Models\Repositories\Albums;
use Chandler\Database\DatabaseConnection as DB;
use Nette\InvalidStateException as ISE;
use Nette\Utils\Image;
@ -11,18 +14,80 @@ class Photo extends Media
const ALLOWED_SIDE_MULTIPLIER = 7;
private function resizeImage(string $filename, string $outputDir, \SimpleXMLElement $size): array
{
$res = [false];
$image = Image::fromFile($filename);
$requiresProportion = ((string) $size["requireProp"]) != "none";
if($requiresProportion) {
$props = explode(":", (string) $size["requireProp"]);
$px = (int) $props[0];
$py = (int) $props[1];
if(($image->getWidth() / $image->getHeight()) > ($px / $py)) {
# For some weird reason using resize with EXACT flag causes system to consume an unholy amount of RAM
$image->crop(0, 0, "100%", (int) ceil(($px * $image->getWidth()) / $py));
$res[0] = true;
}
}
if(isset($size["maxSize"])) {
$maxSize = (int) $size["maxSize"];
$image->resize($maxSize, $maxSize, Image::SHRINK_ONLY | Image::FIT);
} else if(isset($size["maxResolution"])) {
$resolution = explode("x", (string) $size["maxResolution"]);
$image->resize((int) $resolution[0], (int) $resolution[1], Image::SHRINK_ONLY | Image::FIT);
} else {
throw new \RuntimeException("Malformed size description: " . (string) $size["id"]);
}
$res[1] = $image->getWidth();
$res[2] = $image->getHeight();
if($res[1] <= 300 || $res[2] <= 300)
$image->save("$outputDir/" . (string) $size["id"] . ".gif");
else
$image->save("$outputDir/" . (string) $size["id"] . ".jpeg");
imagedestroy($image->getImageResource());
unset($image);
return $res;
}
private function saveImageResizedCopies(string $filename, string $hash): void
{
$dir = dirname($this->pathFromHash($hash));
$dir = "$dir/$hash" . "_cropped";
if(!is_dir($dir)) {
@unlink($dir); # Added to transparently bypass issues with dead pesudofolders summoned by buggy SWIFT impls (selectel)
mkdir($dir);
}
$sizes = simplexml_load_file(OPENVK_ROOT . "/data/photosizes.xml");
if(!$sizes)
throw new \RuntimeException("Could not load photosizes.xml!");
$sizesMeta = [];
foreach($sizes->Size as $size)
$sizesMeta[(string) $size["id"]] = $this->resizeImage($filename, $dir, $size);
$sizesMeta = MessagePack::pack($sizesMeta);
$this->stateChanges("sizes", $sizesMeta);
}
protected function saveFile(string $filename, string $hash): bool
{
$image = Image::fromFile($filename);
if(($image->height >= ($image->width * Photo::ALLOWED_SIDE_MULTIPLIER)) || ($image->width >= ($image->height * Photo::ALLOWED_SIDE_MULTIPLIER)))
throw new ISE("Invalid layout: image is too wide/short");
$image->resize(8192, 4320, Image::SHRINK_ONLY | Image::FIT);
$image->save($this->pathFromHash($hash), 92, Image::JPEG);
$this->saveImageResizedCopies($filename, $hash);
return true;
}
function crop(real $left, real $top, real $width, real $height): bool
function crop(real $left, real $top, real $width, real $height): void
{
if(isset($this->changes["hash"]))
$hash = $this->changes["hash"];
@ -33,7 +98,7 @@ class Photo extends Media
$image = Image::fromFile($this->pathFromHash($hash));
$image->crop($left, $top, $width, $height);
return $image->save($this->pathFromHash($hash));
$image->save($this->pathFromHash($hash));
}
function isolate(): void
@ -44,6 +109,130 @@ class Photo extends Media
DB::i()->getContext()->table("album_relations")->where("media", $this->getRecord()->id)->delete();
}
function getSizes(bool $upgrade = false, bool $forceUpdate = false): ?array
{
$sizes = $this->getRecord()->sizes;
if(!$sizes || $forceUpdate) {
if($forceUpdate || $upgrade || OPENVK_ROOT_CONF["openvk"]["preferences"]["photos"]["upgradeStructure"]) {
$hash = $this->getRecord()->hash;
$this->saveImageResizedCopies($this->pathFromHash($hash), $hash);
$this->save();
return $this->getSizes();
}
return NULL;
}
$res = [];
$sizes = MessagePack::unpack($sizes);
foreach($sizes as $id => $meta) {
$url = $this->getURL();
$url = str_replace(".$this->fileExtension", "_cropped/$id.", $url);
$url .= ($meta[1] <= 300 || $meta[2] <= 300) ? "gif" : "jpeg";
$res[$id] = (object) [
"url" => $url,
"width" => $meta[1],
"height" => $meta[2],
"crop" => $meta[0]
];
}
[$x, $y] = $this->getDimensions();
$res["UPLOADED_MAXRES"] = (object) [
"url" => $this->getURL(),
"width" => $x,
"height" => $y,
"crop" => false
];
return $res;
}
function getVkApiSizes(): ?array
{
$res = [];
$sizes = $this->getSizes();
if(!$sizes)
return NULL;
$manifest = simplexml_load_file(OPENVK_ROOT . "/data/photosizes.xml");
if(!$manifest)
return NULL;
$mappings = [];
foreach($manifest->Size as $size)
$mappings[(string) $size["id"]] = (string) $size["vkId"];
foreach($sizes as $id => $meta) {
$type = $mappings[$id] ?? $id;
$meta->type = $type;
$res[$type] = $meta;
}
return $res;
}
function getURLBySizeId(string $size): string
{
$sizes = $this->getSizes();
if(!$sizes)
return $this->getURL();
$size = $sizes[$size];
if(!$size)
return $this->getURL();
return $size->url;
}
function getDimensions(): array
{
$x = $this->getRecord()->width;
$y = $this->getRecord()->height;
if(!$x) { # no sizes in database
$hash = $this->getRecord()->hash;
$image = Image::fromFile($this->pathFromHash($hash));
$x = $image->getWidth();
$y = $image->getHeight();
$this->stateChanges("width", $x);
$this->stateChanges("height", $y);
$this->save();
}
return [$x, $y];
}
function getAlbum(): ?Album
{
return (new Albums)->getAlbumByPhotoId($this);
}
function toVkApiStruct(): object
{
$res = (object) [];
$res->id = $res->pid = $this->getId();
$res->owner_id = $res->user_id = $this->getOwner()->getId()->getId();
$res->aid = $res->album_id = NULL;
$res->width = $this->getDimensions()[0];
$res->height = $this->getDimensions()[1];
$res->date = $res->created = $this->getPublicationTime()->timestamp();
$res->sizes = $this->getVkApiSizes();
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
$res->src = $res->photo_130 = $this->getURLBySizeId("tiny");
$res->src_big = $res->photo_604 = $this->getURLBySizeId("normal");
$res->src_xbig = $res->photo_807 = $this->getURLBySizeId("large");
$res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger");
$res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original");
$res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES");
return $res;
}
static function fastMake(int $owner, string $description = "", array $file, ?Album $album = NULL, bool $anon = false): Photo
{
$photo = new static;

View file

@ -55,16 +55,21 @@ trait TRichText
{
$contentColumn = property_exists($this, "overrideContentColumn") ? $this->overrideContentColumn : "content";
$text = htmlentities($this->getRecord()->{$contentColumn}, ENT_DISALLOWED | ENT_XHTML);
$text = htmlspecialchars($this->getRecord()->{$contentColumn}, ENT_DISALLOWED | ENT_XHTML);
$proc = iconv_strlen($this->getRecord()->{$contentColumn}) <= OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["processingLimit"];
if($html) {
if($proc) {
$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]++) \(((?:[\p{L&}\p{Lo} 0-9]\p{Mn}?)++)\)%Xu", "[$1|$2]", $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("%([\n\r\s]|^)(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "$1<a href='/feed/hashtag/$3'>$2</a>", $text);
$text = preg_replace("%\[([A-Za-z0-9]++)\|((?:[\p{L&}\p{Lo} 0-9@]\p{Mn}?)++)\]%Xu", "<a href='/$1'>$2</a>", $text);
$text = preg_replace_callback("%([\n\r\s]|^)(\#([\p{L}_0-9][\p{L}_0-9\(\)\-\']+[\p{L}_0-9\(\)]|[\p{L}_0-9]{1,2}))%Xu", function($m) {
$slug = rawurlencode($m[3]);
return "$m[1]<a href='/feed/hashtag/$slug'>$m[2]</a>";
}, $text);
$text = $this->formatEmojis($text);
}

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use morphos\Gender;
use openvk\Web\Themes\{Themepack, Themepacks};
use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel;
@ -9,6 +10,7 @@ use openvk\Web\Models\Exceptions\InvalidUserNameException;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
use Chandler\Security\User as ChandlerUser;
use function morphos\Russian\inflectName;
class User extends RowModel
{
@ -31,11 +33,11 @@ class User extends RowModel
const NSFW_TOLERANT = 1;
const NSFW_FULL_TOLERANT = 2;
protected function _abstractRelationGenerator(string $filename, int $page = 1): \Traversable
protected function _abstractRelationGenerator(string $filename, int $page = 1, int $limit = 6): \Traversable
{
$id = $this->getId();
$query = "SELECT id FROM\n" . file_get_contents(__DIR__ . "/../sql/$filename.tsql");
$query .= "\n LIMIT 6 OFFSET " . ( ($page - 1) * 6 );
$query .= "\n LIMIT " . $limit . " OFFSET " . ( ($page - 1) * $limit );
$rels = DatabaseConnection::i()->getConnection()->query($query, $id, $id);
foreach($rels as $rel) {
@ -102,7 +104,7 @@ class User extends RowModel
return "/id" . $this->getId();
}
function getAvatarUrl(): string
function getAvatarUrl(string $size = "miniscule"): string
{
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
@ -115,7 +117,7 @@ class User extends RowModel
if(is_null($avPhoto))
return "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
else
return $avPhoto->getURL();
return $avPhoto->getURLBySizeId($size);
}
function getAvatarLink(): string
@ -167,12 +169,23 @@ class User extends RowModel
return $this->getFirstName() . $pseudo . $this->getLastName();
}
function getMorphedName(string $case = "genitive", bool $fullName = true): string
{
$name = $fullName ? ($this->getLastName() . " " . $this->getFirstName()) : $this->getFirstName();
if(!preg_match("%^[А-яё\-]+$%", $name))
return $name; # name is probably not russian
$inflected = inflectName($name, $case, $this->isFemale() ? Gender::FEMALE : Gender::MALE);
return $inflected ?: $name;
}
function getCanonicalName(): string
{
if($this->getRecord()->deleted)
if($this->getRecord()->deleted)
return "DELETED";
else
return $this->getFirstName() . ' ' . $this->getLastName();
else
return $this->getFirstName() . " " . $this->getLastName();
}
function getPhone(): ?string
@ -215,6 +228,11 @@ class User extends RowModel
return $this->getRecord()->block_reason;
}
function getBanInSupportReason(): ?string
{
return $this->getRecord()->block_in_support_reason;
}
function getType(): int
{
return $this->getRecord()->type;
@ -438,9 +456,9 @@ class User extends RowModel
];
}
function getFriends(int $page = 1): \Traversable
function getFriends(int $page = 1, int $limit = 6): \Traversable
{
return $this->_abstractRelationGenerator("get-friends", $page);
return $this->_abstractRelationGenerator("get-friends", $page, $limit);
}
function getFriendsCount(): int
@ -448,9 +466,9 @@ class User extends RowModel
return $this->_abstractRelationCount("get-friends");
}
function getFollowers(int $page = 1): \Traversable
function getFollowers(int $page = 1, int $limit = 6): \Traversable
{
return $this->_abstractRelationGenerator("get-followers", $page);
return $this->_abstractRelationGenerator("get-followers", $page, $limit);
}
function getFollowersCount(): int
@ -458,9 +476,9 @@ class User extends RowModel
return $this->_abstractRelationCount("get-followers");
}
function getSubscriptions(int $page = 1): \Traversable
function getSubscriptions(int $page = 1, int $limit = 6): \Traversable
{
return $this->_abstractRelationGenerator("get-subscriptions-user", $page);
return $this->_abstractRelationGenerator("get-subscriptions-user", $page, $limit);
}
function getSubscriptionsCount(): int
@ -674,6 +692,11 @@ class User extends RowModel
return !is_null($this->getBanReason());
}
function isBannedInSupport(): bool
{
return !is_null($this->getBanInSupportReason());
}
function isOnline(): bool
{
return time() - $this->getRecord()->online <= 300;

View file

@ -15,6 +15,8 @@ class Video extends Media
protected $tableName = "videos";
protected $fileExtension = "ogv";
protected $processingPlaceholder = "video/rendering";
protected function saveFile(string $filename, string $hash): bool
{
if(!Shell::commandAvailable("ffmpeg") || !Shell::commandAvailable("ffprobe"))
@ -37,11 +39,13 @@ class Video extends Media
throw new \DomainException("$filename does not contain any meaningful video streams");
try {
if(!is_dir($dirId = $this->pathFromHash($hash)))
if(!is_dir($dirId = dirname($this->pathFromHash($hash))))
mkdir($dirId);
$dir = $this->getBaseDir();
Shell::bash(__DIR__ . "/../shell/processVideo.sh", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD
$ext = Shell::isPowershell() ? "ps1" : "sh";
$cmd = Shell::isPowershell() ? "powershell" : "bash";
Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD
} catch(ShellUnavailableException $suex) {
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR);
} catch(UnknownCommandException $ucex) {
@ -52,6 +56,22 @@ class Video extends Media
return true;
}
protected function checkIfFileIsProcessed(): bool
{
if($this->getType() != Video::TYPE_DIRECT)
return true;
if(!file_exists($this->getFileName())) {
if((time() - $this->getRecord()->last_checked) > 3600) {
// TODO notify that video processor is probably dead
}
return false;
}
return true;
}
function getName(): string
{
return $this->getRecord()->name;
@ -81,6 +101,9 @@ class Video extends Media
function getThumbnailURL(): string
{
if($this->getType() === Video::TYPE_DIRECT) {
if(!$this->isProcessed())
return "/assets/packages/static/openvk/video/rendering.apng";
return preg_replace("%\.[A-z]++$%", ".gif", $this->getURL());
} else {
return $this->getVideoDriver()->getThumbnailURL();

View file

@ -1,6 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Album;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
@ -115,4 +116,11 @@ class Albums
return new Album($album);
}
function getAlbumByPhotoId(Photo $photo): ?Album
{
$dbalbum = $this->context->table("album_relations")->where(["media" => $photo->getId()])->fetch();
return $dbalbum->collection ? $this->get($dbalbum->collection) : null;
}
}

View file

@ -45,7 +45,7 @@ class Clubs
function getPopularClubs(): \Traversable
{
$query = "SELECT ROW_NUMBER() OVER (ORDER BY `subscriptions` DESC) as `place`, `target` as `id`, COUNT(`follower`) as `subscriptions` FROM `subscriptions` WHERE `model` = \"openvk\\\Web\\\Models\\\Entities\\\Club\" GROUP BY `target` ORDER BY `subscriptions` DESC, `id` LIMIT 10;";
$query = "SELECT ROW_NUMBER() OVER (ORDER BY `subscriptions` DESC) as `place`, `target` as `id`, COUNT(`follower`) as `subscriptions` FROM `subscriptions` WHERE `model` = \"openvk\\\Web\\\Models\\\Entities\\\Club\" GROUP BY `target` ORDER BY `subscriptions` DESC, `id` LIMIT 30;";
$entries = DatabaseConnection::i()->getConnection()->query($query);
foreach($entries as $entry)

View file

@ -29,7 +29,7 @@ class Notes
function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->page($page, $perPage) as $album)
foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->order("created DESC")->page($page, $perPage) as $album)
yield new Note($album);
}

View file

@ -94,7 +94,6 @@ class Posts
{
$post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post])->fetch();
if(!is_null($post))
return new Post($post);
else
return null;

View file

@ -39,7 +39,7 @@ class Users
function find(string $query): Util\EntityStream
{
$query = "%$query%";
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo) LIKE ?", $query)->where("deleted", 0);
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo, shortcode) LIKE ?", $query)->where("deleted", 0);
return new Util\EntityStream("User", $result);
}

View file

@ -0,0 +1,20 @@
$ovkRoot = $args[0]
$file = $args[1]
$dir = $args[2]
$hash = $args[3]
$hashT = $hash.substring(0, 2)
$temp = [System.IO.Path]::GetTempFileName()
$temp2 = [System.IO.Path]::GetTempFileName()
$shell = Get-WmiObject Win32_process -filter "ProcessId = $PID"
$shell.SetPriority(16384)
Move-Item $file $temp
# video stub logic was implicitly deprecated, so we start processing at once
ffmpeg -i $temp -ss 00:00:01.000 -vframes 1 "$dir$hashT/$hash.gif"
ffmpeg -i $temp -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y $temp2
Move-Item $temp2 "$dir$hashT/$hash.ogv"
Remove-Item $temp
Remove-Item $temp2

View file

@ -5,7 +5,7 @@ cp ../files/video/rendering.apng $3${4:0:2}/$4.gif
cp ../files/video/rendering.ogv $3/${4:0:2}/$4.ogv
nice ffmpeg -i "/tmp/vid_$tmpfile.bin" -ss 00:00:01.000 -vframes 1 $3${4:0:2}/$4.gif
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf scale=640x360,setsar=1:1 -y "/tmp/ffmOi$tmpfile.ogv"
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y "/tmp/ffmOi$tmpfile.ogv"
rm -rf $3${4:0:2}/$4.ogv
mv "/tmp/ffmOi$tmpfile.ogv" $3${4:0:2}/$4.ogv

View file

@ -126,4 +126,11 @@ final class AboutPresenter extends OpenVKPresenter
header("Location: https://github.com/openvk/openvk#readme");
exit;
}
function renderDev(): void
{
header("HTTP/1.1 302 Found");
header("Location: https://docs.openvk.su/");
exit;
}
}

View file

@ -23,7 +23,7 @@ final class AdminPresenter extends OpenVKPresenter
private function warnIfNoCommerce(): void
{
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->flash("warn", "Коммерция отключена системным администратором", "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния.");
$this->flash("warn", tr("admin_commerce_disabled"), tr("admin_commerce_disabled_desc"));
}
private function searchResults(object $repo, &$count)
@ -346,7 +346,20 @@ final class AdminPresenter extends OpenVKPresenter
exit(json_encode([ "error" => "User does not exist" ]));
$user->ban($this->queryParam("reason"));
exit(json_encode([ "reason" => $this->queryParam("reason") ]));
exit(json_encode([ "success" => true, "reason" => $this->queryParam("reason") ]));
}
function renderQuickUnban(int $id): void
{
$this->assertNoCSRF();
$user = $this->users->get($id);
if(!$user)
exit(json_encode([ "error" => "User does not exist" ]));
$user->setBlock_Reason(null);
$user->save();
exit(json_encode([ "success" => true ]));
}
function renderQuickWarn(int $id): void

View file

@ -4,6 +4,7 @@ use openvk\Web\Models\Entities\IP;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Entities\PasswordReset;
use openvk\Web\Models\Entities\EmailVerification;
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Models\Repositories\IPs;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Repositories\Restores;
@ -88,20 +89,25 @@ final class AuthPresenter extends OpenVKPresenter
if (strtotime($this->postParam("birthday")) > time())
$this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment"));
try {
$user = new User;
$user->setFirst_Name($this->postParam("first_name"));
$user->setLast_Name($this->postParam("last_name"));
$user->setSex((int)($this->postParam("sex") === "female"));
$user->setEmail($this->postParam("email"));
$user->setSince(date("Y-m-d H:i:s"));
$user->setRegistering_Ip(CONNECTING_IP);
$user->setBirthday(strtotime($this->postParam("birthday")));
$user->setActivated((int)!OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']);
} catch(InvalidUserNameException $ex) {
$this->flashFail("err", tr("error"), tr("invalid_real_name"));
}
$chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password"));
if(!$chUser)
$this->flashFail("err", tr("failed_to_register"), tr("user_already_exists"));
$user = new User;
$user->setUser($chUser->getId());
$user->setFirst_Name($this->postParam("first_name"));
$user->setLast_Name($this->postParam("last_name"));
$user->setSex((int) ($this->postParam("sex") === "female"));
$user->setEmail($this->postParam("email"));
$user->setSince(date("Y-m-d H:i:s"));
$user->setRegistering_Ip(CONNECTING_IP);
$user->setBirthday(strtotime($this->postParam("birthday")));
$user->setActivated((int) !OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']);
$user->save();
if(!is_null($referer)) {

View file

@ -17,20 +17,23 @@ final class BlobPresenter extends OpenVKPresenter
function renderFile(/*string*/ $dir, string $name, string $format)
{
$dir = $this->getDirName($dir);
$name = preg_replace("%[^a-zA-Z0-9_\-]++%", "", $name);
$path = OPENVK_ROOT . "/storage/$dir/$name.$format";
if(!file_exists($path)) {
$base = realpath(OPENVK_ROOT . "/storage/$dir");
$path = realpath(OPENVK_ROOT . "/storage/$dir/$name.$format");
if(!$path) # Will also check if file exists since realpath fails on ENOENT
$this->notFound();
} else {
if(isset($_SERVER["HTTP_IF_NONE_MATCH"]))
else if(strpos($path, $path) !== 0) # Prevent directory traversal and storage container escape
$this->notFound();
if(isset($_SERVER["HTTP_IF_NONE_MATCH"]))
exit(header("HTTP/1.1 304 Not Modified"));
header("Content-Type: " . mime_content_type($path));
header("Content-Size: " . filesize($path));
header("Cache-Control: public, max-age=1210000");
header("X-Accel-Expires: 1210000");
header("ETag: W/\"" . hash_file("snefru", $path) . "\"");
readfile($path);
exit;
}
}
}

View file

@ -204,6 +204,8 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
$this->template->isTimezoned = Session::i()->get("_timezoneOffset");
$userValidated = 0;
$cacheTime = OPENVK_ROOT_CONF["openvk"]["preferences"]["nginxCacheTime"] ?? 0;
if(!is_null($user)) {
$this->user = (object) [];
$this->user->raw = $user;
@ -261,6 +263,8 @@ abstract class OpenVKPresenter extends SimplePresenter
exit;
}
$userValidated = 1;
$cacheTime = 0; # Force no cache
if ($this->user->identity->onlineStatus() == 0) {
$this->user->identity->setOnline(time());
$this->user->identity->save();
@ -271,6 +275,8 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
}
header("X-OpenVK-User-Validated: $userValidated");
header("X-Accel-Expires: $cacheTime");
setlocale(LC_TIME, ...(explode(";", tr("__locale"))));
parent::onStartup();

View file

@ -72,6 +72,8 @@ final class PhotosPresenter extends OpenVKPresenter
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(empty($this->postParam("name")))
$this->flashFail("err", tr("error"), tr("error_segmentation"));
else if(strlen($this->postParam("name")) > 36)
$this->flashFail("err", tr("error"), tr("error_data_too_big", "name", 36, "bytes"));
$album = new Album;
$album->setOwner(isset($club) ? $club->getId() * -1 : $this->user->id);
@ -100,6 +102,9 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->album = $album;
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(strlen($this->postParam("name")) > 36)
$this->flashFail("err", tr("error"), tr("error_data_too_big", "name", 36, "bytes"));
$album->setName(empty($this->postParam("name")) ? $album->getName() : $this->postParam("name"));
$album->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
$album->setEdited(time());
@ -276,6 +281,8 @@ final class PhotosPresenter extends OpenVKPresenter
$photo->isolate();
$photo->delete();
exit("Фотография успешно удалена!");
$this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена.");
$this->redirect("/id0", static::REDIRECT_TEMPORARY);
}
}

View file

@ -25,6 +25,10 @@ final class SearchPresenter extends OpenVKPresenter
$type = $this->queryParam("type") ?? "users";
$page = (int) ($this->queryParam("p") ?? 1);
$this->willExecuteWriteAction();
if($query != "")
$this->assertUserLoggedIn();
// https://youtu.be/pSAWM5YuXx8
$repos = [ "groups" => "clubs", "users" => "users" ];

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\Ticket;
use openvk\Web\Models\Repositories\Tickets;
use openvk\Web\Models\Repositories\{Tickets, Users};
use openvk\Web\Models\Entities\TicketComment;
use openvk\Web\Models\Repositories\TicketComments;
use openvk\Web\Util\Telegram;
@ -34,7 +34,13 @@ final class SupportPresenter extends OpenVKPresenter
$this->template->tickets = $this->tickets->getTicketsByUserId($this->user->id, $this->template->page);
}
if($this->template->mode === "new")
$this->template->banReason = $this->user->identity->getBanInSupportReason();
if($_SERVER["REQUEST_METHOD"] === "POST") {
if($this->user->identity->isBannedInSupport())
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
if(!empty($this->postParam("name")) && !empty($this->postParam("text"))) {
$this->willExecuteWriteAction();
@ -268,4 +274,32 @@ final class SupportPresenter extends OpenVKPresenter
exit(header("HTTP/1.1 200 OK"));
}
function renderQuickBanInSupport(int $id): void
{
$this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0);
$this->assertNoCSRF();
$user = (new Users)->get($id);
if(!$user)
exit(json_encode([ "error" => "User does not exist" ]));
$user->setBlock_In_Support_Reason($this->queryParam("reason"));
$user->save();
$this->returnJson([ "success" => true, "reason" => $this->queryParam("reason") ]);
}
function renderQuickUnbanInSupport(int $id): void
{
$this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0);
$this->assertNoCSRF();
$user = (new Users)->get($id);
if(!$user)
exit(json_encode([ "error" => "User does not exist" ]));
$user->setBlock_In_Support_Reason(null);
$user->save();
$this->returnJson([ "success" => true ]);
}
}

View file

@ -31,7 +31,7 @@ final class UserPresenter extends OpenVKPresenter
{
$user = $this->users->get($id);
if(!$user || $user->isDeleted())
$this->notFound();
$this->template->_template = "User/deleted.xml";
else {
if($user->getShortCode())
if(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) !== "/" . $user->getShortCode())
@ -285,7 +285,6 @@ final class UserPresenter extends OpenVKPresenter
$photo->setCreated(time());
$photo->save();
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", tr("error"), tr("error_upload_failed"));
}
@ -482,6 +481,22 @@ final class UserPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("information_-1"), tr("two_factor_authentication_disabled_message"));
}
function renderResetThemepack(): void
{
$this->assertNoCSRF();
$this->setSessionTheme(Themepacks::DEFAULT_THEME_ID);
if($this->user) {
$this->willExecuteWriteAction();
$this->user->identity->setStyle(Themepacks::DEFAULT_THEME_ID);
$this->user->identity->save();
}
$this->redirect("/", static::REDIRECT_TEMPORARY_PRESISTENT);
}
function renderCoinsTransfer(): void
{
$this->assertUserLoggedIn();

View file

@ -78,6 +78,92 @@ final class VKAPIPresenter extends OpenVKPresenter
}
}
function renderPhotoUpload(string $signature): void
{
$secret = CHANDLER_ROOT_CONF["security"]["secret"];
$computedSignature = hash_hmac("sha3-224", $_SERVER["QUERY_STRING"], $secret);
if(!(strlen($signature) == 56 && sodium_memcmp($signature, $computedSignature) == 0)) {
header("HTTP/1.1 422 Unprocessable Entity");
exit("Try harder <3");
}
$data = unpack("vDOMAIN/Z10FIELD/vMF/vMP/PTIME/PUSER/PGROUP", base64_decode($_SERVER["QUERY_STRING"]));
if((time() - $data["TIME"]) > 600) {
header("HTTP/1.1 422 Unprocessable Entity");
exit("Expired");
}
$folder = __DIR__ . "../../tmp/api-storage/photos";
$maxSize = OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["api"]["maxFileSize"];
$maxFiles = OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["api"]["maxFilesPerDomain"];
$usrFiles = sizeof(glob("$folder/$data[USER]_*.oct"));
if($usrFiles >= $maxFiles) {
header("HTTP/1.1 507 Insufficient Storage");
exit("There are $maxFiles pending already. Please save them before uploading more :3");
}
# Not multifile
if($data["MF"] === 0) {
$file = $_FILES[$data["FIELD"]];
if(!$file) {
header("HTTP/1.0 400");
exit("No file");
} else if($file["error"] != UPLOAD_ERR_OK) {
header("HTTP/1.0 500");
exit("File could not be consumed");
} else if($file["size"] > $maxSize) {
header("HTTP/1.0 507 Insufficient Storage");
exit("File is too big");
}
move_uploaded_file($file["tmp_name"], "$folder/$data[USER]_" . ($usrFiles + 1) . ".oct");
header("HTTP/1.0 202 Accepted");
$photo = $data["USER"] . "|" . ($usrFiles + 1) . "|" . $data["GROUP"];
exit(json_encode([
"server" => "ephemeral",
"photo" => $photo,
"hash" => hash_hmac("sha3-224", $photo, $secret),
]));
}
$files = [];
for($i = 1; $i <= 5; $i++) {
$file = $_FILES[$data["FIELD"] . $i] ?? NULL;
if (!$file || $file["error"] != UPLOAD_ERR_OK || $file["size"] > $maxSize) {
continue;
} else if((sizeof($files) + $usrFiles) > $maxFiles) {
# Clear uploaded files since they can't be saved anyway
foreach($files as $f)
unlink($f);
header("HTTP/1.1 507 Insufficient Storage");
exit("There are $maxFiles pending already. Please save them before uploading more :3");
}
$files[++$usrFiles] = move_uploaded_file($file["tmp_name"], "$folder/$data[USER]_$usrFiles.oct");
}
if(sizeof($files) === 0) {
header("HTTP/1.0 400");
exit("No file");
}
$filesManifest = [];
foreach($files as $id => $file)
$filesManifest[] = ["keyholder" => $data["USER"], "resource" => $id, "club" => $data["GROUP"]];
$filesManifest = json_encode($filesManifest);
$manifestHash = hash_hmac("sha3-224", $filesManifest, $secret);
header("HTTP/1.0 202 Accepted");
exit(json_encode([
"server" => "ephemeral",
"photos_list" => $filesManifest,
"album_id" => "undefined",
"hash" => $manifestHash,
]));
}
function renderRoute(string $object, string $method): void
{
$authMechanism = $this->queryParam("auth_mechanism") ?? "token";

View file

@ -45,7 +45,7 @@ final class WallPresenter extends OpenVKPresenter
function renderWall(int $user, bool $embedded = false): void
{
if(false)
exit("Ошибка доступа: " . (string) random_int(0, 255));
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
if(is_null($this->user)) {
@ -54,7 +54,7 @@ final class WallPresenter extends OpenVKPresenter
if(!$owner->isBanned())
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), "Ошибка доступа");
$this->flashFail("err", tr("error"), tr("forbidden"));
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
@ -89,7 +89,7 @@ final class WallPresenter extends OpenVKPresenter
function renderRSS(int $user): void
{
if(false)
exit("Ошибка доступа: " . (string) random_int(0, 255));
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
if(is_null($this->user)) {
@ -98,7 +98,7 @@ final class WallPresenter extends OpenVKPresenter
if(!$owner->isBanned())
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), "Ошибка доступа");
$this->flashFail("err", tr("error"), tr("forbidden"));
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
@ -213,12 +213,12 @@ final class WallPresenter extends OpenVKPresenter
$this->willExecuteWriteAction();
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", "Не удалось опубликовать пост", "Такого пользователя не существует.");
?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
if($wall > 0) {
if(!$wallOwner->isBanned())
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
} else if($wall < 0) {
if($wallOwner->canBeModifiedBy($this->user->identity))
$canPost = true;
@ -229,7 +229,7 @@ final class WallPresenter extends OpenVKPresenter
}
if(!$canPost)
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($wallOwner instanceof Club && $this->postParam("as_group") === "on" && $this->postParam("force_sign") !== "on" && $anon) {
@ -265,10 +265,18 @@ final class WallPresenter extends OpenVKPresenter
} catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать пост", "Файл медиаконтента повреждён или слишком велик.");
}
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"], $anon);
}
} catch(\DomainException $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted"));
} catch(ISE $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted_or_too_large"));
}
if(empty($this->postParam("text")) && empty($photos))
$this->flashFail("err", "Не удалось опубликовать пост", "Пост пустой или слишком большой.");
if(empty($this->postParam("text")) && !$photo && !$video)
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
try {
$post = new Post;
@ -281,7 +289,7 @@ final class WallPresenter extends OpenVKPresenter
$post->setNsfw($this->postParam("nsfw") === "on");
$post->save();
} catch (\LengthException $ex) {
$this->flashFail("err", "Не удалось опубликовать пост", "Пост слишком большой.");
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
}
foreach($photos as $photo) {
@ -300,8 +308,6 @@ final class WallPresenter extends OpenVKPresenter
function renderPost(int $wall, int $post_id): void
{
$this->assertUserLoggedIn();
$post = $this->posts->getPostById($wall, $post_id);
if(!$post || $post->isDeleted())
$this->notFound();
@ -313,7 +319,7 @@ final class WallPresenter extends OpenVKPresenter
$this->template->wallOwner = (new Users)->get($post->getTargetWall());
$this->template->isWallOfGroup = false;
if($this->template->wallOwner->isBanned())
$this->flashFail("err", tr("error"), "Ошибка доступа");
$this->flashFail("err", tr("error"), tr("forbidden"));
} else {
$this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall()));
$this->template->isWallOfGroup = true;
@ -377,7 +383,7 @@ final class WallPresenter extends OpenVKPresenter
$user = $this->user->id;
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", "Не удалось удалить пост", "Такого пользователя не существует.");
?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
if($wall < 0) $canBeDeletedByOtherUser = $wallOwner->canBeModifiedBy($this->user->identity);
else $canBeDeletedByOtherUser = false;
@ -388,7 +394,7 @@ final class WallPresenter extends OpenVKPresenter
$post->delete();
}
} else {
$this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт.");
$this->flashFail("err", tr("failed_to_delete_post"), tr("login_required_error_comment"));
}
$this->redirect($wall < 0 ? "/club".($wall*-1) : "/id".$wall, static::REDIRECT_TEMPORARY);
@ -405,7 +411,7 @@ final class WallPresenter extends OpenVKPresenter
$this->notFound();
if(!$post->canBePinnedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Вам нельзя закреплять этот пост.");
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
if(($this->queryParam("act") ?? "pin") === "pin") {
$post->pin();
@ -414,6 +420,6 @@ final class WallPresenter extends OpenVKPresenter
}
// TODO localize message based on language and ?act=(un)pin
$this->flashFail("succ", "Операция успешна", "Операция успешна.");
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
}
}

View file

@ -4,7 +4,7 @@
</div>
<div class="container_gray">
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat">

View file

@ -3,7 +3,7 @@
{block wrap}
<div class="ovk-lw-container">
<div class="ovk-lw--list">
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{if sizeof($data) > 0}
<table n:foreach="$data as $dat" border="0" style="font-size:11px;" class="post">

View file

@ -1,5 +1,4 @@
{var instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
<!DOCTYPE html>
<html>
<head>

View file

@ -1,7 +1,7 @@
{var instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
{if !isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'}
<!DOCTYPE html>
<html n:if="!isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'">
<html>
<head>
<title>
{ifset title}{include title} - {/ifset}{$instance_name}
@ -98,12 +98,12 @@
<div class="layout">
<div id="xhead" class="dm"></div>
<div class="page_header {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}page_custom_header{/if}">
<a href="/" class="home_button {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}home_button_custom{/if}" title="{$instance_name}">{$instance_name}</a>
<div class="page_header{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME} page_custom_header{/if}">
<a href="/" class="home_button{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME} home_button_custom{/if}" title="{$instance_name}">{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}{$instance_name}{/if}</a>
<div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser}
<div class="link">
<a href="/">{_header_home}</a>
<a href="/" title="[Alt+Shift+,]" accesskey=",">{_header_home}</a>
</div>
<div class="link">
<a href="/search?type=groups">{_header_groups}</a>
@ -122,14 +122,14 @@
</div>
<div class="link">
<form action="/search" method="get">
<input type="search" name="query" placeholder="{_header_search}" style="height: 20px;background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" />
<input type="search" name="query" placeholder="{_header_search}" style="height: 20px;background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" title="{_header_search} [Alt+Shift+F]" accesskey="f" />
</form>
</div>
{else}
<div class="link">
<a href="/login">{_header_login}</a>
</div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" class="link">
<div class="link">
<a href="/reg">{_header_registration}</a>
</div>
<div class="link">
@ -144,7 +144,7 @@
{ifset $thisUser}
{if !$thisUser->isBanned() XOR !$thisUser->isActivated()}
<a href="/edit" class="link edit-button">{_edit_button}</a>
<a href="{$thisUser->getURL()}" class="link">{_my_page}</a>
<a href="{$thisUser->getURL()}" class="link" title="{_my_page} [Alt+Shift+.]" accesskey=".">{_my_page}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends}
<object type="internal/link" n:if="$thisUser->getFollowersCount() > 0">
<a href="/friends{$thisUser->getId()}?act=incoming">
@ -161,19 +161,19 @@
</a>
<a n:if="$thisUser->getLeftMenuItemStatus('notes')" href="/notes{$thisUser->getId()}" class="link">{_my_notes}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('groups')" href="/groups{$thisUser->getId()}" class="link">{_my_groups}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link">{_my_feed}</a>
<a href="/notifications" class="link">{_my_feedback}
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link" title="{_my_feed} [Alt+Shift+W]" accesskey="w">{_my_feed}</a>
<a href="/notifications" class="link" title="{_my_feedback} [Alt+Shift+N]" accesskey="n">{_my_feedback}
{if $thisUser->getNotificationsCount() > 0}
(<b>{$thisUser->getNotificationsCount()}</b>)
{/if}
</a>
<a href="/settings" class="link">{_my_settings}</a>
{var canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
{var canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
{var menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
{var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
{var $canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
{var $menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
<a href="/admin" class="link" n:if="$canAccessAdminPanel">Админ-панель</a>
<a href="/admin" class="link" n:if="$canAccessAdminPanel" title="{_admin} [Alt+Shift+A]" accesskey="a">{_admin}</a>
<a href="/support/tickets" class="link" n:if="$canAccessHelpdesk">Helpdesk
{if $helpdeskTicketNotAnsweredCount > 0}
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
@ -186,6 +186,14 @@
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
<a n:foreach="$thisUser->getPinnedClubs() as $club" href="{$club->getURL()}" class="link group_link">{$club->getName()}</a>
</div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && $thisUser->getCoins() != 0" id="votesBalance">
{tr("you_still_have_x_points", $thisUser->getCoins())|noescape}
<br /><br />
<a href="/settings?act=finance">{_top_up_your_account} &#xbb;</a>
</div>
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable'] && $thisUser->getLeftMenuItemStatus('poster')" href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
<img src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}" alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}" class="psa-poster" style="max-width: 100%; margin-top: 50px;" />
</a>
@ -208,7 +216,7 @@
<input type="hidden" name="jReturnTo" value="{$_SERVER['REQUEST_URI']}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_log_in}" class="button" style="display: inline-block;" />
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" href="/reg" class="button" style="display: inline-block;">{_registration}</a><br><br>
<a href="/reg" class="button" style="display: inline-block;">{_registration}</a><br><br>
<a href="/restore">{_forgot_password}</a>
</form>
{/ifset}
@ -251,7 +259,7 @@
</div>
<div class="page_footer">
{var dbVersion = \Chandler\Database\DatabaseConnection::i()->getConnection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION)}
{var $dbVersion = \Chandler\Database\DatabaseConnection::i()->getConnection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION)}
<div class="navigation_footer">
<a href="/about" class="link">{_footer_about_instance}</a>
@ -289,11 +297,43 @@
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['enable']" async defer data-domain="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['domain']}" src="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['server']}js/plausible.js"></script>
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['piwik']['enable']">
{var $piwik = (object) OPENVK_ROOT_CONF['openvk']['telemetry']['piwik']}
//<![CDATA[
(function(window,document,dataLayerName,id){
window[dataLayerName]=window[dataLayerName]||[],window[dataLayerName].push({ start:(new Date).getTime(),event:"stg.start" });var scripts=document.getElementsByTagName('script')[0],tags=document.createElement('script');
function stgCreateCookie(a,b,c){ var d="";if(c){ var e=new Date;e.setTime(e.getTime()+24*c*60*60*1e3),d=";expires="+e.toUTCString() }document.cookie=a+"="+b+d+";path=/" }
var isStgDebug=(window.location.href.match("stg_debug")||document.cookie.match("stg_debug"))&&!window.location.href.match("stg_disable_debug");stgCreateCookie("stg_debug",isStgDebug?1:"",isStgDebug?14:-1);
var qP=[];dataLayerName!=="dataLayer"&&qP.push("data_layer_name="+dataLayerName),isStgDebug&&qP.push("stg_debug");var qPString=qP.length>0?("?"+qP.join("&")):"";
tags.async=!0,tags.src={$piwik->container . "/"}+id+".js"+qPString,scripts.parentNode.insertBefore(tags,scripts);
!function(a,n,i){ a[n]=a[n]||{ };for(var c=0;c<i.length;c++)!function(i){ a[n][i]=a[n][i]||{ },a[n][i].api=a[n][i].api||function(){ var a=[].slice.call(arguments,0);"string"==typeof a[0]&&window[dataLayerName].push({ event:n+"."+i+":"+a[0],parameters:[].slice.call(arguments,1) }) } }(i[c]) }(window,"ppms",["tm","cm"]);
})(window,document,{$piwik->layer}, {$piwik->site});
//]]>
</script>
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['matomo']['enable']">
{var $matomo = (object) OPENVK_ROOT_CONF['openvk']['telemetry']['matomo']}
//<![CDATA[
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//" + {$matomo->container} + "/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', {$matomo->site}]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
//]]>
</script>
{ifset bodyScripts}
{include bodyScripts}
{/ifset}
</body>
</html>
{/if}
{if isset($parentModule) && substr($parentModule, 0, 21) !== 'libchandler:absolute.'}
<!-- INCLUDING TEMPLATE FROM PARENTMODULE: {$parentModule} -->

View file

@ -1,70 +1,74 @@
{extends "@layout.xml"}
{block wrap}
<div class="page_wrap">
<div n:ifset="tabs" n:ifcontent class="tabs">
{include tabs}
</div>
<div class="wrap2">
<div class="wrap1">
<div class="page_wrap padding_top">
<div n:ifset="tabs" class="tabs">
{include tabs}
</div>
{ifset size}
{include size, x => $dat}
{/ifset}
{ifset size}
{include size, x => $dat}
{/ifset}
{ifset specpage}
{include specpage, x => $dat}
{else}
<div class="container_gray">
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{ifset specpage}
{include specpage, x => $dat}
{else}
<div class="container_gray">
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{include link, x => $dat}">
{include preview, x => $dat}
</a>
</td>
<td valign="top" style="width: 100%">
{ifset infoTable}
{include infoTable, x => $dat}
{else}
{if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat">
<table>
<tbody>
<tr>
<td valign="top">
<a href="{include link, x => $dat}">
{include preview, x => $dat}
</a>
</td>
<td valign="top" style="width: 100%">
{ifset infotable}
{include infotable, x => $dat}
{else}
<a href="{include link, x => $dat}">
<b>
{include name, x => $dat}
</b>
</a>
<br/>
{include description, x => $dat}
{/ifset}
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px; text-transform: lowercase;">
{include actions, x => $dat}
</td>
</tr>
</tbody>
</table>
</div>
{include "components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($data),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
{else}
{ifset customErrorMessage}
{include customErrorMessage}
{include description, x => $dat}
{/ifset}
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px; text-transform: lowercase;">
{include actions, x => $dat}
</td>
</tr>
</tbody>
</table>
</div>
{include "components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($data),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
{else}
{include "components/nothing.xml"}
{/ifset}
{/if}
</div>
{/ifset}
{ifset customErrorMessage}
{include customErrorMessage}
{else}
{include "components/nothing.xml"}
{/ifset}
{/if}
</div>
{/ifset}
{ifset bottom}
{include bottom}
{/ifset}
{ifset bottom}
{include bottom}
{/ifset}
</div>
</div>
</div>
{/block}

View file

@ -9,7 +9,7 @@
<table width="100%" cellspacing="0" cellpadding="0">
<tbody>
<tr valign="top">
<td width="250" {if sizeof($admins) > 0}style="padding-right: 10px;"{/if}>
<td width="250"{if sizeof($admins) > 0} style="padding-right: 10px;"{/if}>
<h4>{_statistics}</h4>
<div style="margin-top: 5px;">
{_on_this_instance_are}
@ -21,6 +21,15 @@
<li><span>{tr("about_wall_posts", $postsCount)|noescape}</span></li>
</ul>
</div>
{if OPENVK_ROOT_CONF['openvk']['preferences']['about']['links']}
<h4>{_about_links}</h4>
<div style="margin-top: 5px;">
{_instance_links}
<ul>
<li n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['about']['links'] as $aboutLink"><a href="{$aboutLink['url']}" target="_blank" class="link">{$aboutLink["name"]}</a></li>
</ul>
</div>
{/if}
</td>
<td n:if="sizeof($admins) > 0">
<h4>{_administrators}</h4>
@ -44,14 +53,23 @@
{if sizeof($popularClubs) !== 0}
<h4>{_most_popular_groups}</h4>
<ol>
<li n:foreach="$popularClubs as $entry" style="margin-top: 5px;">
<a href="{$entry->club->getURL()}">{$entry->club->getName()}</a>
<div>
{tr("participants", $entry->subscriptions)}
</div>
</li>
</ol>
{var $entries = array_chunk($popularClubs, 10, true)}
<table width="100%" cellspacing="0" cellpadding="0">
<tbody>
<tr valign="top">
<td n:foreach="$entries as $chunk">
<ol>
<li value="{$num+1}" style="margin-top: 5px;" n:foreach="$chunk as $num => $club">
<a href="{$club->club->getURL()}">{$club->club->getName()}</a>
<div>
{tr("participants", $club->subscriptions)}
</div>
</li>
</ol>
</td>
</tr>
</tbody>
</table>
{/if}
<h4>{_rules}</h4>

View file

@ -9,7 +9,7 @@
{presenter "openvk!Support->knowledgeBaseArticle", "about"}
<center>
<a class="button" style="margin-right: 5px;cursor: pointer;" href="/login">{_"log_in"}</a>
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" class="button" style="cursor: pointer;" href="/reg">{_"registration"}</a>
<a class="button" style="cursor: pointer;" href="/reg">{_"registration"}</a>
</center>
{* TO-DO: Add statistics about this instance as on mastodon.social *}
{/block}

View file

@ -1,12 +1,13 @@
{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
<style>
{var css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")}
{var $css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")}
{str_replace("fonts/", "/assets/packages/static/openvk/js/node_modules/@atlassian/aui/dist/aui/fonts/", $css)|noescape}
</style>
<title>{include title} - Админ-панель {=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</title>
<title>{include title} - {_admin} {$instance_name}</title>
</head>
<body>
<div id="page">
@ -16,23 +17,15 @@
<div class="aui-header-primary">
<h1 id="logo" class="aui-header-logo aui-header-logo-textonly">
<a href="/admin">
<span class="aui-header-logo-device">{=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</span>
<span class="aui-header-logo-device">{$instance_name}</span>
</a>
</h1>
</div>
<div n:if="$search ?? false" class="aui-header-secondary">
<ul class="aui-nav">
<form class="aui-quicksearch dont-default-focus ajs-dirty-warning-exempt">
<input
id="quickSearchInput"
autocomplete="off"
class="search"
type="text"
placeholder="{include searchTitle}"
value="{$_GET['q'] ?? ''}"
name="q"
accesskey="Q" />
<input type="hidden" value=1 name=p />
<input id="quickSearchInput" autocomplete="off" class="search" type="text" placeholder="{include searchTitle}" value="{$_GET['q'] ?? ''}" name="q" accesskey="Q" />
<input type="hidden" value=1 name=p />
</form>
</ul>
</div>
@ -46,83 +39,64 @@
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<div class="aui-nav-heading">
<strong>Обзор</strong>
<strong>{_admin_overview}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin">
Сводка
</a>
<a href="/admin">{_admin_overview_summary}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Пользовательский контент</strong>
<strong>{_admin_content}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin/users">
Пользователи
</a>
<a href="/admin/users">{_users}</a>
</li>
<li>
<a href="/admin/clubs">
Группы
</a>
<a href="/admin/clubs">{_groups}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Платные услуги</strong>
<strong>{_admin_services}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin/vouchers">
{_vouchers}
</a>
<a href="/admin/vouchers">{_vouchers}</a>
</li>
<li>
<a href="/admin/gifts">
Подарки
</a>
<a href="/admin/gifts">{_gifts}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Настройки</strong>
<strong>{_admin_settings}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin/settings/tuning">
Общие
</a>
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
</li>
<li>
<a href="/admin/settings/appearance">
Внешний вид
</a>
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
</li>
<li>
<a href="/admin/settings/security">
Безопасность
</a>
<a href="/admin/settings/security">{_admin_settings_security}</a>
</li>
<li>
<a href="/admin/settings/integrations">
Интеграции
</a>
<a href="/admin/settings/integrations">{_admin_settings_integrations}</a>
</li>
<li>
<a href="/admin/settings/system">
Система
</a>
<a href="/admin/settings/system">{_admin_settings_system}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Об OpenVK</strong>
<strong>{_admin_about}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/about:openvk">
Версия
</a>
<a href="/about:openvk">{_admin_about_version}</a>
</li>
<li>
<a href="/about">{_admin_about_instance}</a>
</li>
</ul>
</div>
@ -131,7 +105,7 @@
</div>
<section class="aui-page-panel-content">
{ifset $flashMessage}
{var type = ["err" => "error", "warn" => "warning", "info" => "basic", "succ" => "success"][$flashMessage->type]}
{var $type = ["err" => "error", "warn" => "warning", "info" => "basic", "succ" => "success"][$flashMessage->type]}
<div class="aui-message aui-message-{$type}" style="margin-bottom: 15px;">
<p class="title">
<strong>{$flashMessage->title}</strong>

View file

@ -1,191 +1,157 @@
{extends "@layout.xml"}
{block title}
Редактировать {$club->getCanonicalName()}
{_edit} {$club->getCanonicalName()}
{/block}
{block heading}
{$club->getCanonicalName()}
{/block}
{block content}
{var isMain = $mode === 'main'}
{var isBan = $mode === 'ban'}
{var isFollowers = $mode === 'followers'}
{var $isMain = $mode === 'main'}
{var $isBan = $mode === 'ban'}
{var $isFollowers = $mode === 'followers'}
{if $isMain}
<!-- This main block -->
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li class="aui-nav-selected"><a href="?act=main">Главное</a></li>
<li><a href="?act=ban">Бан</a></li>
<li><a href="?act=followers">Участники</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">
Аватарка
</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$club->getAvatarUrl()}" style="object-fit: cover;"></img>
</span>
</span>
</div>
<div class="field-group">
<label for="id">
ID
</label>
<input class="text medium-field" type="number" id="id" disabled value="{$club->getId()}" />
</div>
<div class="field-group">
<label for="id_owner">
ID владельца
</label>
<input class="text medium-field" type="text" id="id_owner" name="id_owner" value="{$club->getOwner()->getId()}" />
</div>
<div class="field-group">
<label for="name">
Название
</label>
<input class="text medium-field" type="text" id="name" name="name" value="{$club->getName()}" />
</div>
<div class="field-group">
<label for="about">
Описание
</label>
<input class="text medium-field" type="text" id="about" name="about" value="{$club->getDescription()}" />
</div>
<div class="field-group">
<label for="shortcode">
Адрес
</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$club->getShortCode()}" />
</div>
<br/>
<div class="group">
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
<label for="verify">
Верификация
</label>
</div>
<div class="group">
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
<label for="hide_from_global_feed">
Не отображать записи в глобальной ленте
</label>
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="Сохранить">
</div>
</div>
</form>
</div>
{/if}
{if $isBan}
<!-- This ban block -->
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">Главное</a></li>
<li class="aui-nav-selected"><a href="?act=ban">Бан</a></li>
<li><a href="?act=followers">Участники</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="ban_reason">
Причина бана
</label>
<input class="text" type="text" id="text-input" name="ban_reason" value="{$club->getBanReason()}" />
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="Сохранить">
</div>
</div>
</form>
</div>
{/if}
{if $isFollowers}
<!-- This followers block -->
{var followers = iterator_to_array($followers)}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">Главное</a></li>
<li><a href="?act=ban">Бан</a></li>
<li class="aui-nav-selected"><a href="?act=followers">Участники</a></li>
</ul>
</div>
</div>
</nav>
<table rules="none" class="aui aui-table-list">
<tbody>
<tr n:foreach="$followers as $follower">
<td>{$follower->getId()}</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
{if $isMain}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li class="aui-nav-selected"><a href="?act=main">{_admin_tab_main}</a></li>
<li><a href="?act=ban">{_admin_tab_ban}</a></li>
<li><a href="?act=followers">{_admin_tab_followers}</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">{_avatar}</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$follower->getAvatarUrl()}" alt="{$follower->getCanonicalName()}" role="presentation" />
<img src="{$club->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
</span>
</span>
</div>
<div class="field-group">
<label for="id">ID</label>
<input class="text medium-field" type="number" id="id" disabled value="{$club->getId()}" />
</div>
<div class="field-group">
<label for="id_owner">{_admin_ownerid}</label>
<input class="text medium-field" type="text" id="id_owner" name="id_owner" value="{$club->getOwner()->getId()}" />
</div>
<div class="field-group">
<label for="name">{_admin_title}</label>
<input class="text medium-field" type="text" id="name" name="name" value="{$club->getName()}" />
</div>
<div class="field-group">
<label for="about">{_admin_description}</label>
<input class="text medium-field" type="text" id="about" name="about" value="{$club->getDescription()}" />
</div>
<div class="field-group">
<label for="shortcode">{_admin_shortcode}</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$club->getShortCode()}" />
</div>
<br/>
<div class="group">
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
<label for="verify">{_admin_verification}</label>
</div>
<div class="group">
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
<label for="hide_from_global_feed">{_admin_club_excludeglobalfeed}</label>
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="{_save}">
</div>
</div>
</form>
</div>
{/if}
<a href="{$follower->getURL()}">{$follower->getCanonicalName()}</a>
{if $isBan}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">{_admin_tab_main}</a></li>
<li class="aui-nav-selected"><a href="?act=ban">{_admin_tab_ban}</a></li>
<li><a href="?act=followers">{_admin_tab_followers}</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="ban_reason">{_admin_banreason}</label>
<input class="text" type="text" id="text-input" name="ban_reason" value="{$club->getBanReason()}" />
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="{_save}">
</div>
</div>
</form>
</div>
{/if}
<span n:if="$follower->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
заблокирован
</span>
</td>
<td>{$follower->isFemale() ? "Женский" : "Мужской"}</td>
<td>{$follower->getShortCode() ?? "(отсутствует)"}</td>
<td>{$follower->getRegistrationTime()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/users/id{$follower->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
</a>
</td>
</tr>
</tbody>
</table>
<div align="right">
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
{if $isFollowers}
{var $followers = iterator_to_array($followers)}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
</div>
</div>
{/if}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">{_admin_tab_main}</a></li>
<li><a href="?act=ban">{_admin_tab_ban}</a></li>
<li class="aui-nav-selected"><a href="?act=followers">{_admin_tab_followers}</a></li>
</ul>
</div>
</div>
</nav>
<table rules="none" class="aui aui-table-list">
<tbody>
<tr n:foreach="$followers as $follower">
<td>{$follower->getId()}</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$follower->getAvatarUrl()}" alt="{$follower->getCanonicalName()}" role="presentation" />
</span>
</span>
<a href="{$follower->getURL()}">{$follower->getCanonicalName()}</a>
<span n:if="$follower->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
</td>
<td>{$follower->isFemale() ? tr("female") : tr("male")}</td>
<td>{$follower->getShortCode() ?? "(" . tr("none") . ")"}</td>
<td>{$follower->getRegistrationTime()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/users/id{$follower->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
</td>
</tr>
</tbody>
</table>
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
</div>
</div>
{/if}
{/block}

View file

@ -1,29 +1,31 @@
{extends "@layout.xml"}
{var search = true}
{var $search = true}
{block title}
Группы
{_admin_club_search}
{/block}
{block heading}
Бутылки
{_groups}
{/block}
{block searchTitle}Поиск бутылок{/block}
{block searchTitle}
{include title}
{/block}
{block content}
{var clubs = iterator_to_array($clubs)}
{var amount = sizeof($clubs)}
{var $clubs = iterator_to_array($clubs)}
{var $amount = sizeof($clubs)}
<table class="aui aui-table-list">
<thead>
<tr>
<th>#</th>
<th>Имя</th>
<th>Автор</th>
<th>Описание</th>
<th>Короткий адрес</th>
<th>Действия</th>
<th>ID</th>
<th>{_admin_title}</th>
<th>{_admin_author}</th>
<th>{_admin_description}</th>
<th>{_admin_shortcode}</th>
<th>{_admin_actions}</th>
</tr>
</thead>
<tbody>
@ -32,28 +34,28 @@
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$club->getAvatarUrl()}" alt="{$club->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
<img src="{$club->getAvatarUrl('miniscule')}" alt="{$club->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
</span>
</span>
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
</td>
<td>
{var user = $club->getOwner()}
{var $user = $club->getOwner()}
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$user->getAvatarUrl()}" alt="{$user->getCanonicalName()}" role="presentation" />
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" role="presentation" />
</span>
</span>
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
</td>
<td>{$club->getDescription() ?? "(не указано)"}</td>
<td>{$club->getDescription() ?? "(" . tr("none") . ")"}</td>
<td>{$club->getShortCode()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/clubs/id{$club->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
</td>
</tr>
@ -61,13 +63,9 @@
</table>
<br/>
<div align="right">
{var isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
{var $isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -2,9 +2,9 @@
{block title}
{if $form->id === 0}
Новый подарок
{_admin_newgift}
{else}
Подарок "{$form->name}"
{_gift} "{$form->name}"
{/if}
{/block}
@ -16,7 +16,7 @@
<form class="aui" method="POST" enctype="multipart/form-data">
<div class="field-group">
<label for="avatar">
Изображение
{_admin_image}
<span n:if="$form->id === 0" class="aui-icon icon-required"></span>
</label>
{if $form->id === 0}
@ -29,43 +29,39 @@
</span>
<input style="display: none;" id="picInput" type="file" name="pic" accept="image/jpeg,image/png,image/gif,image/webp" />
<div class="description">
<a id="picChange" href="javascript:false">Заменить изображение?</a>
<a id="picChange" href="javascript:false">{_admin_image_replace}</a>
</div>
{/if}
</div>
<div class="field-group">
<label for="id">
ID
</label>
<label for="id">ID</label>
<input class="text long-field" type="number" id="id" disabled="disabled" value="{$form->id}" />
</div>
<div class="field-group">
<label for="putin">
Использований
</label>
<input class="text long-field" type="number" id="putin" disabled="disabled" value="{$form->usages}" />
<label for="usages">{_admin_uses}</label>
<input class="text long-field" type="number" id="usages" disabled="disabled" value="{$form->usages}" />
<div n:if="$form->usages > 0" class="description">
<a href="javascript:$('#putin').value(0);">Обнулить?</a>
<a href="javascript:$('#usages').value(0);">{_admin_uses_reset}</a>
</div>
</div>
<div class="field-group">
<label for="name">
Внутренее имя
{_admin_name}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="text" id="name" name="name" value="{$form->name}" />
</div>
<div class="field-group">
<label for="price">
Цена
{_admin_price}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="number" id="price" name="price" min="0" value="{$form->price}" />
</div>
<div class="field-group">
<label for="limit">
Ограничение
{_admin_limits}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="number" min="-1" id="limit" name="limit" value="{$form->limit}" />
@ -76,7 +72,7 @@
<input n:attr="disabled => $form->id === 0, checked => $form->id === 0" class="checkbox" type="checkbox" name="reset_limit" id="reset_limit" />
<span class="aui-form-glyph"></span>
<label for="reset_limit">Сбросить счётчик ограничений</label>
<label for="reset_limit">{_admin_limits_reset}</label>
</div>
</fieldset>

View file

@ -1,7 +1,7 @@
{extends "@layout.xml"}
{block title}
Наборы подарков
{_admin_giftsets}
{/block}
{block headingWrap}
@ -9,7 +9,7 @@
{_create}
</a>
<h1>Наборы подарков</h1>
<h1>{_admin_giftsets}</h1>
{/block}
{block content}
@ -27,12 +27,11 @@
</td>
<td style="vertical-align: middle; text-align: right;">
<a class="aui-button aui-button-primary" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}.meta">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
<a class="aui-button" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}/">
<span class="aui-icon aui-icon-small aui-iconfont-gallery">Открыть</span>
Открыть
<span class="aui-icon aui-icon-small aui-iconfont-gallery">{_admin_open}</span>
</a>
</td>
</tr>
@ -40,17 +39,13 @@
</table>
{else}
<center>
<p>Наборов подарков нету. Чтобы создать подарок, создайте набор.</p>
<p>{_admin_giftsets_none}</p>
</center>
{/if}
<div align="right">
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -2,7 +2,7 @@
{block title}
{if $form->id === 0}
Создать набор подарков
{_admin_giftsets_create}
{else}
{$form->languages["master"]->name}
{/if}
@ -14,7 +14,7 @@
{block content}
<form class="aui" method="POST">
<h3>Общие настройки</h3>
<h3>{_admin_commonsettings}</h3>
<fieldset>
<div class="field-group">
<label for="id">
@ -24,37 +24,37 @@
</div>
<div class="field-group">
<label for="name_master">
Наименование
{_admin_name}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="text" id="name_master" name="name_master" value="{$form->languages['master']->name}" />
<div class="description">Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
<div class="description">{_admin_giftsets_title}</div>
</div>
<div class="field-group">
<label for="description_master">
Описание
{_admin_description}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="text" id="description_master" name="description_master" value="{$form->languages['master']->description}" />
<div class="description">Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
<div class="description">{_admin_giftsets_description}</div>
</div>
</fieldset>
<h3>Языко-зависимые настройки</h3>
<h3>{_admin_langsettings}</h3>
<fieldset>
{foreach $form->languages as $locale => $data}
{continueIf $locale === "master"}
<div class="field-group">
<label for="name_{$locale}">
Наименование
{_admin_name}
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
</label>
<input class="text long-field" type="text" id="name_{$locale}" name="name_{$locale}" value="{$data->name}" />
</div>
<div class="field-group">
<label for="description_{$locale}">
Описание
{_admin_description}
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
</label>
<input class="text long-field" type="text" id="description_{$locale}" name="description_{$locale}" value="{$data->description}" />

View file

@ -9,7 +9,7 @@
{_create}
</a>
<h1>Набор "{$cat->getName()}"</h1>
<h1>{_admin_giftset} "{$cat->getName()}"</h1>
{/block}
{block content}
@ -32,11 +32,11 @@
<td style="vertical-align: middle;">
{$gift->getName()}
<span n:if="$gift->isFree()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">
бесплатный
{_admin_price_free}
</span>
</td>
<td style="vertical-align: middle;">
{$gift->getPrice()} голосов
{tr("points_amount", $gift->getPrice())}
</td>
<td style="vertical-align: middle;">
{$gift->getUsages()} раз
@ -71,12 +71,9 @@
{/if}
<div align="right">
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -1,13 +1,13 @@
{extends "@layout.xml"}
{block title}
Сводка
{_admin_overview_summary}
{/block}
{block heading}
Сводка
{_admin_overview_summary}
{/block}
{block content}
Да!
┬─┬︵/(.□.)╯
{/block}

View file

@ -1,7 +1,7 @@
{extends "@layout.xml"}
{block title}
Редактировать {$user->getCanonicalName()}
{_edit} {$user->getCanonicalName()}
{/block}
{block heading}
@ -10,89 +10,70 @@
{block content}
<div class="aui-tabs horizontal-tabs">
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">
Аватарка
</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$user->getAvatarUrl()}" style="object-fit: cover;"></img>
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">{_avatar}</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$user->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
</span>
</span>
</span>
</div>
<div class="field-group">
<label for="id">
ID
</label>
<input class="text medium-field" type="number" id="id" disabled value="{$user->getId()}" />
</div>
<div class="field-group">
<label for="guid">
GUID
</label>
<input class="text medium-field" id="guid" disabled value="{$user->getChandlerUser()->getId()}" />
</div>
<div class="field-group">
<label for="first_name">
Имя
</label>
<input class="text medium-field" type="text" id="first_name" name="first_name" value="{$user->getFirstName()}" />
</div>
<div class="field-group">
<label for="last_name">
Фамилия
</label>
<input class="text medium-field" type="text" id="last_name" name="last_name" value="{$user->getLastName()}" />
</div>
<div class="field-group">
<label for="nickname">
Никнейм
</label>
<input class="text medium-field" type="text" id="nickname" name="nickname" value="{$user->getPseudo()}" />
</div>
<div class="field-group">
<label for="status">
Статус
</label>
<input class="text medium-field" type="text" id="status" name="status" value="{$user->getStatus()}" />
</div>
<div class="field-group">
<label for="email">
E-Mail
</label>
<input class="text medium-field" type="email" id="email" name="email" value="{$user->getEmail()}" />
</div>
<div class="field-group">
<label for="shortcode">
Адрес страницы
</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$user->getShortCode()}" />
</div>
<hr>
<div class="field-group">
<label for="city">
Верификация
</label>
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $user->isVerified()} checked {/if} />
</div>
<div class="field-group">
<label for="city">
Онлайн статус
</label>
<select name="online" class="select">
<option value="0" {if $user->onlineStatus() > 2}selected{/if}>По-умолчанию</option>
<option value="1" {if $user->onlineStatus() == 1}selected{/if}>Инкогнито</option>
<option value="2" {if $user->onlineStatus() == 2}selected{/if}>Юзер умер</option>
</select>
</div>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="Сохранить">
</div>
</div>
</form>
<div class="field-group">
<label for="id">ID</label>
<input class="text medium-field" type="number" id="id" disabled value="{$user->getId()}" />
</div>
<div class="field-group">
<label for="guid">GUID</label>
<input class="text medium-field" id="guid" disabled value="{$user->getChandlerUser()->getId()}" />
</div>
<div class="field-group">
<label for="registration_ip">{_admin_first_known_ip}</label>
<input class="text medium-field" id="guid" disabled value="{$user->getRegistrationIP()}" />
</div>
<div class="field-group">
<label for="first_name">{_name}</label>
<input class="text medium-field" type="text" id="first_name" name="first_name" value="{$user->getFirstName()}" />
</div>
<div class="field-group">
<label for="last_name">{_surname}</label>
<input class="text medium-field" type="text" id="last_name" name="last_name" value="{$user->getLastName()}" />
</div>
<div class="field-group">
<label for="nickname">{_nickname}</label>
<input class="text medium-field" type="text" id="nickname" name="nickname" value="{$user->getPseudo()}" />
</div>
<div class="field-group">
<label for="status">{_status}</label>
<input class="text medium-field" type="text" id="status" name="status" value="{$user->getStatus()}" />
</div>
<div class="field-group">
<label for="email">E-Mail</label>
<input class="text medium-field" type="email" id="email" name="email" value="{$user->getEmail()}" />
</div>
<div class="field-group">
<label for="shortcode">{_admin_shortcode}</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$user->getShortCode()}" />
</div>
<hr>
<div class="field-group">
<label for="city">{_admin_verification}</label>
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $user->isVerified()} checked {/if} />
</div>
<div class="field-group">
<label for="city">{_admin_user_online}</label>
<select name="online" class="select">
<option value="0" {if $user->onlineStatus() > 2}selected{/if}>{_admin_user_online_default}</option>
<option value="1" {if $user->onlineStatus() == 1}selected{/if}>{_admin_user_online_incognite}</option>
<option value="2" {if $user->onlineStatus() == 2}selected{/if}>{_admin_user_online_deceased}</option>
</select>
</div>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="{_save}">
</div>
</div>
</form>
</div>
{/block}

View file

@ -1,29 +1,31 @@
{extends "@layout.xml"}
{var search = true}
{var $search = true}
{block title}
Пользователи
{_admin_user_search}
{/block}
{block heading}
Пиздюки
{_users}
{/block}
{block searchTitle}Поиск пиздюков{/block}
{block searchTitle}
{include title}
{/block}
{block content}
{var users = iterator_to_array($users)}
{var amount = sizeof($users)}
{var $users = iterator_to_array($users)}
{var $amount = sizeof($users)}
<table class="aui aui-table-list">
<thead>
<tr>
<th>#</th>
<th>Имя</th>
<th>Пол</th>
<th>Короткий адрес</th>
<th>Дата регистрации</th>
<th>Действия</th>
<th>ID</th>
<th>{_admin_name}</th>
<th>{_gender}</th>
<th>{_admin_shortcode}</th>
<th>{_registration_date}</th>
<th>{_admin_actions}</th>
</tr>
</thead>
<tbody>
@ -32,26 +34,24 @@
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$user->getAvatarUrl()}" alt="{$user->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
</span>
</span>
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
заблокирован
</span>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
</td>
<td>{$user->isFemale() ? "Женский" : "Мужской"}</td>
<td>{$user->getShortCode() ?? "(отсутствует)"}</td>
<td>{$user->isFemale() ? tr("female") : tr("male")}</td>
<td>{$user->getShortCode() ?? "(" . tr("none") . ")"}</td>
<td>{$user->getRegistrationTime()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/users/id{$user->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
{if $thisUser->getChandlerUser()->can("substitute")->model('openvk\Web\Models\Entities\User')->whichBelongsTo(0)}
<a class="aui-button" href="/setSID/{$user->getChandlerUser()->getId()}?hash={rawurlencode($csrfToken)}">
<span class="aui-icon aui-icon-small aui-iconfont-sign-in">Войти как</span>
<span class="aui-icon aui-icon-small aui-iconfont-sign-in">{_admin_loginas}</span>
</a>
{/if}
</td>
@ -60,13 +60,9 @@
</table>
<br/>
<div align="right">
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -5,45 +5,36 @@
{/block}
{block heading}
{_edit} {$form->token ?? "undefined"}
{_edit} #{$form->token ?? "undefined"}
{/block}
{block content}
<div style="margin: 8px -8px;" class="aui-tabs horizontal-tabs">
<ul class="tabs-menu">
<li class="menu-item active-tab">
<a href="#info">Информация</a>
<a href="#info">{_admin_tab_main}</a>
</li>
<li class="menu-item">
<a href="#activators">{_voucher_activators}</a>
</li>
</ul>
<div class="tabs-pane active-pane" id="info">
<form class="aui" method="POST">
<div class="field-group">
<label for="id">
ID
</label>
<label for="id">ID</label>
<input class="text long-field" type="number" id="id" name="id" disabled value="{$form->id}" />
</div>
<div class="field-group">
<label for="token">
Серийный номер
</label>
<label for="token">{_admin_voucher_serial}</label>
<input class="text long-field" type="text" id="token" name="token" value="{$form->token}" />
<div class="description">Номер состоит из 24 символов, если формат неправильный или поле не заполнено, будет назначен автоматически.</div>
<div class="description">{_admin_voucher_serial_desc}</div>
</div>
<div class="field-group">
<label for="coins">
Количество голосов
</label>
<label for="coins">{_admin_voucher_coins}</label>
<input class="text long-field" type="number" min="0" id="coins" name="coins" value="{$form->coins}" />
</div>
<div class="field-group">
<label for="rating">
Количество рейтинга
</label>
<label for="rating">{_admin_voucher_rating}</label>
<input class="text long-field" type="number" min="0" id="rating" name="rating" value="{$form->rating}" />
</div>
<div class="field-group">
@ -55,7 +46,7 @@
{/if}
</label>
<input class="text long-field" type="number" min="-1" id="usages" name="usages" value="{$form->usages}" />
<div class="description">Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет Infinity.</div>
<div class="description">{_admin_voucher_usages_desc}</div>
</div>
<div class="buttons-container">
@ -66,7 +57,6 @@
</div>
</form>
</div>
<div class="tabs-pane" id="activators">
<table rules="none" class="aui aui-table-list">
<tbody>
@ -74,15 +64,13 @@
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$user->getAvatarUrl()}" alt="{$user->getCanonicalName()}" role="presentation" />
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" role="presentation" />
</span>
</span>
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
заблокирован
</span>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
</td>
</tr>
</tbody>

View file

@ -16,13 +16,13 @@
<table class="aui aui-table-list">
<thead>
<tr>
<th>#</th>
<th>Серийный номер</th>
<th>Голоса</th>
<th>Рейгтинг</th>
<th>Осталось использований</th>
<th>Состояние</th>
<th>Действия</th>
<th>ID</th>
<th>{_admin_voucher_serial}</th>
<th>{_coins}</th>
<th>{_admin_voucher_rating}</th>
<th>{_usages_left}</th>
<th>{_admin_voucher_status}</th>
<th>{_admin_actions}</th>
</tr>
</thead>
<tbody>
@ -34,14 +34,14 @@
<td>{$voucher->getRemainingUsages() === INF ? "∞" : $voucher->getRemainingUsages()}</td>
<td>
{if $voucher->isExpired()}
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">закончился</span>
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_voucher_status_closed}</span>
{else}
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">активен</span>
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">{_admin_voucher_status_opened}</span>
{/if}
</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/vouchers/id{$voucher->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
</td>
</tr>
@ -50,12 +50,9 @@
<br/>
<div align="right">
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -18,7 +18,7 @@
<span>{_code}: </span>
</td>
<td>
<input type="text" name="code" required />
<input type="text" name="code" autocomplete="off" required />
</td>
</tr>
<tr>

View file

@ -47,7 +47,7 @@
<span>{_"gender"}: </span>
</td>
<td>
{var femalePreferred = OPENVK_ROOT_CONF["openvk"]["preferences"]["femaleGenderPriority"]}
{var $femalePreferred = OPENVK_ROOT_CONF["openvk"]["preferences"]["femaleGenderPriority"]}
<select name="sex" required>
<option n:attr="selected => !$femalePreferred" value="male">{_"male"}</option>
<option n:attr="selected => $femalePreferred" value="female">{_"female"}</option>

View file

@ -92,7 +92,7 @@
<span class="nobold">{_group_administrators_list}: </span>
</td>
<td>
{var areAllAdminsHidden = $club->getManagersCount(true) == 0}
{var $areAllAdminsHidden = $club->getManagersCount(true) == 0}
<input type="radio" name="administrators_list_display" value="0" n:attr="checked => $club->getAdministratorsListDisplay() == 0, disabled => $areAllAdminsHidden" /> {_group_display_only_creator}<br>
<input type="radio" name="administrators_list_display" value="1" n:attr="checked => $club->getAdministratorsListDisplay() == 1, disabled => $areAllAdminsHidden" /> {_group_display_all_administrators}<br>
<input type="radio" name="administrators_list_display" value="2" n:attr="checked => $club->getAdministratorsListDisplay() == 2" /> {_group_dont_display_administrators_list}<br>

View file

@ -1,9 +1,9 @@
{extends "../@listView.xml"}
{var $Manager = openvk\Web\Models\Entities\Manager::class}
{var iterator = $onlyShowManagers ? $managers : $followers}
{var count = $paginatorConf->count}
{var page = $paginatorConf->page}
{var perPage = 6}
{var $iterator = $onlyShowManagers ? $managers : $followers}
{var $count = $paginatorConf->count}
{var $page = $paginatorConf->page}
{var $perPage = 6}
{block title}{_followers} {$club->getCanonicalName()}{/block}
@ -41,7 +41,7 @@
{/block}
{block preview}
<img src="{$x instanceof $Manager ? $x->getUser()->getAvatarURL() : $x->getAvatarURL()}" alt="{$x instanceof $Manager ? $x->getUser()->getCanonicalName() : $x->getCanonicalName()}" width=75 />
<img src="{$x instanceof $Manager ? $x->getUser()->getAvatarURL() : $x->getAvatarURL('miniscule')}" alt="{$x instanceof $Manager ? $x->getUser()->getCanonicalName() : $x->getCanonicalName()}" width=75 />
{/block}
{block name}
@ -49,8 +49,8 @@
{/block}
{block description}
{var user = $x instanceof $Manager ? $x->getUser() : $x}
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
{var $user = $x instanceof $Manager ? $x->getUser() : $x}
{var $manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
<table>
<tbody>
<tr>
@ -106,8 +106,8 @@
{/block}
{block actions}
{var user = $x instanceof $Manager ? $x->getUser() : $x}
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
{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?user={$user->getId()}&hash={rawurlencode($csrfToken)}" n:if="$club->getOwner()->getId() !== $user->getId()">
{if $manager}

View file

@ -14,6 +14,8 @@
{block content}
<div class="left_big_block">
<div n:if="!is_null($alert = $club->getAlert())" class="group-alert">{strpos($alert, "@") === 0 ? tr(substr($alert, 1)) : $alert}</div>
<div class="content_title_expanded" onclick="hidePanel(this);">
{_"information"}
</div>
@ -41,7 +43,7 @@
</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}
@ -57,7 +59,7 @@
<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()}" />
<img class="ava" src="{$follower->getAvatarUrl('miniscule')}" />
</a>
</div>
<a href="{$follower->getURL()}" class="cl_name">
@ -91,10 +93,10 @@
{presenter "openvk!Wall->wallEmbedded", -$club->getId()}
</div>
<div class="right_small_block">
{var avatarPhoto = $club->getAvatarPhoto()}
{var avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())}
{var $avatarPhoto = $club->getAvatarPhoto()}
{var $avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())}
<a href="{$avatarLink|nocheck}">
<img src="{$club->getAvatarUrl()}" style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
<img src="{$club->getAvatarUrl('normal')}" style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
</a>
<div n:ifset="$thisUser" id="profile_links">
{if $club->canBeModifiedBy($thisUser)}
@ -132,7 +134,7 @@
{_"creator"}
</div>
<div class="avatar-list-item" style="padding: 8px;">
{var author = $club->getOwner()}
{var $author = $club->getOwner()}
<div class="avatar">
<a href="{$author->getURL()}">
<img class="ava" src="{$author->getAvatarUrl()}" />
@ -149,7 +151,7 @@
</div>
</div>
<div n:if="$club->getAdministratorsListDisplay() == 1">
{var managersCount = $club->getManagersCount(true)}
{var $managersCount = $club->getManagersCount(true)}
<div class="content_title_expanded" onclick="hidePanel(this, {$managersCount});">
{_"administrators"}
@ -163,7 +165,7 @@
</div>
<div class="avatar-list">
<div class="avatar-list-item" n:if="!$club->isOwnerHidden()">
{var author = $club->getOwner()}
{var $author = $club->getOwner()}
<div class="avatar">
<a href="{$author->getURL()}">
<img class="ava" src="{$author->getAvatarUrl()}" />
@ -175,7 +177,7 @@
</div>
</div>
<div class="avatar-list-item" n:foreach="$club->getManagers(1, true) as $manager">
{var user = $manager->getUser()}
{var $user = $manager->getUser()}
<div class="avatar">
<a href="{$user->getURL()}">
<img height="32" class="ava" src="{$user->getAvatarUrl()}" />
@ -203,7 +205,7 @@
<div style="padding: 5px;">
<div class="ovk-album" style="display: inline-block;" n:foreach="$albums as $album">
<div style="text-align: center;float: left;height: 54pt;width: 100px;">
{var cover = $album->getCoverPhoto()}
{var $cover = $album->getCoverPhoto()}
<img
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURL()}"

View file

@ -6,7 +6,7 @@
<a href="/im">{_my_messages}</a> »
<a href="{$correspondent->getURL()}">{$correspondent->getCanonicalName()}</a>
<div n:if="($online = $correspondent->getOnline()->timestamp()) + 2505600 > time()" style="float: right;">
{var diff = date_diff(date_create(), date_create('@' . $online))}
{var $diff = date_diff(date_create(), date_create('@' . $online))}
{if 5 >= $diff->i}
<span><b>{_online}</b></span>
{else}
@ -44,7 +44,7 @@
</div>
<div class="messenger-app--input">
{if $correspondent->getId() === $thisUser->getId() || $correspondent->getPrivacyPermission('messages.write', $thisUser)}
<img class="ava" src="{$thisUser->getAvatarUrl()}" alt="{$thisUser->getCanonicalName()}" />
<img class="ava" src="{$thisUser->getAvatarUrl('miniscule')}" alt="{$thisUser->getCanonicalName()}" />
<div class="messenger-app--input---messagebox">
<textarea
data-bind="value: messageContent, event: { keydown: onTextareaKeyPress }"
@ -52,7 +52,7 @@
placeholder="Введите сообщение"></textarea>
<button class="button" data-bind="click: sendMessage">Отправить</button>
</div>
<img class="ava" src="{$correspondent->getAvatarUrl()}" alt="{$correspondent->getCanonicalName()}" />
<img class="ava" src="{$correspondent->getAvatarUrl('miniscule')}" alt="{$correspondent->getCanonicalName()}" />
{else}
<div class="blocked" data-localized-text="Вы не можете писать сообщения {$correspondent->getCanonicalName()} из-за его настроек приватности."></div>
{/if}

View file

@ -21,11 +21,11 @@
<div n:foreach="$corresps as $coresp"
class="crp-entry"
onmousedown="window.location.href = {$coresp->getURL()};" >
{var recipient = $coresp->getCorrespondents()[1]}
{var lastMsg = $coresp->getPreviewMessage()}
{var $recipient = $coresp->getCorrespondents()[1]}
{var $lastMsg = $coresp->getPreviewMessage()}
<div class="crp-entry--image">
<img src="{$recipient->getAvatarURL()}"
<img src="{$recipient->getAvatarURL('miniscule')}"
alt="Фотография пользователя" />
</div>
<div class="crp-entry--info">
@ -33,10 +33,10 @@
<span>{$lastMsg->getSendTime()->format("%e %B %G" . tr("time_at_sp") . "%X")}</span>
</div>
<div n:class="crp-entry--message, $lastMsg->getUnreadState() ? unread">
{var _author = $lastMsg->getSender()}
{var $_author = $lastMsg->getSender()}
<div class="crp-entry--message---av" n:if="$_author->getId() === $thisUser->getId()">
<img src="{$_author->getAvatarURL()}"
<img src="{$_author->getAvatarURL('miniscule')}"
alt="Фотография пользователя" />
</div>
<div class="crp-entry--message---text">

View file

@ -3,7 +3,7 @@
{block title}{_edit_note}{/block}
{block header}
{var author = $note->getOwner()}
{var $author = $note->getOwner()}
<a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
»
<a href="/notes{$author->getId()}">{_notes}</a>

View file

@ -1,6 +1,6 @@
{extends "../@listView.xml"}
{var iterator = iterator_to_array($notes)}
{var page = $paginatorConf->page}
{var $iterator = iterator_to_array($notes)}
{var $page = $paginatorConf->page}
{block title}{_notes}{/block}
@ -34,15 +34,41 @@
{* BEGIN ELEMENTS DESCRIPTION *}
{block specpage}
<style>
#userContent img {
max-width: 245pt;
max-height: 200pt;
}
#userContent blockquote {
background-color: #f3f3f3;
border-bottom: 5px solid #969696;
padding: 1;
}
#userContent cite {
margin-top: 1em;
display: block;
}
#userContent cite::before {
content: "— ";
}
#userContent .underline {
text-decoration: underline;
}
</style>
<div class="container_gray" style="background: white; border-top: none;">
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{if sizeof($data) > 0}
<div n:foreach="$data as $dat">
<div class="profile_thumb">
<a href="{$owner->getURL()}">
<img src="{$owner->getAvatarUrl()}" style="width: 50px;">
<img src="{$owner->getAvatarUrl('miniscule')}" style="width: 50px;">
</a>
</div>
<article class="note_body" id="userContent" style="width: 540px; display: inline-block; margin-bottom: 35px;">

View file

@ -3,7 +3,7 @@
{block title}{$note->getName()}{/block}
{block header}
{var author = $note->getOwner()}
{var $author = $note->getOwner()}
<a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
»
<a href="/notes{$author->getId()}">{_notes}</a>
@ -12,7 +12,7 @@
{/block}
{block content}
{var author = $note->getOwner()}
{var $author = $note->getOwner()}
<style>
#userContent img {
max-width: 245pt;
@ -33,6 +33,10 @@
#userContent cite::before {
content: "— ";
}
#userContent .underline {
text-decoration: underline;
}
</style>
<article id="userContent" style="margin: 10px 10px 0;">

View file

@ -1,5 +1,5 @@
{extends "../@listView.xml"}
{var sorting = false}
{var $sorting = false}
{block title}
{_feedback}
@ -26,7 +26,7 @@
{/block}
{block preview}
<img src="{$x->getModel(1)->getAvatarUrl()}" width=64 />
<img src="{$x->getModel(1)->getAvatarUrl('miniscule')}" width=64 />
{/block}
{block name}

View file

@ -3,7 +3,7 @@
{block title}Альбом {$album->getName()}{/block}
{block header}
{var isClub = ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)}
{var $isClub = ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)}
<a href="{$album->getOwner()->getURL()}">
{$album->getOwner()->getCanonicalName()}

View file

@ -1,16 +1,35 @@
{extends "../@listView.xml"}
{var iterator = iterator_to_array($albums)}
{var page = $paginatorConf->page}
{var $iterator = iterator_to_array($albums)}
{var $page = $paginatorConf->page}
{block title}{_"albums"} {$owner->getCanonicalName()}{/block}
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
» {_"albums"}
{if isset($thisUser) && $thisUser->getId() == $owner->getId()}
{_my_photos}
{else}
<a href="{$owner->getURL()}">
{$owner->getCanonicalName()}</a>
»
{_albums}
{/if}
{/block}
<div n:if="$canEdit" style="float: right;">
{var isClub = ($owner instanceof openvk\Web\Models\Entities\Club)}
<a href="/albums/create{$isClub ? '?gpid=' . $owner->getId() : ''}">{_"create_album"}</a>
{block size}
<div style="padding-bottom: 0px; padding-top: 0;" class="summaryBar">
<div class="summary">
{if !is_null($thisUser) && $owner->getId() === $thisUser->getId()}
{tr("albums_list", $count)}
{else}
{tr("albums", $count)}
{/if}
<span n:if="$canEdit" style="float: right;">
&nbsp;|&nbsp;
{var $isClub = ($owner instanceof \openvk\Web\Models\Entities\Club)}
<a href="/albums/create{$isClub ? '?gpid=' . $owner->getId() : ''}">{_create_album}</a>
</span>
</div>
</div>
{/block}
@ -25,8 +44,8 @@
{/block}
{block preview}
{var cover = $x->getCoverPhoto()}
{var preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURL()}
{var $cover = $x->getCoverPhoto()}
{var $preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURLBySizeId("normal")}
<a href="/album{$x->getPrettyId()}">
<img src="{$preview}" alt="{$x->getName()}" style="height: 130px; width: 170px; object-fit: cover" />

View file

@ -21,7 +21,7 @@
{block content}
<center style="margin-bottom: 8pt;">
<img src="{$photo->getURL()}" style="max-width: 80%; max-height: 60vh;" />
<img src="{$photo->getURLBySizeId('large')}" style="max-width: 80%; max-height: 60vh;" />
</center>
<hr/>

View file

@ -50,7 +50,7 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl()}" width="75" alt="{_"photo"}" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="{_"photo"}" />
{/block}
{block name}

View file

@ -47,7 +47,7 @@
<tr>
{if $comment->getUType() === 0}
<td width="54" valign="top">
<img src="{$comment->getUser()->getAvatarUrl()}" width="50" />
<img src="{$comment->getUser()->getAvatarUrl('miniscule')}" width="50" />
</td>
{else}
<td width="54" valign="top">
@ -70,7 +70,7 @@
{if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
<a href="{$comment->getUser()->getURL()}">
<span class="nobold">
{var lastName = $comment->getUser()->getLastName()}
{var $lastName = $comment->getUser()->getLastName()}
{if empty(trim($lastName))}
({$comment->getUser()->getFirstName()})
{else}

View file

@ -6,9 +6,9 @@
{/block}
{block content}
{var isMain = $mode === 'faq'}
{var isNew = $mode === 'new'}
{var isList = $mode === 'list'}
{var $isMain = $mode === 'faq'}
{var $isNew = $mode === 'new'}
{var $isList = $mode === 'list'}
{if $thisUser}
<div class="tabs">
@ -26,16 +26,26 @@
<br />
{if $isNew}
<div class="new">
<form action="/support" method="post" style="margin:0;">
<center>
<input name="name" style="width: 80%; resize: vertical;" placeholder="{_support_new_title}" /><br /><br />
<textarea name="text" style="width: 80%; resize: vertical;" placeholder="{_support_new_content}"></textarea><br /><br />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_write}" class="button" style="margin-left: 70%;" /><br /><br />
</center>
</form>
</div>
{if !is_null($banReason)}
<center>
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_'banned_alt'}" style="width: 20%;" />
</center>
<p>
{tr("banned_in_support_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/>
{tr("banned_in_support_2", htmlentities($banReason))|noescape}
</p>
{else}
<div class="new">
<form action="/support" method="post" style="margin:0;">
<center>
<input name="name" style="width: 80%; resize: vertical;" placeholder="{_support_new_title}" /><br /><br />
<textarea name="text" style="width: 80%; resize: vertical;" placeholder="{_support_new_content}"></textarea><br /><br />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_write}" class="button" style="margin-left: 70%;" /><br /><br />
</center>
</form>
</div>
{/if}
{/if}
{/if}

View file

@ -37,7 +37,7 @@
{/block}
{block description}
{var author = $x->getUser()}
{var $author = $x->getUser()}
{ovk_proc_strtr($x->getContext(), 50)}<br/>
<span class="nobold">{_author}: </span> <a href="{$author->getURL()}">{$author->getCanonicalName()}</a>

View file

@ -60,7 +60,7 @@
<tr>
{if $comment->getUType() === 0}
<td width="54" valign="top">
<img src="{$comment->getUser()->getAvatarUrl()}" width="50" />
<img src="{$comment->getUser()->getAvatarUrl('miniscule')}" width="50" />
</td>
{else}
<td width="54" valign="top">
@ -109,7 +109,7 @@
{if $comment->getUType() === 1}
<div class="post-menu">
{var isLikedByUser = $comment->isLikedByUser()}
{var $isLikedByUser = $comment->isLikedByUser()}
<strong id="markText-{$comment->getId()}">
{if !is_null($isLikedByUser)}
{if $comment->isLikedByUser()}

View file

@ -1,6 +1,6 @@
{extends "../@listView.xml"}
{var iterator = iterator_to_array($topics)}
{var page = $paginatorConf->page}
{var $iterator = iterator_to_array($topics)}
{var $page = $paginatorConf->page}
{block title}{_discussions} {$club->getCanonicalName()}{/block}
@ -46,7 +46,7 @@
<div style="float: left;">
{tr("messages", $x->getCommentsCount())}
</div>
{var lastComment = $x->getLastComment()}
{var $lastComment = $x->getLastComment()}
<div n:if="$lastComment" class="avatar-list-item" style="float: right;">
<div class="avatar">
<a href="{$lastComment->getOwner()->getURL()}">

View file

@ -7,10 +7,10 @@
{block content}
{var isMain = $mode === 'main'}
{var isContacts = $mode === 'contacts'}
{var isInterests = $mode === 'interests'}
{var isAvatar = $mode === 'avatar'}
{var $isMain = $mode === 'main'}
{var $isContacts = $mode === 'contacts'}
{var $isInterests = $mode === 'interests'}
{var $isAvatar = $mode === 'avatar'}
<div n:if="$user->hasPendingNumberChange()" class="msg">
<b>Подтверждение номера телефона</b><br/>
Введите код для подтверждения смены номера: <a href="/edit/verify_phone">ввести код</a>.

View file

@ -1,17 +1,17 @@
{extends "../@listView.xml"}
{var perPage = 6} {* Why 6? Check User::_abstractRelationGenerator *}
{var $perPage = 6} {* Why 6? Check User::_abstractRelationGenerator *}
{var act = $_GET["act"] ?? "friends"}
{var $act = $_GET["act"] ?? "friends"}
{if $act == "incoming"}
{var iterator = iterator_to_array($user->getFollowers($page))}
{var count = $user->getFollowersCount()}
{var $iterator = iterator_to_array($user->getFollowers($page))}
{var $count = $user->getFollowersCount()}
{elseif $act == "outcoming"}
{var iterator = iterator_to_array($user->getSubscriptions($page))}
{var count = $user->getSubscriptionsCount()}
{var $iterator = iterator_to_array($user->getSubscriptions($page))}
{var $count = $user->getSubscriptionsCount()}
{else}
{var iterator = iterator_to_array($user->getFriends($page))}
{var count = $user->getFriendsCount()}
{var $iterator = iterator_to_array($user->getFriends($page))}
{var $count = $user->getFriendsCount()}
{/if}
{block title}
@ -25,13 +25,17 @@
{/block}
{block header}
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
{if $act == "incoming"}
{_"incoming_req"}
{elseif $act == "outcoming"}
{_"outcoming_req"}
{if isset($thisUser) && $thisUser->getId() == $user->getId()}
{_my_friends}
{else}
{_"friends"}
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
{if $act == "incoming"}
{_"incoming_req"}
{elseif $act == "outcoming"}
{_"outcoming_req"}
{else}
{_"friends"}
{/if}
{/if}
{/block}
@ -39,11 +43,38 @@
<div n:attr="id => ($act === 'friends' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($act === 'friends' ? 'act_tab_a' : 'ki')" href="?">{_friends}</a>
</div>
<div n:attr="id => ($act === 'incoming' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($act === 'incoming' ? 'act_tab_a' : 'ki')" href="?act=incoming">{_incoming_req}</a>
<div n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'act_tab_a' : 'ki')" href="?act=incoming">{_req}</a>
</div>
<div n:attr="id => ($act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($act === 'outcoming' ? 'act_tab_a' : 'ki')" href="?act=outcoming">{_outcoming_req}</a>
{/block}
{block size}
<div n:if="$act === 'incoming' || $act === 'outcoming'" class="mb_tabs">
<div n:attr="id => ($act === 'incoming' ? 'active' : 'ki')" class="mb_tab">
<div>
<a href="?act=incoming">{_incoming_req}</a>
</div>
</div>
<div n:attr="id => ($act === 'outcoming' ? 'active' : 'ki')" class="mb_tab">
<div>
<a href="?act=outcoming">{_outcoming_req}</a>
</div>
</div>
</div>
<div style="padding-bottom: 0px;" class="summaryBar">
<div class="summary">
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
{if $act == "incoming"}
{tr("req", $count)}
{elseif $act == "outcoming"}
{tr("req", $count)}
{else}
{tr("friends_list", $count)}
{/if}
{else}
{tr("friends", $count)}
{/if}
</div>
</div>
{/block}
@ -54,7 +85,7 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" />
{/block}
{block name}
@ -82,7 +113,7 @@
{block actions}
{if $x->getId() !== $thisUser->getId()}
{var subStatus = $x->getSubscriptionStatus($thisUser)}
{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" />

View file

@ -1,6 +1,6 @@
{extends "../@listView.xml"}
{var iterator = $user->getClubs($page, $admin)}
{var count = $user->getClubCount($admin)}
{var $iterator = $user->getClubs($page, $admin)}
{var $count = $user->getClubCount($admin)}
{block title}
{_groups}
@ -32,7 +32,7 @@
{/block}
{block size}
<div n:if="!is_null($thisUser) && $user->getId() === $thisUser->getId()" style="padding-bottom: 0px; border-bottom: 0;" class="summaryBar">
<div style="padding-bottom: 0px;" class="summaryBar">
<div class="summary">
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
{tr("groups_list", $thisUser->getClubCount())}
@ -48,12 +48,12 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография группы" />
{/block}
{block name}{/block}
{block infoTable}
{block infotable}
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
@ -73,26 +73,28 @@
{/block}
{block actions}
{var clubPinned = $thisUser->isClubPinned($x)}
{var $clubPinned = $thisUser->isClubPinned($x)}
{if $x->canBeModifiedBy($thisUser ?? NULL)}
<a style="width: 140px;" class="profile_link" href="{$x->getURL()}">
{_check_community}
</a>
{if ($clubPinned || $thisUser->getPinnedClubCount() <= 10)}
<a style="width: 140px;" 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}
<div class="navigation" style="width: 140px;">
<a class="link" href="{$x->getURL()}">
{_check_community}
</a>
{/if}
<form action="/setSub/club" method="post">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input style="width: 140px; text-transform: lowercase;" type="submit" id="profile_link" value="{_leave_community}" />
</form>
{if ($clubPinned || $thisUser->getPinnedClubCount() <= 10)}
<a class="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>
{/if}
<form action="/setSub/club" method="post">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input style="text-transform: lowercase; width: 100%;" class="link" type="submit" value="{_"leave_community"}" />
</form>
</div>
{/if}
{/block}

View file

@ -7,11 +7,11 @@
{block content}
{var isMain = $mode === 'main'}
{var isPrivacy = $mode === 'privacy'}
{var isFinance = $mode === 'finance'}
{var isFinanceTU = $mode === 'finance.top-up'}
{var isInterface = $mode === 'interface'}
{var $isMain = $mode === 'main'}
{var $isPrivacy = $mode === 'privacy'}
{var $isFinance = $mode === 'finance'}
{var $isFinanceTU = $mode === 'finance.top-up'}
{var $isInterface = $mode === 'interface'}
<div class="tabs">
<div n:attr="id => ($isMain ? 'activetabs' : 'ki')" class="tab">
@ -347,16 +347,22 @@
<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;">
<b>
{_on_your_account}<br/>
<span style="font-size: 50px;">{$thisUser->getCoins()}</span><br/>
{_points_count}<br/><br/>
<small><a href="?act=finance.top-up">[{_have_voucher}?]</a></small>
</b>
<div style="margin: 0; font-size: medium; text-align: center; font-weight: 900;">
{_on_your_account}
<div style="width: 100%; height: 60px; font-weight: 100;" id="balance">{$thisUser->getCoins()}</div>
{_points_count}<br/>
<small><a href="?act=finance.top-up">[{_have_voucher}?]</a></small>
</p>
</div>
{script "js/node_modules/textfit/textFit.min.js"}
<script>
let balance = document.querySelector("#balance");
balance.style.width = Math.ceil(balance.parentNode.getBoundingClientRect().width) + "px";
textFit(balance, { alignVert: true });
</script>
{elseif $isFinanceTU}
<p>{_voucher_explanation} {_voucher_explanation_ex}</p>
@ -407,7 +413,7 @@
</td>
<td>
<select name="style_avatar">
<option value="0" {if $user->getStyleAvatar() == 0}selected{/if}>{_"default"}</option>
<option value="0" {if $user->getStyleAvatar() == 0}selected{/if}>{_"arbitrary_avatars"} ({_"default"})</option>
<option value="1" {if $user->getStyleAvatar() == 1}selected{/if}>{_"cut"}</option>
<option value="2" {if $user->getStyleAvatar() == 2}selected{/if}>{_"round_avatars"}</option>
</select>

View file

@ -7,7 +7,7 @@
<!-- openGraph -->
<meta property="og:title" content="{$user->getCanonicalName()}" />
<meta property="og:url" content="http://{$_SERVER['HTTP_HOST']}{$user->getURL()}" />
<meta property="og:image" content="{$user->getAvatarUrl()}" />
<meta property="og:image" content="{$user->getAvatarUrl('normal')}" />
<meta property="og:type" content="profile" />
<meta property="og:first_name" content="{$user->getFirstName()}" />
<meta property="og:last_name" content="{$user->getLastName()}" />
@ -62,7 +62,7 @@
<div class="left_small_block">
<div>
<a href="{$user->getAvatarLink()|nocheck}">
<img src="{$user->getAvatarUrl()}"
<img src="{$user->getAvatarUrl('normal')}"
alt="{$user->getCanonicalName()}"
style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
</a>
@ -72,7 +72,7 @@
<div id="profile_link" style="width: 194px;">
<a href="/edit" class="link">{_"edit_page"}</a>
</div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce']" id="profile_link" style="width: 194px;">
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && !$thisUser->prefersNotToSeeRating()" id="profile_link" style="width: 194px;">
<a onClick="showIncreaseRatingDialog({$thisUser->getCoins()}, {ltrim($thisUser->getUrl(), '/')}, {$csrfToken})" class="link">{_increase_rating}</a>
</div>
{else}
@ -94,10 +94,20 @@
</a>
{/if}
{if $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
<a href="javascript:toggleBanInSupport()" class="profile_link">
{if $user->isBannedInSupport()}
{_unban_in_support_user_action}
{else}
{_ban_in_support_user_action}
{/if}
</a>
{/if}
<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>
<a n:if="$user->getPrivacyPermission('messages.write', $thisUser)" href="/im?sel={$user->getId()}" class="profile_link">{_"send_message"}</a>
{var subStatus = $user->getSubscriptionStatus($thisUser)}
{var $subStatus = $user->getSubscriptionStatus($thisUser)}
{if $subStatus === 0}
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" />
@ -131,7 +141,7 @@
<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()}
{var $completeness = $user->getProfileCompletenessReport()}
<div n:class="completeness-gauge, $completeness->total >= 100 ? completeness-gauge-gold">
<div style="width: {$completeness->percent}%"></div>
@ -164,7 +174,7 @@
</div>
<br />
<div n:if="$user->getFriendsCount() > 0 && $user->getPrivacyPermission('friends.read', $thisUser ?? NULL)">
{var friendCount = $user->getFriendsCount()}
{var $friendCount = $user->getFriendsCount()}
<div class="content_title_expanded" onclick="hidePanel(this, {$friendCount});">
{_"friends"}
@ -180,7 +190,7 @@
<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()}" />
<img class="ava" src="{$friend->getAvatarUrl('miniscule')}" />
</a>
</div>
<a href="{$friend->getURL()}" class="cl_name">
@ -205,10 +215,10 @@
<div style="padding: 5px;">
<div class="ovk-album" style="display: inline-block;" n:foreach="$albums as $album">
<div style="text-align: center;float: left;height: 54pt;width: 100px;">
{var cover = $album->getCoverPhoto()}
{var $cover = $album->getCoverPhoto()}
<img
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURL()}"
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURLBySizeId('small')}"
style="max-width: 80px; max-height: 54pt;" />
</div>
<div style="overflow: hidden; overflow-wrap: break-word;">
@ -274,7 +284,7 @@
</div>
</div>
<div n:if="$user->getClubCount() > 0 && $user->getPrivacyPermission('groups.read', $thisUser ?? NULL)">
{var clubsCount = $user->getClubCount()}
{var $clubsCount = $user->getClubCount()}
<div class="content_title_expanded" onclick="hidePanel(this, {$clubsCount})">
{_"groups"}
</div>
@ -293,7 +303,7 @@
</div>
</div>
<div n:if="$user->getMeetingCount() > 0 && $user->getPrivacyPermission('groups.read', $thisUser ?? NULL)">
{var meetingCount = $user->getMeetingCount()}
{var $meetingCount = $user->getMeetingCount()}
<div class="content_title_expanded" onclick="hidePanel(this, {$meetingCount})">
{_meetings}
</div>
@ -317,7 +327,7 @@
<div class="right_big_block">
<div class="page_info">
<div n:if="!is_null($alert = $user->getAlert())" class="user-alert">{strpos($alert, "@") === 0 ? tr(substr($alert, 1)) : $alert}</div>
{var thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
{var $thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
<div n:if="$thatIsThisUser" class="page_status_popup" id="status_editor" style="display: none;">
<form name="status_popup_form" onsubmit="changeStatus(); return false;">
<div style="margin-bottom: 10px;">
@ -485,7 +495,7 @@
</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}
{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;"
@ -512,7 +522,7 @@
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/ban/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось забанить пользователя...", ["OK"], [Function.noop]);
else
MessageBox("Операция успешна", "Пользователь заблокирован", ["OK"], [Function.noop]);
@ -526,7 +536,7 @@
function warnUser() {
uBanMsgTxt = "Вы собираетесь предупредить пользователя " + {$user->getCanonicalName()} + ".";
uBanMsgTxt += "<br/>Мы отправим уведомление пользователю в личные сообщения от имени аккаунта администратора.";
uBanMsgTxt += "<br/><br/><b>Текст предупреждения</b>: <input type='text' id='uWarnMsgInput' placeholder='придумайте что-нибудь крутое' />"
uBanMsgTxt += "<br/><br/><b>Текст предупреждения</b>: <input type='text' id='uWarnMsgInput' placeholder='придумайте что-нибудь крутое' />";
MessageBox("Выдать предупреждение " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
(function() {
@ -546,6 +556,51 @@
}
</script>
<script n:if="isset($thisUser) && $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)">
{if $user->isBannedInSupport()}
function toggleBanInSupport() {
uBanMsgTxt = "Вы собираетесь разблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
uBanMsgTxt += "<br/>Сейчас он заблокирован по причине <strong>" + {$user->getBanInSupportReason()} + "</strong>.";
MessageBox("Разблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
(function() {
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/support/unban/" + {$user->getId()} + "?hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось разблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
else
MessageBox("Операция успешна", "Пользователь разблокирован в поддержке", ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
{else}
function toggleBanInSupport() {
uBanMsgTxt = "Вы собираетесь заблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
uBanMsgTxt += "<br/><br/><b>Причина бана</b>: <input type='text' id='uBanMsgInput' placeholder='придумайте что-нибудь крутое' />";
MessageBox("Заблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
(function() {
res = document.querySelector("#uBanMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/support/ban/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось заблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
else
MessageBox("Операция успешна", "Пользователь заблокирован в поддержке", ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
{/if}
</script>
<script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()" n:syntax="off">
function setStatusEditorShown(shown) {
document.getElementById("status_editor").style.display = shown ? "block" : "none";

View file

@ -4,4 +4,86 @@
{tr("user_banned", htmlentities($user->getFirstName()))|noescape}<br/>
{_"user_banned_comment"} <b>{$user->getBanReason()}</b>.
</p>
{if isset($thisUser)}
<p n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL) || $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)">
<br />
<a n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)" href="javascript:unbanUser()" class="button">{_unban_user_action}</a>
<a n:if="$thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)" href="javascript:toggleBanInSupport()" class="button">
{if $user->isBannedInSupport()}
{_unban_in_support_user_action}
{else}
{_ban_in_support_user_action}
{/if}
</a>
</p>
{/if}
</center>
{if isset($thisUser)}
<script n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)">
function unbanUser() {
uUnbanMsgTxt = "Вы собираетесь разбанить пользователя " + {$user->getCanonicalName()} + ".";
uUnbanMsgTxt += "<br/>Сейчас он заблокирован по причине: <strong>" + {$user->getBanReason()} + "</strong>.";
MessageBox("Разбанить " + {$user->getFirstName()}, uUnbanMsgTxt, ["Подтвердить", "Отмена"], [
(function() {
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/unban/" + {$user->getId()} + "?hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось разблокировать пользователя...", ["OK"], [Function.noop]);
else
MessageBox("Операция успешна", "Пользователь разблокирован", ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
<script n:if="$thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)">
{if $user->isBannedInSupport()}
function toggleBanInSupport() {
uBanMsgTxt = "Вы собираетесь разблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
uBanMsgTxt += "<br/>Сейчас он заблокирован по причине <strong>" + {$user->getBanInSupportReason()} + "</strong>.";
MessageBox("Разблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
(function() {
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/support/unban/" + {$user->getId()} + "?hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось разблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
else
MessageBox("Операция успешна", "Пользователь разблокирован в поддержке", ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
{else}
function toggleBanInSupport() {
uBanMsgTxt = "Вы собираетесь заблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
uBanMsgTxt += "<br/><br/><b>Причина бана</b>: <input type='text' id='uBanMsgInput' placeholder='придумайте что-нибудь крутое' />";
MessageBox("Заблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
(function() {
res = document.querySelector("#uBanMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/admin/support/ban/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("success") === -1)
MessageBox("Ошибка", "Не удалось заблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
else
MessageBox("Операция успешна", "Пользователь заблокирован в поддержке", ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
{/if}
</script>
{/if}

View file

@ -0,0 +1,14 @@
{extends "../@layout.xml"}
{block title}{_information}{/block}
{block header}
{_information}
{/block}
{block content}
<center style="background: white;border: #DEDEDE solid 1px;">
<span style="color: #707070;margin: 60px 0;display: block;">
{_profile_not_found_text}
</span>
</center>
{/block}

View file

@ -1,7 +1,7 @@
{extends "../@listView.xml"}
{var iterator = $videos}
{var count = $paginatorConf->count}
{var page = $paginatorConf->page}
{var $iterator = $videos}
{var $count = $paginatorConf->count}
{var $page = $paginatorConf->page}
{block title}{_"videos"} {$user->getCanonicalName()}{/block}
@ -11,13 +11,13 @@
{/block}
{block size}
<div style="padding-bottom: 0px;border-bottom: 0; padding-top: 0;" class="summaryBar">
<div style="padding-bottom: 0px; padding-top: 0;" class="summaryBar">
<div class="summary">
{tr("videos", $count)}
<span n:if="isset($thisUser) && $thisUser->getId() == $user->getId()">
&nbsp;|&nbsp;
<a href="/videos/upload">{_upload_video}</a>
</span>
{tr("videos", $count)}
<span n:if="isset($thisUser) && $thisUser->getId() == $user->getId()">
&nbsp;|&nbsp;
<a href="/videos/upload">{_upload_video}</a>
</span>
</div>
</div>
{/block}
@ -33,9 +33,11 @@
{/block}
{block preview}
<img src="{$x->getThumbnailURL()}"
alt="{$x->getName()}"
style="max-height: 43pt; max-width: 140px; height: unset; width: unset; border-radius: unset;" />
<div class="video-preview">
<img src="{$x->getThumbnailURL()}"
alt="{$x->getName()}"
style="max-width: 170px; max-height: 127px; margin: auto;" />
</div>
{/block}
{block name}
@ -43,7 +45,13 @@
{/block}
{block description}
<span>{$x->getDescription() ?? ""}</span><br/>
<p>
<span>{$x->getDescription() ?? ""}</span>
</p>
<span style="color: grey;">{_"video_uploaded"} {$x->getPublicationTime()}</span><br/>
<span style="color: grey;">{_"video_updated"} {$x->getEditTime() ?? $x->getPublicationTime()}</span>
<p>
<a href="/video{$x->getPrettyId()}">{_view_video}</a>
{if $x->getCommentsCount() > 0}| <a href="/video{$x->getPrettyId()}#comments">{_"comments"} ({$x->getCommentsCount()})</a>{/if}
</p>
{/block}

View file

@ -15,7 +15,7 @@
{if $video->getType() === 0}
<video width="610" src="{$video->getURL()}" controls></video>
{else}
{var driver = $video->getVideoDriver()}
{var $driver = $video->getVideoDriver()}
{if !$driver}
Эта видеозапись не поддерживается в вашей версии OpenVK.
{else}

View file

@ -27,10 +27,9 @@
<div style="float: left; min-height: 100px; width: 32%;">
<h4>{_actions}</h4>
{if isset($thisUser)}
{var canDelete = $post->canBeDeletedBy($thisUser)}
{var $canDelete = $post->canBeDeletedBy($thisUser)}
{/if}
<a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a>
<a class="profile_link" style="display:block;width:96%;" href="/report.pl/{$post->getId()}?type=post">{_report}</a>
</div>
{/block}

Some files were not shown because too many files have changed in this diff Show more