Merge branch 'master' into feature-reports

This commit is contained in:
Ilya Prokopenko 2022-04-09 14:08:33 +07:00 committed by GitHub
commit f86963dedd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 2756 additions and 312 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>

45
.idea/openvk.iml Normal file
View file

@ -0,0 +1,45 @@
<?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" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

47
.idea/php.xml Normal file
View file

@ -0,0 +1,47 @@
<?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" />
</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,13 +6,13 @@ _[Русский](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://github.com/openvk/archive/actions/workflows/nightly.yml)
## Instances
@ -24,7 +24,7 @@ Updating the source code is done with this command: `git pull`
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,7 +32,7 @@ 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:
@ -48,26 +48,25 @@ ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/ext
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
4. 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**
5. Import `install/init-event-db.sql` to a **separate database** (Yandex.Clickhouse can also be used, higly recommended)
6. Copy `openvk-example.yml` to `openvk.yml` and change options to your liking
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`
8. Run `composer install` in commitcaptcha directory
9. Move to `Web/static/js` and execute `yarn install`
10. 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 +79,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

@ -10,9 +10,9 @@ _[English](README.md)_
## Когда релиз?
Пожалуйста, используйте ветку master, так как в ней больше всего изменений.
Обновление исходного кода выполняется с помощью этой команды: `git pull`.
Мы выпустим OpenVK, как только он будет готов. На данный момент Вы можете:
* Сделать `git clone` master ветки этой репозитории (используйте `git pull` для обновления)
* Взять готовую сборку OpenVK из [GitHub Actions](https://github.com/openvk/archive/actions/workflows/nightly.yml)
## Инстанции
@ -32,7 +32,7 @@ _[English](README.md)_
1. Установите PHP 7.4, веб-сервер, Composer, Node.js, Yarn и [Chandler](https://github.com/openvk/chandler)
* PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать.
* PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать (обновление: он не работает).
2. Установите [commitcaptcha](https://github.com/openvk/commitcaptcha) и OpenVK в качестве расширений Chandler следующим образом:
@ -49,11 +49,12 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
```
4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных**
5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных**
5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных** (Яндекс.Clickhouse также может быть использован, настоятельно рекомендуется)
6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры
7. Запустите `composer install` в директории OpenVK
8. Перейдите в `Web/static/js` и выполните `yarn install`
9. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
8. Запустите `composer install` в директории commitcaptcha
9. Перейдите в `Web/static/js` и выполните `yarn install`
10. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
После этого вы можете войти как системный администратор в саму сеть (регистрация не требуется):
@ -61,13 +62,11 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* **Пароль**: `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 +79,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 [собака] tutanota [точка] com**.
**Внимание**: баг-трекер, форум, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собака] tutanota [точка] com**.
<a href="https://codeberg.org/OpenVK/openvk">
<img alt="Получить на 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) [

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;

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;
@ -71,10 +73,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 +160,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,32 @@ 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([
"height" => 500, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так
"url" => $attachment->getURL(),
"type" => "m",
"width" => 500,
]),
"text" => "",
"has_tags" => false
]
];
}
}
$items[] = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
@ -35,6 +61,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 +83,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)
@ -127,6 +156,31 @@ final class Wall extends VKAPIRequestHandler
$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" => 500, // я ещё я заебался вставлять одинаковый код в два разных места
"url" => $attachment->getURL(),
"type" => "m",
"width" => 500,
]),
"text" => "",
"has_tags" => false
]
];
}
}
$items[] = (object)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
@ -141,6 +195,7 @@ final class Wall extends VKAPIRequestHandler
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"post_source" => (object)["type" => "vk"],
"attachments" => $attachments,
"comments" => (object)[
"count" => $post->getCommentsCount(),
"can_post" => 1
@ -161,6 +216,8 @@ final class Wall extends VKAPIRequestHandler
$profiles[] = $from_id;
else
$groups[] = $from_id * -1;
$attachments = null; // free attachments so it will not clone everythingg
}
}

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;
}

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

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;
@ -8,21 +11,83 @@ class Photo extends Media
{
protected $tableName = "photos";
protected $fileExtension = "jpeg";
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
@ -43,7 +108,128 @@ 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)
$res[$mappings[$id] ?? $id] = $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;
@ -53,10 +239,10 @@ class Photo extends Media
$photo->setCreated(time());
$photo->setFile($file);
$photo->save();
if(!is_null($album))
$album->addPhoto($photo);
return $photo;
}
}

View file

@ -31,11 +31,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 +102,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 +115,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
@ -214,6 +214,11 @@ class User extends RowModel
{
return $this->getRecord()->block_reason;
}
function getBanInSupportReason(): ?string
{
return $this->getRecord()->block_in_support_reason;
}
function getType(): int
{
@ -438,9 +443,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 +453,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 +463,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
@ -673,6 +678,11 @@ class User extends RowModel
{
return !is_null($this->getBanReason());
}
function isBannedInSupport(): bool
{
return !is_null($this->getBanInSupportReason());
}
function isOnline(): bool
{

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

@ -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

@ -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

@ -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();
@ -273,6 +277,8 @@ abstract class OpenVKPresenter extends SimplePresenter
}
}
header("X-OpenVK-User-Validated: $userValidated");
header("X-Accel-Expires: $cacheTime");
setlocale(LC_TIME, ...(explode(";", tr("__locale"))));
parent::onStartup();

View file

@ -276,6 +276,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

@ -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"));
}

View file

@ -77,6 +77,92 @@ final class VKAPIPresenter extends OpenVKPresenter
exit; # Terminate request processing as this is definitely a CORS preflight request.
}
}
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
{

View file

@ -103,7 +103,7 @@
<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,7 +122,7 @@
</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}
@ -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,8 +161,8 @@
</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}
@ -173,7 +173,7 @@
{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="Админ-панель [Alt+Shift+A]" accesskey="a">Админ-панель</a>
<a href="/support/tickets" class="link" n:if="$canAccessHelpdesk">Helpdesk
{if $helpdeskTicketNotAnsweredCount > 0}
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
@ -293,6 +293,37 @@
{/if}
<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}

View file

@ -38,7 +38,7 @@
</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>
<img src="{$club->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
</span>
</span>
</div>
@ -155,7 +155,7 @@
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$follower->getAvatarUrl()}" alt="{$follower->getCanonicalName()}" role="presentation" />
<img src="{$follower->getAvatarUrl('miniscule')}" alt="{$follower->getCanonicalName()}" role="presentation" />
</span>
</span>
@ -188,4 +188,4 @@
</div>
</div>
{/if}
{/block}
{/block}

View file

@ -32,7 +32,7 @@
<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>
@ -43,7 +43,7 @@
<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>

View file

@ -17,7 +17,7 @@
</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>
<img src="{$user->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
</span>
</span>
</div>

View file

@ -32,7 +32,7 @@
<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>

View file

@ -74,7 +74,7 @@
<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>

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

@ -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}
@ -140,4 +140,4 @@
</a>
{/if}
{/if}
{/block}
{/block}

View file

@ -57,7 +57,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">
@ -94,7 +94,7 @@
{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)}

View file

@ -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

@ -25,7 +25,7 @@
{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">
@ -36,7 +36,7 @@
{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

@ -68,7 +68,7 @@
<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

@ -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

@ -23,9 +23,11 @@
{else}
{tr("albums", $count)}
{/if}
<span n:if="isset($thisUser) && $thisUser->getId() == $owner->getId()">
<span n:if="$canEdit" style="float: right;">
&nbsp;|&nbsp;
<a href="/albums/create">{_create_album}</a>
{var isClub = ($owner instanceof \openvk\Web\Models\Entities\Club)}
<a href="/albums/create{$isClub ? '?gpid=' . $owner->getId() : ''}">{_create_album}</a>
</span>
</div>
</div>
@ -43,7 +45,7 @@
{block preview}
{var cover = $x->getCoverPhoto()}
{var preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURL()}
{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">

View file

@ -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

@ -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">

View file

@ -85,7 +85,7 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" />
{/block}
{block name}
@ -145,4 +145,4 @@
</form>
{/if}
{/if}
{/block}
{/block}

View file

@ -48,7 +48,7 @@
{/block}
{block preview}
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография группы" />
{/block}
{block name}{/block}

View file

@ -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>

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}
@ -93,6 +93,16 @@
{_warn_user_action}
</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>
@ -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">
@ -208,7 +218,7 @@
{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;">
@ -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,82 @@
{tr("user_banned", htmlentities($user->getFirstName()))|noescape}<br/>
{_"user_banned_comment"} <b>{$user->getBanReason()}</b>.
</p>
<p n:if="isset($thisUser) && $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>
</center>
<script n:if="isset($thisUser) && $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="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>

View file

@ -2,11 +2,11 @@
{if !$attachment->isDeleted()}
{var link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
<a href="{$link}">
<img class="media" src="{$attachment->getURL()}" alt="{$attachment->getDescription()}" />
<img class="media" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" />
</a>
{else}
<a href="javascript:alert('{_"attach_no_longer_available"}');">
<img class="media" src="/assets/packages/static/openvk/img/camera_200.png" alt="{_"attach_no_longer_available"}" />
<a href="javascript:alert('{_attach_no_longer_available}');">
<img class="media" src="/assets/packages/static/openvk/img/camera_200.png" alt="{_attach_no_longer_available}" />
</a>
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
@ -14,12 +14,12 @@
{elseif $attachment instanceof \openvk\Web\Models\Entities\Post}
{php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1}
{if $GLOBALS["_nesAttGloCou"] > 2}
<a href="/wall{$attachment->getPrettyId()}">{_"open_post"}</a>
<a href="/wall{$attachment->getPrettyId()}">{_open_post}</a>
{else}
{include "post.xml", post => $attachment, compact => true}
{/if}
{else}
<span style="color:red;">{_"version_incompatibility"}</span>
<span style="color:red;">{_version_incompatibility}</span>
{/if}
{php $GLOBALS["_nesAttGloCou"] = NULL}

View file

@ -8,10 +8,7 @@
<tr>
<td width="30" valign="top">
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="30"
class="cCompactAvatars" />
<img src="{$author->getAvatarURL('miniscule')}" width="30" class="cCompactAvatars" />
</a>
</td>
<td width="100%" valign="top">
@ -19,7 +16,7 @@
<a href="{$author->getURL()}"><b>
{$author->getCanonicalName()}
</b></a>
{if $author->isVerified()}<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">{/if}<br/>
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png"><br/>
</div>
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">
@ -34,9 +31,9 @@
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
<a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}</a>&nbsp;|
{if $comment->canBeDeletedBy($thisUser)}
<a href="/comment{$comment->getId()}/delete">{_"delete"}</a>&nbsp;|
<a href="/comment{$comment->getId()}/delete">{_delete}</a>&nbsp;|
{/if}
<a class="comment-reply">{_"reply"}</a>
<a class="comment-reply">{_reply}</a>
<div style="float: right; font-size: .7rem;">
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
<div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div>
@ -45,7 +42,6 @@
</div>
</div>
</div>
</td>
</tr>
</tbody>

View file

@ -1,4 +1,4 @@
<h4 n:if="$showTitle ?? true">{_"comments"} ({$count})</h4>
<h4 n:if="$showTitle ?? true">{_comments} ({$count})</h4>
<div n:ifset="$thisUser">
{var commentsURL = "/al_comments/create/$model/" . $parent->getId()}
@ -27,7 +27,7 @@
{else}
<p>Будьте первым кто оставит комментарий к этой дичи!</p>
{/if} -->
{_"comments_tip"}
{_comments_tip}
{/if}
{script "js/al_comments.js"}

View file

@ -3,16 +3,10 @@
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" style="padding: 8px;">
<div n:class="paginator, ($conf->atBottom ?? false) ? paginator-at-bottom">
{if $conf->page > $space}
<a n:attr="class => ($conf->page === 1 ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => 1]), 'k', '&', PHP_QUERY_RFC3986)}">«</a>
{/if}
<a n:if="$conf->page > $space" n:attr="class => ($conf->page === 1 ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => 1]), 'k', '&', PHP_QUERY_RFC3986)}">&laquo;</a>
{for $j = $conf->page - ($space-1); $j <= $conf->page + ($space-1); $j++}
{if $j > 0 && $j <= $pageCount}
<a n:attr="class => ($conf->page === $j ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $j]), 'k', '&', PHP_QUERY_RFC3986)}">{$j}</a>
{/if}
<a n:if="$j > 0 && $j <= $pageCount" n:attr="class => ($conf->page === $j ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $j]), 'k', '&', PHP_QUERY_RFC3986)}">{$j}</a>
{/for}
{if $conf->page <= $pageCount-$space}
<a n:attr="class => ($conf->page === $pageCount ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $pageCount]), 'k', '&', PHP_QUERY_RFC3986)}">»</a>
{/if}
<a n:if="$conf->page <= $pageCount-$space" n:attr="class => ($conf->page === $pageCount ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $pageCount]), 'k', '&', PHP_QUERY_RFC3986)}">&raquo;</a>
</div>
</div>

View file

@ -1,9 +1,4 @@
{var microblogEnabled = isset($thisUser) ? $thisUser->hasMicroblogEnabled() : false}
{if !$post->isPostedOnBehalfOfGroup()}
{var then = date_create("@" . $post->getOwner()->getOnline()->timestamp())}
{var now = date_create()}
{var diff = date_diff($now, $then)}
{/if}
{if $microblogEnabled}
{include "post/microblogpost.xml", post => $post, diff => $diff, commentSection => $commentSection}

View file

@ -9,25 +9,14 @@
<tr>
<td width="54" valign="top">
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}"
{ifset $compact}class="cCompactAvatars"{/ifset} />
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
<span n:if="$author->isOnline()" class="post-online">
{_online}
</span>
{/if}
<img src="{$author->getAvatarURL('miniscule')}" width="{ifset $compact}25{else}50{/ifset}" {ifset $compact}class="cCompactAvatars"{/ifset} />
<span n:if="!$post->isPostedOnBehalfOfGroup() && !$compact && $author->isOnline()" class="post-online">{_online}</span>
</a>
</td>
<td width="100%" valign="top">
<div class="post-author">
<a href="{$author->getURL()}">
<b>
{$author->getCanonicalName()}
</b>
</a>
{if $author->isVerified()}<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">{/if}
<a href="{$author->getURL()}"><b>{$author->getCanonicalName()}</b></a>
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">
{if ($onWallOf ?? false) &&!$post->isPostedOnBehalfOfGroup() && $post->getOwnerPost() !== $post->getTargetWall()}
{var wallId = $post->getTargetWall()}
{var wallURL = $wallId > -1 ? "/id$wallId" : "/club" . abs($wallId)}
@ -44,18 +33,17 @@
</b>
</a>
{/if}
{ifset $compact}<br>
<a href="/wall{$post->getPrettyId()}" class="date">
{$post->getPublicationTime()}
</a>
{ifset $compact}
<br>
<a href="/wall{$post->getPrettyId()}" class="date">
{$post->getPublicationTime()}
</a>
{/ifset}
{if $post->isPinned()}
<span class="nobold">{_pinned}</span>
{/if}
{if $post->canBeDeletedBy($thisUser) && !($forceNoDeleteLink ?? false) && !isset($compact)}
<a class="delete" href="/wall{$post->getPrettyId()}/delete"></a>
{/if}
<span n:if="$post->isPinned()" class="nobold">{_pinned}</span>
<a n:if="$post->canBeDeletedBy($thisUser) && !($forceNoDeleteLink ?? false) && !isset($compact)" class="delete" href="/wall{$post->getPrettyId()}/delete"></a>
{if $post->canBePinnedBy($thisUser) && !($forceNoPinLink ?? false) && !isset($compact)}
{if $post->isPinned()}
@ -80,11 +68,11 @@
&nbsp;! Этот пост был размещён за взятку.
</div>
<div n:if="$post->isSigned()" class="post-signature">
{var acutalAuthor = $post->getOwner(false)}
{var actualAuthor = $post->getOwner(false)}
<span>
Автор:
<a href="{$acutalAuthor->getURL()}">
{$acutalAuthor->getCanonicalName()}
{_author}:
<a href="{$actualAuthor->getURL()}">
{$actualAuthor->getCanonicalName()}
</a>
</span>
</div>
@ -94,26 +82,17 @@
{if isset($thisUser)}
&nbsp;
{if !($forceNoCommentsLink ?? false)}
<a n:if="$commentsCount == 0" href="javascript:expand_comment_textarea({$commentTextAreaId})">
{_"comment"}
</a>
{/if}
<a n:if="!($forceNoCommentsLink ?? false) && $commentsCount == 0" href="javascript:expand_comment_textarea({$commentTextAreaId})">{_comment}</a>
<div class="like_wrap">
{if !($forceNoShareLink ?? false)}
<a class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
<div class="repost-icon" style="opacity: 0.4;"></div>
<span class="likeCnt">{if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if}</span>
</a>
{/if}
<a n:if="!($forceNoShareLink ?? false)" class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
<div class="repost-icon" style="opacity: 0.4;"></div>
<span class="likeCnt">{if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if}</span>
</a>
{if !($forceNoLike ?? false)}
{var liked = $post->hasLikeFrom($thisUser)}
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}"
class="post-like-button"
data-liked="{(int) $liked}"
data-likes="{$post->getLikesCount()}">
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$post->getLikesCount()}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
</a>
@ -121,21 +100,17 @@
</div>
{/if}
</div>
{if !($forceNoCommentsLink ?? false)}
<div n:if="$commentSection == true && $compact == false" class="post-menu-s">
{if $commentsCount > 3}
<a href="/wall{$post->getPrettyId()}" class="expand_button">{_view_other_comments}</a>
{/if}
{foreach $comments as $comment}
{include "../comment.xml", comment => $comment, $compact => true}
{/foreach}
<div n:ifset="$thisUser" id="commentTextArea{$commentTextAreaId}" n:attr="style => ($commentsCount == 0 ? 'display: none;')" class="commentsTextFieldWrap">
{var commentsURL = "/al_comments/create/posts/" . $post->getId()}
{var club = is_null($club) ? ($post->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($post->getTargetWall())) : NULL) : $club}
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post, club => $club}
</div>
<div n:if="!($forceNoCommentsLink ?? false) && $commentSection == true && $compact == false" class="post-menu-s">
<a n:if="$commentsCount > 3" href="/wall{$post->getPrettyId()}" class="expand_button">{_view_other_comments}</a>
{foreach $comments as $comment}
{include "../comment.xml", comment => $comment, $compact => true}
{/foreach}
<div n:ifset="$thisUser" id="commentTextArea{$commentTextAreaId}" n:attr="style => ($commentsCount == 0 ? 'display: none;')" class="commentsTextFieldWrap">
{var commentsURL = "/al_comments/create/posts/" . $post->getId()}
{var club = is_null($club) ? ($post->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($post->getTargetWall())) : NULL) : $club}
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post, club => $club}
</div>
{/if}
</div>
</td>
</tr>
</tbody>

View file

@ -5,26 +5,15 @@
<tr>
<td width="54" valign="top">
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="50" />
<img src="{$author->getAvatarURL('miniscule')}" width="50" />
<span n:if="!$post->isPostedOnBehalfOfGroup() && !($compact ?? false) && $author->isOnline()" class="post-online">{_online}</span>
</a>
{if !$post->isPostedOnBehalfOfGroup() && !($compact ?? false)}
<span n:if="$author->isOnline()" class="post-online">
{_online}
</span>
{/if}
</td>
<td width="100%" valign="top">
<div class="post-author">
<a href="{$author->getURL()}">
<b>
{$author->getCanonicalName()}
</b>
</a>
{if $author->isVerified()}<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">{/if}
{$post->isPostedOnBehalfOfGroup() ? "опубликовали" : ($author->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}
<a href="{$author->getURL()}"><b>{$author->getCanonicalName()}</b></a>
<img n:if="$author->isVerified()" class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">
{$post->isPostedOnBehalfOfGroup() ? tr("post_writes_g") : ($author->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}
{if ($onWallOf ?? false) &&!$post->isPostedOnBehalfOfGroup() && $post->getOwnerPost() !== $post->getTargetWall()}
{var wallId = $post->getTargetWall()}
{var wallURL = $wallId > -1 ? "/id$wallId" : "/club" . abs($wallId)}
@ -43,10 +32,7 @@
{/if}
<br/>
<a href="/wall{$post->getPrettyId()}" class="date">
{$post->getPublicationTime()}
{if $post->isPinned()}
, {_pinned}
{/if}
{$post->getPublicationTime()}{if $post->isPinned()}, {_pinned}{/if}
</a>
</div>
<div class="post-content" id="{$post->getPrettyId()}">
@ -64,68 +50,54 @@
&nbsp;! Этот пост был размещён за взятку.
</div>
<div n:if="$post->isSigned()" class="post-signature">
{var acutalAuthor = $post->getOwner(false)}
{var actualAuthor = $post->getOwner(false)}
<span>
Автор:
<a href="{$acutalAuthor->getURL()}">
{$acutalAuthor->getCanonicalName()}
{_author}:
<a href="{$actualAuthor->getURL()}">
{$actualAuthor->getCanonicalName()}
</a>
</span>
</div>
</div>
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
{if $post->canBeDeletedBy($thisUser) && !($forceNoDeleteLink ?? false)}
<a href="/wall{$post->getPrettyId()}/delete">{_"delete"}</a>&nbsp;|&nbsp;
<a href="/wall{$post->getPrettyId()}/delete">{_delete}</a> &nbsp;|&nbsp;
{/if}
{if $post->canBePinnedBy($thisUser) && !($forceNoPinLink ?? false)}
{if $post->isPinned()}
<a href="/wall{$post->getPrettyId()}/pin?act=unpin&hash={rawurlencode($csrfToken)}">{_unpin}</a>&nbsp;|&nbsp;
<a href="/wall{$post->getPrettyId()}/pin?act=unpin&hash={rawurlencode($csrfToken)}">{_unpin}</a>
{else}
<a href="/wall{$post->getPrettyId()}/pin?act=pin&hash={rawurlencode($csrfToken)}">{_pin}</a>&nbsp;|&nbsp;
<a href="/wall{$post->getPrettyId()}/pin?act=pin&hash={rawurlencode($csrfToken)}">{_pin}</a>
{/if}
&nbsp;|&nbsp;
{/if}
{if !($forceNoCommentsLink ?? false)}
<a href="/wall{$post->getPrettyId()}#comments">
{if $post->getCommentsCount() > 0}
{_"comments"} (<b>{$post->getCommentsCount()}</b>)
{else}
{_"comments"}
{/if}
</a>
{/if}
<a n:if="!($forceNoCommentsLink ?? false)" href="/wall{$post->getPrettyId()}#comments">
{_comments}
{if $post->getCommentsCount() > 0}
(<b>{$post->getCommentsCount()}</b>)
{/if}
</a>
{if !($forceNoCommentsLink ?? false) && !($forceNoShareLink ?? false)}
&nbsp;|&nbsp;
{/if}
{if !($forceNoShareLink ?? false)}
<a class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
{if $post->getRepostCount() > 0}
{_"share"}
(<b>{$post->getRepostCount()}</b>)
{else}
{_"share"}
{/if}
</a>
{/if}
<a n:if="!($forceNoShareLink ?? false)" class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
{_share}
{if $post->getRepostCount() > 0}
(<b>{$post->getRepostCount()}</b>)
{/if}
</a>
{if !($forceNoLike ?? false)}
<div class="like_wrap">
{var liked = $post->hasLikeFrom($thisUser)}
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}"
class="post-like-button"
data-liked="{(int) $liked}"
data-likes="{$post->getLikesCount()}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
</a>
</div>
{/if}
</div>
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu-s">
<!-- kosfurler -->
<div n:if="!($forceNoLike ?? false)" class="like_wrap">
{var liked = $post->hasLikeFrom($thisUser)}
<a href="/wall{$post->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$post->getLikesCount()}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
</a>
</div>
</div>
</td>
</tr>

View file

@ -27,25 +27,25 @@
</script>
<label>
<input type="checkbox" name="as_group" onchange="onWallAsGroupClick(this)" /> {_"post_as_group"}
<input type="checkbox" name="as_group" onchange="onWallAsGroupClick(this)" /> {_post_as_group}
</label>
<label id="forceSignOpt" style="display: none;">
<input type="checkbox" name="force_sign" /> {_"add_signature"}
<input type="checkbox" name="force_sign" /> {_add_signature}
</label>
{/if}
{/if}
<label n:if="$anonEnabled" id="octoberAnonOpt">
<input type="checkbox" name="anon" /> {_"as_anonymous"}
<input type="checkbox" name="anon" /> {_as_anonymous}
</label>
<label>
<input type="checkbox" name="nsfw" /> {_"contains_nsfw"}
<input type="checkbox" name="nsfw" /> {_contains_nsfw}
</label>
</div>
<div n:if="!($postOpts ?? true) && !is_null($thisUser) && !is_null($club ?? NULL) && $club->canBeModifiedBy($thisUser)" class="post-opts">
<label>
<input type="checkbox" name="as_group" /> {_"comment_as_group"}
<input type="checkbox" name="as_group" /> {_comment_as_group}
</label>
</div>
<input type="file" class="postFileSel" id="postFilePic" name="_pic_attachment" accept="image/*" style="display:none;" />
@ -53,7 +53,7 @@
<input type="hidden" name="type" value="1" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<br/>
<input type="submit" value="{_'write'}" class="button" />
<input type="submit" value="{_write}" class="button" />
<div style="float: right; display: flex; flex-direction: column;">
<a href="javascript:void(u('#post-buttons{$textAreaId} #wallAttachmentMenu').toggleClass('hidden'));">
{_attach}

View file

@ -1,9 +1,9 @@
<div class="content_divider">
<div class="content_title_expanded" onclick="hidePanel(this);">
{_"wall"}
{_wall}
<nobold>
{tr("wall", $count)}
<a href="/wall{$owner}" class="float-right lowercase">{_all_title}</a>
{tr("wall", $count)}
<a href="/wall{$owner}" class="float-right lowercase">{_all_title}</a>
</nobold>
</div>
<div>

View file

@ -271,16 +271,24 @@ routes:
handler: "Admin->gifts"
- url: "/admin/ban/{num}"
handler: "Admin->quickBan"
- url: "/admin/unban/{num}"
handler: "Admin->quickUnban"
- url: "/admin/warn/{num}"
handler: "Admin->quickWarn"
- url: "/support/reports"
- url: "/admin/support/ban/{num}"
handler: "Support->quickBanInSupport"
- url: "/admin/support/unban/{num}"
handler: "Support->quickUnbanInSupport"
- url: "/admin/support/reports"
handler: "Report->list"
- url: "/support/report{num}"
- url: "/admin/support/report{num}"
handler: "Report->view"
- url: "/support/reportAction{num}"
- url: "/admin/support/reportAction{num}"
handler: "Report->action"
- url: "/report/{num}"
handler: "Report->create"
- url: "/upload/photo/{text}"
handler: "VKAPI->photoUpload"
- url: "/method/{text}.{text}"
handler: "VKAPI->route"
- url: "/token"

View file

@ -360,7 +360,7 @@ p {
}
.page_footer {
margin-left: 95px;
margin-left: 130px;
padding-top: 5px;
clear: both;
text-align: center;
@ -1334,6 +1334,15 @@ body.scrolled .toTop:hover {
font-size: 15px;
}
#test-label {
padding: 8pt;
margin: 4pt;
border: 1px solid #9a205e;
background-color: #f5e9ec;
position: fixed;
font-weight: bold;
}
.post-upload {
margin-top: 11px;
margin-left: 3px;
@ -1912,6 +1921,7 @@ table td[width="120"] {
width: 50px;
display: inline-block;
vertical-align: top;
box-sizing: border-box;
}
.mb_tabs {

View file

@ -14,6 +14,7 @@
"react-dom-factories": "^1.0.2",
"requirejs": "^2.3.6",
"soundjs": "^1.0.1",
"textfit": "^2.4.0",
"umbrellajs": "^3.1.0"
}
}

View file

@ -271,6 +271,11 @@ soundjs@^1.0.1:
resolved "https://registry.yarnpkg.com/soundjs/-/soundjs-1.0.1.tgz#99970542d28d0df2a1ebd061ae75c961a98c8180"
integrity sha512-MgFPvmKYfpcNiE3X5XybNvScie3DMQlZgmNzUn4puBcpw64f4LqjH/fhM8Sb/eTJ8hK57Crr7mWy0bfJOqPj6Q==
textfit@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/textfit/-/textfit-2.4.0.tgz#80cba8006bfb9c3d9d552739257957bdda95c79c"
integrity sha512-/x4aoY5+/tJmu+iwpBH1yw75TFp86M6X15SvaaY/Eep7YySQYtqdOifEtfvVyMwzl7SZ+G4RQw00FD9g5R6i1Q==
trim-extra-html-whitespace@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/trim-extra-html-whitespace/-/trim-extra-html-whitespace-1.3.0.tgz#b47efb0d1a5f2a56a85cc45cea525651e93404cf"

View file

@ -14,7 +14,9 @@
"chillerlan/php-qrcode": "dev-main",
"vearutop/php-obscene-censor-rus": "dev-master",
"erusev/parsedown": "dev-master",
"bhaktaraz/php-rss-generator": "dev-master"
"bhaktaraz/php-rss-generator": "dev-master",
"ext-simplexml": "*",
"symfony/console": "5.4.x-dev"
},
"minimum-stability": "dev"
}

804
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2c94032cae911ca438bbcfc46c346961",
"content-hash": "878bd996183ccbb15637a7399ba03ab9",
"packages": [
{
"name": "al/emoji-detector",
@ -852,6 +852,54 @@
},
"time": "2016-08-06T20:24:11+00:00"
},
{
"name": "psr/container",
"version": "1.x-dev",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
"reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/1.1.2"
},
"time": "2021-11-05T16:50:12+00:00"
},
{
"name": "psr/http-message",
"version": "dev-master",
@ -1085,6 +1133,336 @@
},
"time": "2022-01-06T19:41:32+00:00"
},
{
"name": "symfony/console",
"version": "5.4.x-dev",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "900275254f0a1a2afff1ab0e11abd5587a10e1d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/900275254f0a1a2afff1ab0e11abd5587a10e1d6",
"reference": "900275254f0a1a2afff1ab0e11abd5587a10e1d6",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.9",
"symfony/polyfill-php80": "^1.16",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/string": "^5.1|^6.0"
},
"conflict": {
"psr/log": ">=3",
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
"symfony/lock": "<4.4",
"symfony/process": "<4.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0"
},
"require-dev": {
"psr/log": "^1|^2",
"symfony/config": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
"symfony/event-dispatcher": "^4.4|^5.0|^6.0",
"symfony/lock": "^4.4|^5.0|^6.0",
"symfony/process": "^4.4|^5.0|^6.0",
"symfony/var-dumper": "^4.4|^5.0|^6.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.4.7"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-03-31T17:09:19+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "2.5.x-dev",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
"reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/2.5"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-01-02T09:53:40+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-10-20T20:35:02+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783",
"reference": "81b86b50cf841a64252b439e738e97f4a34e2783",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-11-23T21:10:46+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
"version": "v1.24.0",
@ -1256,6 +1634,89 @@
],
"time": "2021-02-19T12:13:01+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-11-30T18:21:41+00:00"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.24.0",
@ -1332,6 +1793,338 @@
],
"time": "2021-05-27T09:17:38+00:00"
},
{
"name": "symfony/polyfill-php73",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5",
"reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php73\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2021-06-05T21:20:04+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.25.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-03-04T08:16:47+00:00"
},
{
"name": "symfony/service-contracts",
"version": "2.5.x-dev",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c",
"reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.1",
"symfony/deprecation-contracts": "^2.1|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"suggest": {
"symfony/service-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/2.5"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-03-13T20:07:29+00:00"
},
{
"name": "symfony/string",
"version": "5.4.x-dev",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "92043b7d8383e48104e411bc9434b260dbeb5a10"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/92043b7d8383e48104e411bc9434b260dbeb5a10",
"reference": "92043b7d8383e48104e411bc9434b260dbeb5a10",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "~1.15"
},
"conflict": {
"symfony/translation-contracts": ">=3.0"
},
"require-dev": {
"symfony/error-handler": "^4.4|^5.0|^6.0",
"symfony/http-client": "^4.4|^5.0|^6.0",
"symfony/translation-contracts": "^1.1|^2",
"symfony/var-exporter": "^4.4|^5.0|^6.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.4.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-01-02T09:53:40+00:00"
},
{
"name": "vearutop/php-obscene-censor-rus",
"version": "dev-master",
@ -1562,11 +2355,14 @@
"chillerlan/php-qrcode": 20,
"vearutop/php-obscene-censor-rus": 20,
"erusev/parsedown": 20,
"bhaktaraz/php-rss-generator": 20
"bhaktaraz/php-rss-generator": 20,
"symfony/console": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform": {
"ext-simplexml": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

13
data/photosizes.xml Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<PhotoSizes name="standard">
<Size id="miniscule" vkId="s" maxSize="75" requireProp="none" />
<Size id="tiny" vkId="m" maxSize="130" requireProp="none" />
<Size id="tinier" vkId="o" maxSize="130" requireProp="3:2" />
<Size id="xsmall" vkId="p" maxSize="200" requireProp="3:2" />
<Size id="small" vkId="q" maxSize="320" requireProp="3:2" />
<Size id="medium" vkId="r" maxSize="510" requireProp="3:2" />
<Size id="normal" vkId="x" maxSize="604" requireProp="none" />
<Size id="large" vkId="y" maxSize="807" requireProp="none" />
<Size id="larger" vkId="z" maxResolution="1080x1024" requireProp="none" />
<Size id="original" vkId="w" maxResolution="2560x2048" requireProp="none" />
</PhotoSizes>

View file

@ -0,0 +1 @@
ALTER TABLE `profiles` ADD COLUMN `block_in_support_reason` text COLLATE utf8mb4_unicode_520_ci DEFAULT NULL AFTER `block_reason`;

View file

@ -0,0 +1,3 @@
ALTER TABLE `photos` ROW_FORMAT=COMPRESSED;
ALTER TABLE `photos` ADD COLUMN `sizes` VARBINARY(486) NULL DEFAULT NULL AFTER `hash`;
ALTER TABLE `photos` ADD COLUMN `width` SMALLINT UNSIGNED NULL DEFAULT NULL AFTER `sizes`, ADD COLUMN `height` SMALLINT UNSIGNED NULL DEFAULT NULL AFTER `sizes`;

View file

@ -693,6 +693,9 @@
"ticket_changed" = "Ticket changed";
"ticket_changed_comment" = "The changes will take effect in a few seconds.";
"banned_in_support_1" = "Sorry, <b>$1</b>, but now you can't create tickets.";
"banned_in_support_2" = "And the reason for this is simple: <b>$1</b>. Unfortunately, this time we had to take away this opportunity from you forever.";
/* Invite */
"invite" = "Invite";
@ -836,7 +839,10 @@
"manage_user_action" = "Manage user";
"manage_group_action" = "Manage group";
"ban_user_action" = "Ban user";
"unban_user_action" = "Unban user";
"warn_user_action" = "Warn user";
"ban_in_support_user_action" = "Ban in support";
"unban_in_support_user_action" = "Unban in support";
/* Paginator (deprecated) */

View file

@ -398,7 +398,7 @@
"default" = "әдепкі";
"arbitrary_avatars" = "Ерікті"
"arbitrary_avatars" = "Ерікті";
"cut" = "Шаршы";
"round_avatars" = "Дөңгелек";

View file

@ -730,6 +730,9 @@
"ticket_changed" = "Тикет изменён";
"ticket_changed_comment" = "Изменения вступят силу через несколько секунд.";
"banned_in_support_1" = "Извините, <b>$1</b>, но теперь вам нельзя создавать обращения.";
"banned_in_support_2" = "А причина этому проста: <b>$1</b>. К сожалению, на этот раз нам пришлось отобрать у вас эту возможность навсегда.";
/* Invite */
"invite" = "Пригласить";
@ -879,7 +882,10 @@
"manage_user_action" = "Управление пользователем";
"manage_group_action" = "Управление группой";
"ban_user_action" = "Заблокировать пользователя";
"unban_user_action" = "Разблокировать пользователя";
"warn_user_action" = "Предупредить пользователя";
"ban_in_support_user_action" = "Заблокировать в поддержке";
"unban_in_support_user_action" = "Разблокировать в поддержке";
/* Paginator (deprecated) */

View file

@ -255,6 +255,7 @@
"hidden_yes" = "Скрыт: Да";
"hidden_no" = "Скрыт: Нет";
"group_allow_post_for_everyone" = "Разрешить публиковать записи всем";
"group_hide_from_global_feed" = "Не отображать записи в публичной доске";
"statistics" = "Статистика";
"group_administrators_list" = "Список админов";
"group_display_only_creator" = "Отображать только создателя группы";
@ -430,7 +431,7 @@
"ui_settings_rating_hide" = "Скрывать";
"additional_links" = "Дополнительные ссылки";
"ad_poster" = "Рекламный плакат";
/* Two-factor authentication */
"two_factor_authentication" = "Двухфакторная аутентификация";
@ -609,6 +610,21 @@
/* Support */
"increase_rating" = "Увеличить рейтинг";
"increase_rating_button" = "Увеличить";
"to_whom" = "Кому";
"increase_by" = "Увеличить на";
"price" = "Ценник";
"you_have_unused_votes" = "У Вас $1 неиспользованных рубля на кошельке.";
"apply_voucher" = "Применить купон";
"failed_to_increase_rating" = "Не удалось увеличить рейтинг";
"rating_increase_successful" = "Вы успешно увеличили рейтинг <b><a href=\"$1\">$2</a></b> на <b>$3%</b>.";
"negative_rating_value" = "Орган не может украсть баллы рейтинга у другого гражданина, так не положено.";
"increased_your_rating_by" = "увеличил ваш рейтинг на";
"support_opened" = "Открытые";
"support_answered" = "С ответом";
"support_closed" = "Закрытые";
@ -759,7 +775,7 @@
"token_manipulation_error" = "Ошибка манипулирования токеном";
"token_manipulation_error_comment" = "Токен недействителен или истёк";
"profile_changed" = "Досье изменёно";
"profile_changed" = "Досье изменено";
"profile_changed_comment" = "Товарищ, орган одобрил изменение вашего досье.";
"profile_not_found" = "Гражданин не найден.";
@ -795,6 +811,36 @@
/* About */
"about_openvk" = "Об органе OpenVK";
"footer_about_instance" = "о стране";
"about_this_instance" = "Об этой стране";
"rules" = "Правила";
"most_popular_groups" = "Самые популярные клубы";
"on_this_instance_are" = "На этой стране";
"about_users_one" = "<b>1</b> гражданин";
"about_users_few" = "<b>$1</b> гражданина";
"about_users_many" = "<b>$1</b> гражданинов";
"about_users_other" = "<b>$1</b> гражданинов";
"about_online_users_one" = "<b>1</b> пользователь в сети";
"about_online_users_few" = "<b>$1</b> пользователя в сети";
"about_online_users_many" = "<b>$1</b> гражданинов в сети";
"about_online_users_other" = "<b>$1</b> гражданинов в сети";
"about_active_users_one" = "<b>1</b> активный пользователь";
"about_active_users_few" = "<b>$1</b> активных пользователя";
"about_active_users_many" = "<b>$1</b> активных гражданинов";
"about_active_users_other" = "<b>$1</b> активных гражданинов";
"about_groups_one" = "<b>1</b> клуб";
"about_groups_few" = "<b>$1</b> клубы";
"about_groups_many" = "<b>$1</b> клубов";
"about_groups_other" = "<b>$1</b> клубов";
"about_wall_posts_one" = "<b>1</b> заметка на досках";
"about_wall_posts_few" = "<b>$1</b> заметки на досках";
"about_wall_posts_many" = "<b>$1</b> заметки на досках";
"about_wall_posts_other" = "<b>$1</b> заметки на досках";
/* Dialogs */
@ -810,5 +856,20 @@
"question_confirm" = "Товарищ, будьте внимательны с выбором. Вы согласны с вашим выбором? Отменить не представляется возможным.";
/* User alerts */
"apply_style_for_this_device" = "Применить стиль только для этой ЭВМ";
"user_alert_scam" = "Органу управления было дозволено, что данный гражданин обманывает товарищей на денежные средства. Будьте осторожны при разговоре с ним.";
"ec_header" = "Подтверждение регистрации прописки";
"ec_title" = "Спасибо!";
"ec_1" = "<b>$1</b>, на ваш почтовый ящик должно придти письмо с подтверждением регистрации.";
"ec_2" = "Если по каким-то причинам вам не пришло письмо, то проверьте мусорный бак. Если письма не окажется и там, то вы можете переотправить письмо.";
"ec_resend" = "Переотправить письмо";
"email_sent" = "Письмо было успешно отправлено.";
"email_sent_desc" = "Если ваш почтовый ящик существует, вы получите письмо.";
"email_error" = "Непредвиденная ошибка при отправке письма.";
"email_rate_limit_error" = "Нельзя делать это так часто, извините.";
"email_verify_success" = "Ваша регистрация была подтверждена. Приятного времяпрепровождения!";

View file

@ -6,13 +6,19 @@ openvk:
preferences:
femaleGenderPriority: true
nginxCacheTime: null
uploads:
disableLargeUploads: false
mode: "basic"
api:
maxFilesPerDomain: 10
maxFileSize: 25000000
shortcodes:
minLength: 3 # won't affect existing short urls or the ones set via admin panel
forbiddenNames:
- "index.php"
photos:
upgradeStructure: true
security:
requireEmail: false
requirePhone: false
@ -66,6 +72,15 @@ openvk:
enable: false
domain: ""
server: ""
piwik:
enable: false
container: ""
site: ""
layer: "dataLayer"
matomo:
enable: false
container: ""
site: ""
credentials:
smsc:

13
openvkctl Executable file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
namespace openvk;
use Symfony\Component\Console\Application;
$_SERVER["HTTP_ACCEPT_LANGUAGE"] = false;
$bootstrap = require(__DIR__ . "/../../../chandler/Bootstrap.php");
$bootstrap->ignite(true);
$application = new Application();
$application->add(new CLI\RebuildImagesCommand);
$application->run();

View file

View file