mirror of
https://github.com/openvk/openvk
synced 2024-12-22 16:42:32 +03:00
Merge branch 'master' into textarea-refactor
This commit is contained in:
commit
9f699336f7
175 changed files with 5882 additions and 1523 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
8
.idea/.gitignore
vendored
Normal 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
|
46
.idea/intellij-latte/xmlSources/Latte.dtd
Normal file
46
.idea/intellij-latte/xmlSources/Latte.dtd
Normal 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>
|
290
.idea/intellij-latte/xmlSources/Latte.xml
Normal file
290
.idea/intellij-latte/xmlSources/Latte.xml
Normal 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>
|
59
.idea/intellij-latte/xmlSources/NetteApplication.xml
Normal file
59
.idea/intellij-latte/xmlSources/NetteApplication.xml
Normal 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>
|
41
.idea/intellij-latte/xmlSources/NetteForms.xml
Normal file
41
.idea/intellij-latte/xmlSources/NetteForms.xml
Normal 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
6
.idea/misc.xml
Normal 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
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/openvk.iml" filepath="$PROJECT_DIR$/.idea/openvk.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
46
.idea/openvk.iml
Normal file
46
.idea/openvk.iml
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/rybakit/msgpack" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/chillerlan/php-qrcode" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/chillerlan/php-settings-container" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/vearutop/php-obscene-censor-rus" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-normalizer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/scssphp/scssphp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/bhaktaraz/php-rss-generator" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/erusev/parsedown" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ezyang/htmlpurifier" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/whichbrowser/parser" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/komeiji-satori/curl" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/james-heinrich/getid3" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/wapmorgan/binary-stream" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/al/emoji-detector" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/lfkeitel/phptotp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/zadarma/user-api-v1" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/wapmorgan/morphos" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
48
.idea/php.xml
Normal file
48
.idea/php.xml
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/vendor/rybakit/msgpack" />
|
||||
<path value="$PROJECT_DIR$/vendor/chillerlan/php-qrcode" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/cache" />
|
||||
<path value="$PROJECT_DIR$/vendor/chillerlan/php-settings-container" />
|
||||
<path value="$PROJECT_DIR$/vendor/vearutop/php-obscene-censor-rus" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php72" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-idn" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-normalizer" />
|
||||
<path value="$PROJECT_DIR$/vendor/scssphp/scssphp" />
|
||||
<path value="$PROJECT_DIR$/vendor/bhaktaraz/php-rss-generator" />
|
||||
<path value="$PROJECT_DIR$/vendor/erusev/parsedown" />
|
||||
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
|
||||
<path value="$PROJECT_DIR$/vendor/whichbrowser/parser" />
|
||||
<path value="$PROJECT_DIR$/vendor/komeiji-satori/curl" />
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/vendor/james-heinrich/getid3" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
|
||||
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
|
||||
<path value="$PROJECT_DIR$/vendor/wapmorgan/binary-stream" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<path value="$PROJECT_DIR$/vendor/al/emoji-detector" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
|
||||
<path value="$PROJECT_DIR$/vendor/lfkeitel/phptotp" />
|
||||
<path value="$PROJECT_DIR$/vendor/zadarma/user-api-v1" />
|
||||
<path value="$PROJECT_DIR$/../../../chandler" />
|
||||
<path value="$PROJECT_DIR$/../../../vendor" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/string" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php73" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/container" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/console" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/wapmorgan/morphos" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
|
||||
<option name="suggestChangeDefaultLanguageLevel" value="false" />
|
||||
</component>
|
||||
</project>
|
10
.idea/runConfigurations.xml
Normal file
10
.idea/runConfigurations.xml
Normal 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
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
86
CLI/RebuildImagesCommand.php
Normal file
86
CLI/RebuildImagesCommand.php
Normal 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;
|
||||
}
|
||||
}
|
51
README.md
51
README.md
|
@ -6,25 +6,27 @@ _[Русский](README_RU.md)_
|
|||
|
||||
VKontakte belongs to Pavel Durov and VK Group.
|
||||
|
||||
To be honest, we don't even know whether it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug-tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OVK account for this).
|
||||
To be honest, we don't know whether it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug-tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OVK account for this).
|
||||
|
||||
## When's the release?
|
||||
|
||||
Please use the master branch, as it has the most changes.
|
||||
|
||||
Updating the source code is done with this command: `git pull`
|
||||
We will release OpenVK as soon as it's ready. As for now you can:
|
||||
* `git clone` this repo's master branch (use `git pull` to update)
|
||||
* Grab a prebuilt OpenVK distro from [GitHub artifacts](https://nightly.link/openvk/archive/workflows/nightly/master/OpenVK%20Archive.zip)
|
||||
|
||||
## Instances
|
||||
|
||||
* **[openvk.su](https://openvk.su/)**
|
||||
* **[openvk.uk](https://openvk.uk)** - official mirror of openvk.su (<https://t.me/openvkch/1609>)
|
||||
* **[openvk.co](http://openvk.co)** - yet another official mirror of openvk.su without TLS (<https://t.me/openvkch/1654>)
|
||||
* [social.fetbuk.ru](http://social.fetbuk.ru/)
|
||||
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
|
||||
* [vepurovk.xyz](http://vepurovk.xyz/)
|
||||
|
||||
## Can I create my own OpenVK instance?
|
||||
|
||||
Yes! And you're very welcome to.
|
||||
|
||||
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. this extensions are available on most of ISPManager hostings).
|
||||
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. these extensions are available on most of ISPManager hostings).
|
||||
|
||||
If you want, you can add your instance to the list above so that people can register there.
|
||||
|
||||
|
@ -32,42 +34,47 @@ If you want, you can add your instance to the list above so that people can regi
|
|||
|
||||
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
|
||||
|
||||
* PHP 8 has **not** yet been tested, so you should not expect it to work.
|
||||
* PHP 8 has **not** yet been tested, so you should not expect it to work. (edit: it does not work).
|
||||
|
||||
2. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
|
||||
2. Install MySQL-compatible database.
|
||||
|
||||
* We recommend using Percona Server, but any MySQL-compatible server should work
|
||||
* Server should be compatible with at least MySQL 5.6, MySQL 8.0+ recommended.
|
||||
* Support for MySQL 4.1+ is WIP, replace `utf8mb4` and `utf8mb4_unicode_520_ci` with `utf8` and `utf8_unicode_ci` in SQLs.
|
||||
|
||||
3. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk
|
||||
git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha
|
||||
```
|
||||
|
||||
3. And enable them:
|
||||
4. And enable them:
|
||||
|
||||
```bash
|
||||
ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/
|
||||
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
|
||||
```
|
||||
|
||||
4. Import `install/init-static-db.sql` to **same database** you installed Chandler to and import all sqls from `install/sqls` to **same database**
|
||||
5. Import `install/init-event-db.sql` to **separate database**
|
||||
6. Copy `openvk-example.yml` to `openvk.yml` and change options
|
||||
7. Run `composer install` in OpenVK directory
|
||||
8. Move to `Web/static/js` and execute `yarn install`
|
||||
9. Set `openvk` as your root app in `chandler.yml`
|
||||
5. Import `install/init-static-db.sql` to the **same database** you installed Chandler to and import all sqls from `install/sqls` to the **same database**
|
||||
6. Import `install/init-event-db.sql` to a **separate database** (Yandex.Clickhouse can also be used, highly recommended)
|
||||
7. Copy `openvk-example.yml` to `openvk.yml` and change options to your liking
|
||||
8. Run `composer install` in OpenVK directory
|
||||
9. Run `composer install` in commitcaptcha directory
|
||||
10. Move to `Web/static/js` and execute `yarn install`
|
||||
11. Set `openvk` as your root app in `chandler.yml`
|
||||
|
||||
Once you are done, you can login as a system administrator on the network itself (no registration required):
|
||||
|
||||
* **Login**: `admin@localhost.localdomain6`
|
||||
* **Password**: `admin`
|
||||
* It is recommended to change the password before using the built-in account.
|
||||
* It is recommended to change the password of the built-in account or disable it.
|
||||
|
||||
Full example installation instruction for CentOS 8 is also available [here](https://docs.openvk.su/openvk_engine/centos8_installation/).
|
||||
💡Confused? Full installation walkthrough is available [here](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)).
|
||||
|
||||
### If my website uses OpenVK, should I publish it's sources?
|
||||
### If my website uses OpenVK, should I release it's sources?
|
||||
|
||||
You are encouraged to do so. We don't enforce this though. You can keep your sources to yourself (unless you distribute your OpenVK distro to other people).
|
||||
|
||||
You also not required to publish source texts of your themepacks and plugins.
|
||||
It depends. You can keep the sources to yourself if you do not plan to distribute your website binaries. If your website software must be distributed, it can stay non-OSS provided the OpenVK is not used as a primary application and is not modified. If you modified OpenVK for your needs or your work is based on it and you're planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc).
|
||||
|
||||
## Where can I get assistance?
|
||||
|
||||
|
@ -80,7 +87,7 @@ You may reach out to us via:
|
|||
* [Discussions](https://github.com/openvk/openvk/discussions)
|
||||
* Matrix chat: #openvk:matrix.org
|
||||
|
||||
**Attention**: bug tracker, telegram and matrix chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
|
||||
**Attention**: bug tracker, board, telegram and matrix chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
|
||||
|
||||
<a href="https://codeberg.org/OpenVK/openvk">
|
||||
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">
|
||||
|
|
49
README_RU.md
49
README_RU.md
|
@ -2,23 +2,25 @@
|
|||
|
||||
_[English](README.md)_
|
||||
|
||||
**OpenVK** это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. Представленный здесь код пока не стабилен.
|
||||
**OpenVK** - это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент представленный здесь исходный код проекта пока не является стабильным.
|
||||
|
||||
ВКонтакте принадлежит Павлу Дурову и VK Group.
|
||||
|
||||
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OVK).
|
||||
|
||||
## Когда релиз?
|
||||
## Когда выйдет релизная версия?
|
||||
|
||||
Пожалуйста, используйте ветку master, так как в ней больше всего изменений.
|
||||
|
||||
Обновление исходного кода выполняется с помощью этой команды: `git pull`.
|
||||
Мы выпустим OpenVK, как только он будет готов. На данный момент Вы можете:
|
||||
* Склонировать master ветку репозитория командой `git clone` (используйте `git pull` для обновления)
|
||||
* Взять готовую сборку OpenVK из [GitHub Actions](https://nightly.link/openvk/archive/workflows/nightly/master/OpenVK%20Archive.zip)
|
||||
|
||||
## Инстанции
|
||||
|
||||
* **[openvk.su](https://openvk.su/)**
|
||||
* **[openvk.uk](https://openvk.uk)** - официальное зеркало openvk.su (<https://t.me/openvkch/1609>)
|
||||
* **[openvk.co](http://openvk.co)** - ещё одно официальное зеркало openvk.su без TLS (<https://t.me/openvkch/1654>)
|
||||
* [social.fetbuk.ru](http://social.fetbuk.ru/)
|
||||
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
|
||||
* [vepurovk.xyz](http://vepurovk.xyz/)
|
||||
|
||||
## Могу ли я создать свою собственную инстанцию OpenVK?
|
||||
|
||||
|
@ -32,42 +34,47 @@ _[English](README.md)_
|
|||
|
||||
1. Установите PHP 7.4, веб-сервер, Composer, Node.js, Yarn и [Chandler](https://github.com/openvk/chandler)
|
||||
|
||||
* PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать.
|
||||
* PHP 8 еще **не** тестировался, поэтому не стоит ожидать, что он будет работать (UPD: он не работает).
|
||||
|
||||
2. Установите [commitcaptcha](https://github.com/openvk/commitcaptcha) и OpenVK в качестве расширений Chandler следующим образом:
|
||||
2. Установите MySQL-совместимую базу данных.
|
||||
|
||||
* Мы рекомендуем использовать Persona Server, но любая MySQL-совместимая база данных должна работать
|
||||
* Сервер должен поддерживать хотя бы MySQL 5.6, рекомендуется использовать MySQL 8.0+.
|
||||
* Поддержка для MySQL 4.1+ находится в процессе, а пока замените `utf8mb4` и `utf8mb4_unicode_520_ci` на `utf8` и `utf8_unicode_ci` в SQL-файлах, соответственно.
|
||||
|
||||
3. Установите [commitcaptcha](https://github.com/openvk/commitcaptcha) и OpenVK в качестве расширений Chandler:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk
|
||||
git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha
|
||||
```
|
||||
|
||||
3. И включите их:
|
||||
4. И включите их:
|
||||
|
||||
```bash
|
||||
ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/
|
||||
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
|
||||
```
|
||||
|
||||
4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных**
|
||||
5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных**
|
||||
6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры
|
||||
7. Запустите `composer install` в директории OpenVK
|
||||
8. Перейдите в `Web/static/js` и выполните `yarn install`
|
||||
9. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
|
||||
5. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных**
|
||||
6. Импортируйте `install/init-event-db.sql` в **отдельную базу данных** (Яндекс.Clickhouse также может быть использован, настоятельно рекомендуется)
|
||||
7. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры под свои нужды
|
||||
8. Запустите `composer install` в директории OpenVK
|
||||
9. Запустите `composer install` в директории commitcaptcha
|
||||
10. Перейдите в `Web/static/js` и выполните `yarn install`
|
||||
11. Установите `openvk` в качестве корневого приложения в файле `chandler.yml`
|
||||
|
||||
После этого вы можете войти как системный администратор в саму сеть (регистрация не требуется):
|
||||
|
||||
* **Логин**: `admin@localhost.localdomain6`
|
||||
* **Пароль**: `admin`
|
||||
* Перед использованием встроенной учетной записи рекомендуется сменить пароль.
|
||||
* Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её.
|
||||
|
||||
Полный пример инструкции по установке CentOS 8 также доступен [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/).
|
||||
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
|
||||
|
||||
### Если мой сайт использует OpenVK, должен ли я публиковать его исходные тексты?
|
||||
|
||||
Вам рекомендуется это делать. Однако мы не следим за этим. Вы можете держать свои исходные тексты при себе (если только вы не распространяете свой дистрибутив OpenVK среди других людей).
|
||||
|
||||
Вы также не обязаны публиковать исходные тексты ваших тематических пакетов и плагинов.
|
||||
Это зависит от обстоятельств. Вы можете оставить исходные тексты при себе, если не планируете распространять бинарники вашего сайта. Если программное обеспечение вашего сайта должно распространяться, оно может оставаться не-OSS при условии, что OpenVK не используется в качестве основного приложения и не модифицируется. Если вы модифицировали OpenVK для своих нужд или ваша работа основана на нем и вы планируете ее распространять, то вы должны лицензировать ее на условиях любой совместимой с LGPL лицензии (например, OSL, GPL, LGPL и т.д.).
|
||||
|
||||
## Где я могу получить помощь?
|
||||
|
||||
|
@ -80,7 +87,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
|
|||
* [Обсуждения](https://github.com/openvk/openvk/discussions)
|
||||
* Чат в Matrix: #ovk:matrix.org
|
||||
|
||||
**Внимание**: баг-трекер, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [at] tutanota [dot] com**.
|
||||
**Внимание**: баг-трекер, форум, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собака] tutanota [точка] com**.
|
||||
|
||||
<a href="https://codeberg.org/OpenVK/openvk">
|
||||
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">
|
||||
|
|
|
@ -60,7 +60,7 @@ final class Account extends VKAPIRequestHandler
|
|||
return 1;
|
||||
}
|
||||
|
||||
function getAppPermissions(): object
|
||||
function getAppPermissions(): int
|
||||
{
|
||||
return 9355263;
|
||||
}
|
||||
|
|
|
@ -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) [
|
||||
|
|
|
@ -88,4 +88,100 @@ final class Groups extends VKAPIRequestHandler
|
|||
"items" => $rClubs
|
||||
];
|
||||
}
|
||||
|
||||
function getById(string $group_ids = "", string $group_id = "", string $fields = ""): ?array
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
$clubs = new ClubsRepo;
|
||||
|
||||
if ($group_ids == null && $group_id != null)
|
||||
$group_ids = $group_id;
|
||||
|
||||
if ($group_ids == null && $group_id == null)
|
||||
$this->fail(100, "One of the parameters specified was missing or invalid: group_ids is undefined");
|
||||
|
||||
$clbs = explode(',', $group_ids);
|
||||
$response;
|
||||
|
||||
$ic = sizeof($clbs);
|
||||
|
||||
for ($i=0; $i < $ic; $i++) {
|
||||
if($i > 500)
|
||||
break;
|
||||
|
||||
if($clbs[$i] < 0)
|
||||
$this->fail(100, "ты ошибся чутка, у айди группы убери минус");
|
||||
|
||||
$clb = $clubs->get((int) $clbs[$i]);
|
||||
if(is_null($clb))
|
||||
{
|
||||
$response[$i] = (object)[
|
||||
"id" => intval($clbs[$i]),
|
||||
"name" => "DELETED",
|
||||
"screen_name" => "club".intval($clbs[$i]),
|
||||
"type" => "group",
|
||||
"description" => "This group was deleted or it doesn't exist"
|
||||
];
|
||||
}else if($clbs[$i] == null){
|
||||
|
||||
}else{
|
||||
$response[$i] = (object)[
|
||||
"id" => $clb->getId(),
|
||||
"name" => $clb->getName(),
|
||||
"screen_name" => $clb->getShortCode() ?? "club".$clb->getId(),
|
||||
"is_closed" => false,
|
||||
"type" => "group",
|
||||
"can_access_closed" => true,
|
||||
];
|
||||
|
||||
$flds = explode(',', $fields);
|
||||
|
||||
foreach($flds as $field) {
|
||||
switch ($field) {
|
||||
case 'verified':
|
||||
$response[$i]->verified = intval($clb->isVerified());
|
||||
break;
|
||||
case 'has_photo':
|
||||
$response[$i]->has_photo = is_null($clb->getAvatarPhoto()) ? 0 : 1;
|
||||
break;
|
||||
case 'photo_max_orig':
|
||||
$response[$i]->photo_max_orig = $clb->getAvatarURL();
|
||||
break;
|
||||
case 'photo_max':
|
||||
$response[$i]->photo_max = $clb->getAvatarURL();
|
||||
break;
|
||||
case 'members_count':
|
||||
$response[$i]->members_count = $clb->getFollowersCount();
|
||||
break;
|
||||
case 'site':
|
||||
$response[$i]->site = $clb->getWebsite();
|
||||
break;
|
||||
case 'description':
|
||||
$response[$i]->desctiption = $clb->getDescription();
|
||||
break;
|
||||
case 'contacts':
|
||||
$contacts;
|
||||
$contactTmp = $clb->getManagers(1, true);
|
||||
foreach($contactTmp as $contact) {
|
||||
$contacts[] = array(
|
||||
'user_id' => $contact->getUser()->getId(),
|
||||
'desc' => $contact->getComment()
|
||||
);
|
||||
}
|
||||
$response[$i]->contacts = $contacts;
|
||||
break;
|
||||
case 'can_post':
|
||||
if($clb->canBeModifiedBy($this->getUser()))
|
||||
$response[$i]->can_post = true;
|
||||
else
|
||||
$response[$i]->can_post = $clb->canPost();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
74
VKAPI/Handlers/Likes.php
Normal file
74
VKAPI/Handlers/Likes.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
42
VKAPI/Handlers/Newsfeed.php
Normal file
42
VKAPI/Handlers/Newsfeed.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Entities\Post;
|
||||
use openvk\Web\Models\Entities\Postable;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use openvk\Web\Models\Repositories\Posts as PostsRepo;
|
||||
use openvk\VKAPI\Handlers\Wall;
|
||||
|
||||
final class Newsfeed extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $fields = "", int $start_from = 0, int $offset = 0, int $count = 30, int $extended = 0)
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
if($offset != 0) $start_from = $offset;
|
||||
|
||||
$id = $this->getUser()->getId();
|
||||
$subs = DatabaseConnection::i()
|
||||
->getContext()
|
||||
->table("subscriptions")
|
||||
->where("follower", $id);
|
||||
$ids = array_map(function($rel) {
|
||||
return $rel->target * ($rel->model === "openvk\Web\Models\Entities\User" ? 1 : -1);
|
||||
}, iterator_to_array($subs));
|
||||
$ids[] = $this->getUser()->getId();
|
||||
|
||||
$posts = DatabaseConnection::i()
|
||||
->getContext()
|
||||
->table("posts")
|
||||
->select("id")
|
||||
->where("wall IN (?)", $ids)
|
||||
->where("deleted", 0)
|
||||
->order("created DESC");
|
||||
|
||||
$rposts = [];
|
||||
foreach($posts->page((int) ($offset + 1), $count) as $post)
|
||||
$rposts[] = (new PostsRepo)->get($post->id)->getPrettyId();
|
||||
|
||||
return (new Wall)->getById(implode(',', $rposts), $extended, $fields, $this->getUser());
|
||||
}
|
||||
}
|
230
VKAPI/Handlers/Photos.php
Normal file
230
VKAPI/Handlers/Photos.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -5,13 +5,15 @@ use openvk\Web\Models\Repositories\Users as UsersRepo;
|
|||
|
||||
final class Users extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $user_ids = "0", string $fields = "", int $offset = 0, int $count = 100): array
|
||||
function get(string $user_ids = "0", string $fields = "", int $offset = 0, int $count = 100, User $authuser = null /* костыль(( */): array
|
||||
{
|
||||
$this->requireUser();
|
||||
// $this->requireUser();
|
||||
|
||||
if($authuser == null) $authuser = $this->getUser();
|
||||
|
||||
$users = new UsersRepo;
|
||||
if($user_ids == "0")
|
||||
$user_ids = (string) $this->getUser()->getId();
|
||||
$user_ids = (string) $authuser->getId();
|
||||
|
||||
$usrs = explode(',', $user_ids);
|
||||
$response;
|
||||
|
@ -51,7 +53,7 @@ final class Users extends VKAPIRequestHandler
|
|||
$response[$i]->verified = intval($usr->isVerified());
|
||||
break;
|
||||
case 'sex':
|
||||
$response[$i]->sex = $this->getUser()->isFemale() ? 1 : 2;
|
||||
$response[$i]->sex = $usr->isFemale() ? 1 : 2;
|
||||
break;
|
||||
case 'has_photo':
|
||||
$response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1;
|
||||
|
@ -60,8 +62,26 @@ final class Users extends VKAPIRequestHandler
|
|||
$response[$i]->photo_max_orig = $usr->getAvatarURL();
|
||||
break;
|
||||
case 'photo_max':
|
||||
$response[$i]->photo_max = $usr->getAvatarURL();
|
||||
$response[$i]->photo_max = $usr->getAvatarURL("original");
|
||||
break;
|
||||
case 'photo_50':
|
||||
$response[$i]->photo_50 = $usr->getAvatarURL();
|
||||
break;
|
||||
case 'photo_100':
|
||||
$response[$i]->photo_50 = $usr->getAvatarURL("tiny");
|
||||
break;
|
||||
case 'photo_200':
|
||||
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
|
||||
break;
|
||||
case 'photo_200_orig': // вообще не ебу к чему эта строка ну пусть будет кек
|
||||
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
|
||||
break;
|
||||
case 'photo_400_orig':
|
||||
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
|
||||
break;
|
||||
|
||||
// Она хочет быть выебанной видя матан
|
||||
// Покайфу когда ты Виет а вокруг лишь дискриминант
|
||||
case 'status':
|
||||
if($usr->getStatus() != null)
|
||||
$response[$i]->status = $usr->getStatus();
|
||||
|
@ -71,10 +91,10 @@ final class Users extends VKAPIRequestHandler
|
|||
$response[$i]->screen_name = $usr->getShortCode();
|
||||
break;
|
||||
case 'friend_status':
|
||||
switch($usr->getSubscriptionStatus($this->getUser())) {
|
||||
switch($usr->getSubscriptionStatus($authuser)) {
|
||||
case 3:
|
||||
case 0:
|
||||
$response[$i]->friend_status = $usr->getSubscriptionStatus($this->getUser());
|
||||
$response[$i]->friend_status = $usr->getSubscriptionStatus($authuser);
|
||||
break;
|
||||
case 1:
|
||||
$response[$i]->friend_status = 2;
|
||||
|
@ -158,13 +178,14 @@ final class Users extends VKAPIRequestHandler
|
|||
$users = new UsersRepo;
|
||||
|
||||
$array = [];
|
||||
$find = $users->find($q);
|
||||
|
||||
foreach ($users->find($q) as $user) {
|
||||
foreach ($find as $user) {
|
||||
$array[] = $user->getId();
|
||||
}
|
||||
|
||||
return (object)[
|
||||
"count" => $users->getFoundCount($q),
|
||||
"count" => $find->size(),
|
||||
"items" => $this->get(implode(',', $array), $fields, $offset, $count)
|
||||
];
|
||||
}
|
||||
|
|
|
@ -22,6 +22,27 @@ final class Wall extends VKAPIRequestHandler
|
|||
|
||||
foreach ($posts->getPostsFromUsersWall((int)$owner_id, 1, $count, $offset) as $post) {
|
||||
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
|
||||
|
||||
$attachments;
|
||||
foreach($post->getChildren() as $attachment)
|
||||
{
|
||||
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
|
||||
{
|
||||
$attachments[] = [
|
||||
"type" => "photo",
|
||||
"photo" => [
|
||||
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null,
|
||||
"date" => $attachment->getPublicationTime()->timestamp(),
|
||||
"id" => $attachment->getVirtualId(),
|
||||
"owner_id" => $attachment->getOwner()->getId(),
|
||||
"sizes" => array_values($attachment->getVkApiSizes()),
|
||||
"text" => "",
|
||||
"has_tags" => false
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = (object)[
|
||||
"id" => $post->getVirtualId(),
|
||||
"from_id" => $from_id,
|
||||
|
@ -35,6 +56,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
"can_archive" => false, // TODO MAYBE
|
||||
"is_archived" => false,
|
||||
"is_pinned" => $post->isPinned(),
|
||||
"attachments" => $attachments,
|
||||
"post_source" => (object)["type" => "vk"],
|
||||
"comments" => (object)[
|
||||
"count" => $post->getCommentsCount(),
|
||||
|
@ -56,6 +78,8 @@ final class Wall extends VKAPIRequestHandler
|
|||
$profiles[] = $from_id;
|
||||
else
|
||||
$groups[] = $from_id * -1;
|
||||
|
||||
$attachments = null; // free attachments so it will not clone everythingg
|
||||
}
|
||||
|
||||
if($extended == 1)
|
||||
|
@ -110,6 +134,169 @@ final class Wall extends VKAPIRequestHandler
|
|||
];
|
||||
}
|
||||
|
||||
function getById(string $posts, int $extended = 0, string $fields = "", User $user = null)
|
||||
{
|
||||
if($user == null) $user = $this->getUser(); // костыли костыли крылышки
|
||||
|
||||
$items = [];
|
||||
$profiles = [];
|
||||
$groups = [];
|
||||
# $count = $posts->getPostCountOnUserWall((int) $owner_id);
|
||||
|
||||
$psts = explode(",", $posts);
|
||||
|
||||
foreach($psts as $pst)
|
||||
{
|
||||
$id = explode("_", $pst);
|
||||
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]));
|
||||
if($post) {
|
||||
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
|
||||
$attachments;
|
||||
foreach($post->getChildren() as $attachment)
|
||||
{
|
||||
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
|
||||
{
|
||||
$attachments[] = [
|
||||
"type" => "photo",
|
||||
"photo" => [
|
||||
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null,
|
||||
"date" => $attachment->getPublicationTime()->timestamp(),
|
||||
"id" => $attachment->getVirtualId(),
|
||||
"owner_id" => $attachment->getOwner()->getId(),
|
||||
"sizes" => array(
|
||||
[
|
||||
"height" => 2560,
|
||||
"url" => $attachment->getURLBySizeId("normal"),
|
||||
"type" => "m",
|
||||
"width" => 2560,
|
||||
],
|
||||
[
|
||||
"height" => 130,
|
||||
"url" => $attachment->getURLBySizeId("tiny"),
|
||||
"type" => "o",
|
||||
"width" => 130,
|
||||
],
|
||||
[
|
||||
"height" => 604,
|
||||
"url" => $attachment->getURLBySizeId("normal"),
|
||||
"type" => "p",
|
||||
"width" => 604,
|
||||
],
|
||||
[
|
||||
"height" => 807,
|
||||
"url" => $attachment->getURLBySizeId("large"),
|
||||
"type" => "q",
|
||||
"width" => 807,
|
||||
],
|
||||
[
|
||||
"height" => 1280,
|
||||
"url" => $attachment->getURLBySizeId("larger"),
|
||||
"type" => "r",
|
||||
"width" => 1280,
|
||||
],
|
||||
[
|
||||
"height" => 75, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так
|
||||
"url" => $attachment->getURLBySizeId("miniscule"),
|
||||
"type" => "s",
|
||||
"width" => 75,
|
||||
]),
|
||||
"text" => "",
|
||||
"has_tags" => false
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = (object)[
|
||||
"id" => $post->getVirtualId(),
|
||||
"from_id" => $from_id,
|
||||
"owner_id" => $post->getTargetWall(),
|
||||
"date" => $post->getPublicationTime()->timestamp(),
|
||||
"post_type" => "post",
|
||||
"text" => $post->getText(),
|
||||
"can_edit" => 0, // TODO
|
||||
"can_delete" => $post->canBeDeletedBy($user),
|
||||
"can_pin" => $post->canBePinnedBy($user),
|
||||
"can_archive" => false, // TODO MAYBE
|
||||
"is_archived" => false,
|
||||
"is_pinned" => $post->isPinned(),
|
||||
"post_source" => (object)["type" => "vk"],
|
||||
"attachments" => $attachments,
|
||||
"comments" => (object)[
|
||||
"count" => $post->getCommentsCount(),
|
||||
"can_post" => 1
|
||||
],
|
||||
"likes" => (object)[
|
||||
"count" => $post->getLikesCount(),
|
||||
"user_likes" => (int) $post->hasLikeFrom($user),
|
||||
"can_like" => 1,
|
||||
"can_publish" => 1,
|
||||
],
|
||||
"reposts" => (object)[
|
||||
"count" => $post->getRepostCount(),
|
||||
"user_reposted" => 0
|
||||
]
|
||||
];
|
||||
|
||||
if ($from_id > 0)
|
||||
$profiles[] = $from_id;
|
||||
else
|
||||
$groups[] = $from_id * -1;
|
||||
|
||||
$attachments = null; // free attachments so it will not clone everythingg
|
||||
}
|
||||
}
|
||||
|
||||
if($extended == 1)
|
||||
{
|
||||
$profiles = array_unique($profiles);
|
||||
$groups = array_unique($groups);
|
||||
|
||||
$profilesFormatted = [];
|
||||
$groupsFormatted = [];
|
||||
|
||||
foreach ($profiles as $prof) {
|
||||
$user = (new UsersRepo)->get($prof);
|
||||
$profilesFormatted[] = (object)[
|
||||
"first_name" => $user->getFirstName(),
|
||||
"id" => $user->getId(),
|
||||
"last_name" => $user->getLastName(),
|
||||
"can_access_closed" => false,
|
||||
"is_closed" => false,
|
||||
"sex" => $user->isFemale() ? 1 : 2,
|
||||
"screen_name" => $user->getShortCode(),
|
||||
"photo_50" => $user->getAvatarUrl(),
|
||||
"photo_100" => $user->getAvatarUrl(),
|
||||
"online" => $user->isOnline()
|
||||
];
|
||||
}
|
||||
|
||||
foreach($groups as $g) {
|
||||
$group = (new ClubsRepo)->get($g);
|
||||
$groupsFormatted[] = (object)[
|
||||
"id" => $group->getId(),
|
||||
"name" => $group->getName(),
|
||||
"screen_name" => $group->getShortCode(),
|
||||
"is_closed" => 0,
|
||||
"type" => "group",
|
||||
"photo_50" => $group->getAvatarUrl(),
|
||||
"photo_100" => $group->getAvatarUrl(),
|
||||
"photo_200" => $group->getAvatarUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
return (object)[
|
||||
"items" => (array)$items,
|
||||
"profiles" => (array)$profilesFormatted,
|
||||
"groups" => (array)$groupsFormatted
|
||||
];
|
||||
}
|
||||
else
|
||||
return (object)[
|
||||
"items" => (array)$items
|
||||
];
|
||||
}
|
||||
|
||||
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object
|
||||
{
|
||||
$this->requireUser();
|
||||
|
|
|
@ -11,10 +11,11 @@ final class Message
|
|||
public $out;
|
||||
public $title = "";
|
||||
public $body;
|
||||
public $text;
|
||||
public $attachments = [];
|
||||
public $fwd_messages = [];
|
||||
public $emoji;
|
||||
public $important = 1;
|
||||
public $important = true;
|
||||
public $deleted = 0;
|
||||
public $random_id = NULL;
|
||||
}
|
||||
|
|
16
Vagrantfile
vendored
16
Vagrantfile
vendored
|
@ -1,16 +1,22 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "freebsd/FreeBSD-12.1-STABLE"
|
||||
config.vm.box = "freebsd/FreeBSD-13.1-RC2"
|
||||
config.vm.box_version = "2022.04.07"
|
||||
|
||||
config.vm.network "forwarded_port", guest: 80, host: 4000
|
||||
|
||||
config.vm.synced_folder ".", "/.ovk_release"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.gui = true
|
||||
vb.memory = "1024"
|
||||
vb.cpus = 4
|
||||
vb.memory = "1568"
|
||||
end
|
||||
|
||||
config.vm.provider "vmware_workstation" do |vwx|
|
||||
vwx.gui = true
|
||||
vwx.vmx["memsize"] = "1568"
|
||||
vwx.vmx["numvcpus"] = "4"
|
||||
end
|
||||
|
||||
config.vm.provision "shell", inline: "/bin/tcsh /.ovk_release/install/automated/freebsd-12/install"
|
||||
config.vm.provision "shell", inline: "/bin/tcsh /.ovk_release/install/automated/freebsd-13/install"
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
@ -346,6 +346,11 @@ class Club extends RowModel
|
|||
{
|
||||
return $this->getRecord()->website;
|
||||
}
|
||||
|
||||
function getAlert(): ?string
|
||||
{
|
||||
return $this->getRecord()->alert;
|
||||
}
|
||||
|
||||
use Traits\TSubscribable;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,9 @@ abstract class Media extends Postable
|
|||
{
|
||||
protected $fileExtension = "oct"; #octet stream xddd
|
||||
protected $upperNodeReferenceColumnName = "owner";
|
||||
|
||||
protected $processingPlaceholder = NULL;
|
||||
protected $processingTime = 30;
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
#Remove data, if model wasn't presisted
|
||||
|
@ -22,6 +24,11 @@ abstract class Media extends Postable
|
|||
else
|
||||
return OPENVK_ROOT . "/storage/";
|
||||
}
|
||||
|
||||
protected function checkIfFileIsProcessed(): bool
|
||||
{
|
||||
throw new \LogicException("checkIfFileIsProcessed is not implemented");
|
||||
}
|
||||
|
||||
abstract protected function saveFile(string $filename, string $hash): bool;
|
||||
|
||||
|
@ -41,6 +48,10 @@ abstract class Media extends Postable
|
|||
|
||||
function getURL(): string
|
||||
{
|
||||
if(!is_null($this->processingPlaceholder))
|
||||
if(!$this->isProcessed())
|
||||
return "/assets/packages/static/openvk/$this->processingPlaceholder.$this->fileExtension";
|
||||
|
||||
$hash = $this->getRecord()->hash;
|
||||
|
||||
switch(OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["mode"]) {
|
||||
|
@ -55,7 +66,7 @@ abstract class Media extends Postable
|
|||
case "server":
|
||||
$settings = (object) OPENVK_ROOT_CONF["openvk"]["preferences"]["uploads"]["server"];
|
||||
return (
|
||||
$settings->protocol .
|
||||
$settings->protocol ?? ovk_scheme() .
|
||||
"://" . $settings->host .
|
||||
$settings->path .
|
||||
substr($hash, 0, 2) . "/$hash.$this->fileExtension"
|
||||
|
@ -68,6 +79,26 @@ abstract class Media extends Postable
|
|||
{
|
||||
return $this->getRecord()->description;
|
||||
}
|
||||
|
||||
protected function isProcessed(): bool
|
||||
{
|
||||
if(is_null($this->processingPlaceholder))
|
||||
return true;
|
||||
|
||||
if($this->getRecord()->processed)
|
||||
return true;
|
||||
|
||||
$timeDiff = time() - $this->getRecord()->last_checked;
|
||||
if($timeDiff < $this->processingTime)
|
||||
return false;
|
||||
|
||||
$res = $this->checkIfFileIsProcessed();
|
||||
$this->stateChanges("last_checked", time());
|
||||
$this->stateChanges("processed", $res);
|
||||
$this->save();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function isDeleted(): bool
|
||||
{
|
||||
|
@ -89,7 +120,17 @@ abstract class Media extends Postable
|
|||
|
||||
$this->stateChanges("hash", $hash);
|
||||
}
|
||||
|
||||
|
||||
function save(): void
|
||||
{
|
||||
if(!is_null($this->processingPlaceholder) && is_null($this->getRecord())) {
|
||||
$this->stateChanges("processed", 0);
|
||||
$this->stateChanges("last_checked", time());
|
||||
}
|
||||
|
||||
parent::save();
|
||||
}
|
||||
|
||||
function delete(bool $softly = true): void
|
||||
{
|
||||
$deleteQuirk = ovkGetQuirk("blobs.erase-upon-deletion");
|
||||
|
|
|
@ -48,6 +48,7 @@ class Note extends Postable
|
|||
"acronym",
|
||||
"blockquote",
|
||||
"cite",
|
||||
"span",
|
||||
]);
|
||||
$config->set("HTML.AllowedAttributes", [
|
||||
"table.summary",
|
||||
|
@ -59,6 +60,8 @@ class Note extends Postable
|
|||
"img.style",
|
||||
"div.style",
|
||||
"div.title",
|
||||
"span.class",
|
||||
"p.class",
|
||||
]);
|
||||
$config->set("CSS.AllowedProperties", [
|
||||
"float",
|
||||
|
@ -68,6 +71,9 @@ class Note extends Postable
|
|||
"max-width",
|
||||
"font-weight",
|
||||
]);
|
||||
$config->set("Attr.AllowedClasses", [
|
||||
"underline",
|
||||
]);
|
||||
|
||||
$purifier = new HTMLPurifier($config);
|
||||
return $purifier->purify($this->getRecord()->source);
|
||||
|
|
|
@ -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,131 @@ class Photo extends Media
|
|||
|
||||
DB::i()->getContext()->table("album_relations")->where("media", $this->getRecord()->id)->delete();
|
||||
}
|
||||
|
||||
|
||||
function getSizes(bool $upgrade = false, bool $forceUpdate = false): ?array
|
||||
{
|
||||
$sizes = $this->getRecord()->sizes;
|
||||
if(!$sizes || $forceUpdate) {
|
||||
if($forceUpdate || $upgrade || OPENVK_ROOT_CONF["openvk"]["preferences"]["photos"]["upgradeStructure"]) {
|
||||
$hash = $this->getRecord()->hash;
|
||||
$this->saveImageResizedCopies($this->pathFromHash($hash), $hash);
|
||||
$this->save();
|
||||
|
||||
return $this->getSizes();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$res = [];
|
||||
$sizes = MessagePack::unpack($sizes);
|
||||
foreach($sizes as $id => $meta) {
|
||||
$url = $this->getURL();
|
||||
$url = str_replace(".$this->fileExtension", "_cropped/$id.", $url);
|
||||
$url .= ($meta[1] <= 300 || $meta[2] <= 300) ? "gif" : "jpeg";
|
||||
|
||||
$res[$id] = (object) [
|
||||
"url" => $url,
|
||||
"width" => $meta[1],
|
||||
"height" => $meta[2],
|
||||
"crop" => $meta[0]
|
||||
];
|
||||
}
|
||||
|
||||
[$x, $y] = $this->getDimensions();
|
||||
$res["UPLOADED_MAXRES"] = (object) [
|
||||
"url" => $this->getURL(),
|
||||
"width" => $x,
|
||||
"height" => $y,
|
||||
"crop" => false
|
||||
];
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function getVkApiSizes(): ?array
|
||||
{
|
||||
$res = [];
|
||||
$sizes = $this->getSizes();
|
||||
if(!$sizes)
|
||||
return NULL;
|
||||
|
||||
$manifest = simplexml_load_file(OPENVK_ROOT . "/data/photosizes.xml");
|
||||
if(!$manifest)
|
||||
return NULL;
|
||||
|
||||
$mappings = [];
|
||||
foreach($manifest->Size as $size)
|
||||
$mappings[(string) $size["id"]] = (string) $size["vkId"];
|
||||
|
||||
foreach($sizes as $id => $meta) {
|
||||
$type = $mappings[$id] ?? $id;
|
||||
$meta->type = $type;
|
||||
$res[$type] = $meta;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function getURLBySizeId(string $size): string
|
||||
{
|
||||
$sizes = $this->getSizes();
|
||||
if(!$sizes)
|
||||
return $this->getURL();
|
||||
|
||||
$size = $sizes[$size];
|
||||
if(!$size)
|
||||
return $this->getURL();
|
||||
|
||||
return $size->url;
|
||||
}
|
||||
|
||||
function getDimensions(): array
|
||||
{
|
||||
$x = $this->getRecord()->width;
|
||||
$y = $this->getRecord()->height;
|
||||
if(!$x) { # no sizes in database
|
||||
$hash = $this->getRecord()->hash;
|
||||
$image = Image::fromFile($this->pathFromHash($hash));
|
||||
|
||||
$x = $image->getWidth();
|
||||
$y = $image->getHeight();
|
||||
$this->stateChanges("width", $x);
|
||||
$this->stateChanges("height", $y);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
return [$x, $y];
|
||||
}
|
||||
|
||||
function getAlbum(): ?Album
|
||||
{
|
||||
return (new Albums)->getAlbumByPhotoId($this);
|
||||
}
|
||||
|
||||
function toVkApiStruct(): object
|
||||
{
|
||||
$res = (object) [];
|
||||
|
||||
$res->id = $res->pid = $this->getId();
|
||||
$res->owner_id = $res->user_id = $this->getOwner()->getId()->getId();
|
||||
$res->aid = $res->album_id = NULL;
|
||||
$res->width = $this->getDimensions()[0];
|
||||
$res->height = $this->getDimensions()[1];
|
||||
$res->date = $res->created = $this->getPublicationTime()->timestamp();
|
||||
|
||||
$res->sizes = $this->getVkApiSizes();
|
||||
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
|
||||
$res->src = $res->photo_130 = $this->getURLBySizeId("tiny");
|
||||
$res->src_big = $res->photo_604 = $this->getURLBySizeId("normal");
|
||||
$res->src_xbig = $res->photo_807 = $this->getURLBySizeId("large");
|
||||
$res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger");
|
||||
$res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original");
|
||||
$res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES");
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
static function fastMake(int $owner, string $description = "", array $file, ?Album $album = NULL, bool $anon = false): Photo
|
||||
{
|
||||
$photo = new static;
|
||||
|
@ -53,10 +242,10 @@ class Photo extends Media
|
|||
$photo->setCreated(time());
|
||||
$photo->setFile($file);
|
||||
$photo->save();
|
||||
|
||||
|
||||
if(!is_null($album))
|
||||
$album->addPhoto($photo);
|
||||
|
||||
|
||||
return $photo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,16 +55,21 @@ trait TRichText
|
|||
{
|
||||
$contentColumn = property_exists($this, "overrideContentColumn") ? $this->overrideContentColumn : "content";
|
||||
|
||||
$text = htmlentities($this->getRecord()->{$contentColumn}, ENT_DISALLOWED | ENT_XHTML);
|
||||
$text = htmlspecialchars($this->getRecord()->{$contentColumn}, ENT_DISALLOWED | ENT_XHTML);
|
||||
$proc = iconv_strlen($this->getRecord()->{$contentColumn}) <= OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["processingLimit"];
|
||||
if($html) {
|
||||
if($proc) {
|
||||
$rel = $this->isAd() ? "sponsored" : "ugc";
|
||||
$text = $this->formatLinks($text);
|
||||
$text = preg_replace("%@([A-Za-z0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1|$2]", $text);
|
||||
$text = preg_replace("%@([A-Za-z0-9]++) \(((?:[\p{L&}\p{Lo} 0-9]\p{Mn}?)++)\)%Xu", "[$1|$2]", $text);
|
||||
$text = preg_replace("%([\n\r\s]|^)(@([A-Za-z0-9]++))%Xu", "$1[$3|@$3]", $text);
|
||||
$text = preg_replace("%\[([A-Za-z0-9]++)\|([\p{L} 0-9@]+)\]%Xu", "<a href='/$1'>$2</a>", $text);
|
||||
$text = preg_replace("%([\n\r\s]|^)(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "$1<a href='/feed/hashtag/$3'>$2</a>", $text);
|
||||
$text = preg_replace("%\[([A-Za-z0-9]++)\|((?:[\p{L&}\p{Lo} 0-9@]\p{Mn}?)++)\]%Xu", "<a href='/$1'>$2</a>", $text);
|
||||
$text = preg_replace_callback("%([\n\r\s]|^)(\#([\p{L}_0-9][\p{L}_0-9\(\)\-\']+[\p{L}_0-9\(\)]|[\p{L}_0-9]{1,2}))%Xu", function($m) {
|
||||
$slug = rawurlencode($m[3]);
|
||||
|
||||
return "$m[1]<a href='/feed/hashtag/$slug'>$m[2]</a>";
|
||||
}, $text);
|
||||
|
||||
$text = $this->formatEmojis($text);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use morphos\Gender;
|
||||
use openvk\Web\Themes\{Themepack, Themepacks};
|
||||
use openvk\Web\Util\DateTime;
|
||||
use openvk\Web\Models\RowModel;
|
||||
|
@ -9,6 +10,7 @@ use openvk\Web\Models\Exceptions\InvalidUserNameException;
|
|||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use Chandler\Security\User as ChandlerUser;
|
||||
use function morphos\Russian\inflectName;
|
||||
|
||||
class User extends RowModel
|
||||
{
|
||||
|
@ -31,11 +33,11 @@ class User extends RowModel
|
|||
const NSFW_TOLERANT = 1;
|
||||
const NSFW_FULL_TOLERANT = 2;
|
||||
|
||||
protected function _abstractRelationGenerator(string $filename, int $page = 1): \Traversable
|
||||
protected function _abstractRelationGenerator(string $filename, int $page = 1, int $limit = 6): \Traversable
|
||||
{
|
||||
$id = $this->getId();
|
||||
$query = "SELECT id FROM\n" . file_get_contents(__DIR__ . "/../sql/$filename.tsql");
|
||||
$query .= "\n LIMIT 6 OFFSET " . ( ($page - 1) * 6 );
|
||||
$query .= "\n LIMIT " . $limit . " OFFSET " . ( ($page - 1) * $limit );
|
||||
|
||||
$rels = DatabaseConnection::i()->getConnection()->query($query, $id, $id);
|
||||
foreach($rels as $rel) {
|
||||
|
@ -102,7 +104,7 @@ class User extends RowModel
|
|||
return "/id" . $this->getId();
|
||||
}
|
||||
|
||||
function getAvatarUrl(): string
|
||||
function getAvatarUrl(string $size = "miniscule"): string
|
||||
{
|
||||
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
|
||||
|
||||
|
@ -115,7 +117,7 @@ class User extends RowModel
|
|||
if(is_null($avPhoto))
|
||||
return "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
|
||||
else
|
||||
return $avPhoto->getURL();
|
||||
return $avPhoto->getURLBySizeId($size);
|
||||
}
|
||||
|
||||
function getAvatarLink(): string
|
||||
|
@ -166,153 +168,169 @@ class User extends RowModel
|
|||
|
||||
return $this->getFirstName() . $pseudo . $this->getLastName();
|
||||
}
|
||||
|
||||
function getMorphedName(string $case = "genitive", bool $fullName = true): string
|
||||
{
|
||||
$name = $fullName ? ($this->getLastName() . " " . $this->getFirstName()) : $this->getFirstName();
|
||||
if(!preg_match("%^[А-яё\-]+$%", $name))
|
||||
return $name; # name is probably not russian
|
||||
|
||||
$inflected = inflectName($name, $case, $this->isFemale() ? Gender::FEMALE : Gender::MALE);
|
||||
|
||||
return $inflected ?: $name;
|
||||
}
|
||||
|
||||
function getCanonicalName(): string
|
||||
{
|
||||
if($this->getRecord()->deleted)
|
||||
if($this->getRecord()->deleted)
|
||||
return "DELETED";
|
||||
else
|
||||
return $this->getFirstName() . ' ' . $this->getLastName();
|
||||
else
|
||||
return $this->getFirstName() . " " . $this->getLastName();
|
||||
}
|
||||
|
||||
|
||||
function getPhone(): ?string
|
||||
{
|
||||
return $this->getRecord()->phone;
|
||||
}
|
||||
|
||||
|
||||
function getEmail(): ?string
|
||||
{
|
||||
return $this->getRecord()->email;
|
||||
}
|
||||
|
||||
|
||||
function getOnline(): DateTime
|
||||
{
|
||||
return new DateTime($this->getRecord()->online);
|
||||
}
|
||||
|
||||
|
||||
function getDescription(): ?string
|
||||
{
|
||||
return $this->getRecord()->about;
|
||||
}
|
||||
|
||||
|
||||
function getStatus(): ?string
|
||||
{
|
||||
return $this->getRecord()->status;
|
||||
}
|
||||
|
||||
|
||||
function getShortCode(): ?string
|
||||
{
|
||||
return $this->getRecord()->shortcode;
|
||||
}
|
||||
|
||||
|
||||
function getAlert(): ?string
|
||||
{
|
||||
return $this->getRecord()->alert;
|
||||
}
|
||||
|
||||
|
||||
function getBanReason(): ?string
|
||||
{
|
||||
return $this->getRecord()->block_reason;
|
||||
}
|
||||
|
||||
|
||||
function getBanInSupportReason(): ?string
|
||||
{
|
||||
return $this->getRecord()->block_in_support_reason;
|
||||
}
|
||||
|
||||
function getType(): int
|
||||
{
|
||||
return $this->getRecord()->type;
|
||||
}
|
||||
|
||||
|
||||
function getCoins(): float
|
||||
{
|
||||
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
|
||||
return 0.0;
|
||||
|
||||
|
||||
return $this->getRecord()->coins;
|
||||
}
|
||||
|
||||
|
||||
function getRating(): int
|
||||
{
|
||||
return OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"] ? $this->getRecord()->rating : 0;
|
||||
}
|
||||
|
||||
|
||||
function getReputation(): int
|
||||
{
|
||||
return $this->getRecord()->reputation;
|
||||
}
|
||||
|
||||
|
||||
function getRegistrationTime(): DateTime
|
||||
{
|
||||
return new DateTime($this->getRecord()->since->getTimestamp());
|
||||
}
|
||||
|
||||
|
||||
function getRegistrationIP(): string
|
||||
{
|
||||
return $this->getRecord()->registering_ip;
|
||||
}
|
||||
|
||||
|
||||
function getHometown(): ?string
|
||||
{
|
||||
return $this->getRecord()->hometown;
|
||||
}
|
||||
|
||||
|
||||
function getPoliticalViews(): int
|
||||
{
|
||||
return $this->getRecord()->polit_views;
|
||||
}
|
||||
|
||||
|
||||
function getMaritalStatus(): int
|
||||
{
|
||||
return $this->getRecord()->marital_status;
|
||||
}
|
||||
|
||||
|
||||
function getContactEmail(): ?string
|
||||
{
|
||||
return $this->getRecord()->email_contact;
|
||||
}
|
||||
|
||||
|
||||
function getTelegram(): ?string
|
||||
{
|
||||
return $this->getRecord()->telegram;
|
||||
}
|
||||
|
||||
|
||||
function getInterests(): ?string
|
||||
{
|
||||
return $this->getRecord()->interests;
|
||||
}
|
||||
|
||||
|
||||
function getFavoriteMusic(): ?string
|
||||
{
|
||||
return $this->getRecord()->fav_music;
|
||||
}
|
||||
|
||||
|
||||
function getFavoriteFilms(): ?string
|
||||
{
|
||||
return $this->getRecord()->fav_films;
|
||||
}
|
||||
|
||||
|
||||
function getFavoriteShows(): ?string
|
||||
{
|
||||
return $this->getRecord()->fav_shows;
|
||||
}
|
||||
|
||||
|
||||
function getFavoriteBooks(): ?string
|
||||
{
|
||||
return $this->getRecord()->fav_books;
|
||||
}
|
||||
|
||||
|
||||
function getFavoriteQuote(): ?string
|
||||
{
|
||||
return $this->getRecord()->fav_quote;
|
||||
}
|
||||
|
||||
|
||||
function getCity(): ?string
|
||||
{
|
||||
return $this->getRecord()->city;
|
||||
}
|
||||
|
||||
|
||||
function getPhysicalAddress(): ?string
|
||||
{
|
||||
return $this->getRecord()->address;
|
||||
}
|
||||
|
||||
|
||||
function getNotificationOffset(): int
|
||||
{
|
||||
return $this->getRecord()->notification_offset;
|
||||
|
@ -327,7 +345,7 @@ class User extends RowModel
|
|||
{
|
||||
return (int)floor((time() - $this->getBirthday()->timestamp()) / YEAR);
|
||||
}
|
||||
|
||||
|
||||
function get2faSecret(): ?string
|
||||
{
|
||||
return $this->getRecord()["2fa_secret"];
|
||||
|
@ -342,7 +360,7 @@ class User extends RowModel
|
|||
{
|
||||
$this->stateChanges("notification_offset", time());
|
||||
}
|
||||
|
||||
|
||||
function getLeftMenuItemStatus(string $id): bool
|
||||
{
|
||||
return (bool) bmask($this->getRecord()->left_menu, [
|
||||
|
@ -359,7 +377,7 @@ class User extends RowModel
|
|||
],
|
||||
])->get($id);
|
||||
}
|
||||
|
||||
|
||||
function getPrivacySetting(string $id): int
|
||||
{
|
||||
return (int) bmask($this->getRecord()->privacy, [
|
||||
|
@ -378,7 +396,7 @@ class User extends RowModel
|
|||
],
|
||||
])->get($id);
|
||||
}
|
||||
|
||||
|
||||
function getPrivacyPermission(string $permission, ?User $user = NULL): bool
|
||||
{
|
||||
$permStatus = $this->getPrivacySetting($permission);
|
||||
|
@ -386,7 +404,7 @@ class User extends RowModel
|
|||
return $permStatus === User::PRIVACY_EVERYONE;
|
||||
else if($user->getId() === $this->getId())
|
||||
return true;
|
||||
|
||||
|
||||
switch($permStatus) {
|
||||
case User::PRIVACY_ONLY_FRIENDS:
|
||||
return $this->getSubscriptionStatus($user) === User::SUBSCRIPTION_MUTUAL;
|
||||
|
@ -397,12 +415,12 @@ class User extends RowModel
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getProfileCompletenessReport(): object
|
||||
{
|
||||
$incompleteness = 0;
|
||||
$unfilled = [];
|
||||
|
||||
|
||||
if(!$this->getRecord()->status) {
|
||||
$unfilled[] = "status";
|
||||
$incompleteness += 15;
|
||||
|
@ -423,46 +441,46 @@ class User extends RowModel
|
|||
$unfilled[] = "interests";
|
||||
$incompleteness += 20;
|
||||
}
|
||||
|
||||
|
||||
$total = max(100 - $incompleteness + $this->getRating(), 0);
|
||||
if(ovkGetQuirk("profile.rating-bar-behaviour") === 0)
|
||||
if ($total >= 100)
|
||||
$percent = round(($total / 10**strlen(strval($total))) * 100, 0);
|
||||
else
|
||||
$percent = min($total, 100);
|
||||
|
||||
|
||||
return (object) [
|
||||
"total" => $total,
|
||||
"percent" => $percent,
|
||||
"unfilled" => $unfilled,
|
||||
];
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
return $this->_abstractRelationCount("get-subscriptions-user");
|
||||
|
@ -472,7 +490,7 @@ class User extends RowModel
|
|||
{
|
||||
return sizeof(DatabaseConnection::i()->getContext()->table("messages")->where(["recipient_id" => $this->getId(), "unread" => 1]));
|
||||
}
|
||||
|
||||
|
||||
function getClubs(int $page = 1, bool $admin = false): \Traversable
|
||||
{
|
||||
if($admin) {
|
||||
|
@ -497,7 +515,7 @@ class User extends RowModel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getClubCount(bool $admin = false): int
|
||||
{
|
||||
if($admin) {
|
||||
|
@ -512,7 +530,7 @@ class User extends RowModel
|
|||
return sizeof($sel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getPinnedClubs(): \Traversable
|
||||
{
|
||||
foreach($this->getRecord()->related("groups.owner")->where("owner_club_pinned", true) as $target) {
|
||||
|
@ -553,16 +571,16 @@ class User extends RowModel
|
|||
foreach($sel as $target) {
|
||||
$target = (new Clubs)->get($target->event);
|
||||
if(!$target) continue;
|
||||
|
||||
|
||||
yield $target;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getMeetingCount(): int
|
||||
{
|
||||
return sizeof($this->getRecord()->related("event_turnouts.user"));
|
||||
}
|
||||
|
||||
|
||||
function getGifts(int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$gifts = $this->getRecord()->related("gift_user_relations.receiver")->order("sent DESC")->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
|
@ -576,7 +594,7 @@ class User extends RowModel
|
|||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getGiftCount(): int
|
||||
{
|
||||
return sizeof($this->getRecord()->related("gift_user_relations.receiver"));
|
||||
|
@ -611,9 +629,9 @@ class User extends RowModel
|
|||
|
||||
function use2faBackupCode(int $code): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->related("2fa_backup_codes.owner")->where("code", $code)->delete();
|
||||
return (bool) $this->getRecord()->related("2fa_backup_codes.owner")->where("code", $code)->delete();
|
||||
}
|
||||
|
||||
|
||||
function getSubscriptionStatus(User $user): int
|
||||
{
|
||||
$subbed = !is_null($this->getRecord()->related("subscriptions.follower")->where([
|
||||
|
@ -624,71 +642,76 @@ class User extends RowModel
|
|||
"model" => static::class,
|
||||
"follower" => $user->getId(),
|
||||
])->fetch());
|
||||
|
||||
|
||||
if($subbed && $followed) return User::SUBSCRIPTION_MUTUAL;
|
||||
if($subbed) return User::SUBSCRIPTION_INCOMING;
|
||||
if($followed) return User::SUBSCRIPTION_OUTGOING;
|
||||
|
||||
|
||||
return User::SUBSCRIPTION_ABSENT;
|
||||
}
|
||||
|
||||
|
||||
function getNotificationsCount(bool $archived = false): int
|
||||
{
|
||||
return (new Notifications)->getNotificationCountByUser($this, $this->getNotificationOffset(), $archived);
|
||||
}
|
||||
|
||||
|
||||
function getNotifications(int $page, bool $archived = false): \Traversable
|
||||
{
|
||||
return (new Notifications)->getNotificationsByUser($this, $this->getNotificationOffset(), $archived, $page);
|
||||
}
|
||||
|
||||
|
||||
function getPendingPhoneVerification(): ?ActiveRow
|
||||
{
|
||||
return $this->getRecord()->ref("number_verification", "id");
|
||||
}
|
||||
|
||||
|
||||
function getRefLinkId(): string
|
||||
{
|
||||
$hash = hash_hmac("Snefru", (string) $this->getId(), CHANDLER_ROOT_CONF["security"]["secret"], true);
|
||||
|
||||
|
||||
return dechex($this->getId()) . " " . base64_encode($hash);
|
||||
}
|
||||
|
||||
|
||||
function getNsfwTolerance(): int
|
||||
{
|
||||
return $this->getRecord()->nsfw_tolerance;
|
||||
}
|
||||
|
||||
|
||||
function isFemale(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->sex;
|
||||
}
|
||||
|
||||
|
||||
function isVerified(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->verified;
|
||||
}
|
||||
|
||||
|
||||
function isBanned(): bool
|
||||
{
|
||||
return !is_null($this->getBanReason());
|
||||
}
|
||||
|
||||
|
||||
function isBannedInSupport(): bool
|
||||
{
|
||||
return !is_null($this->getBanInSupportReason());
|
||||
}
|
||||
|
||||
function isOnline(): bool
|
||||
{
|
||||
return time() - $this->getRecord()->online <= 300;
|
||||
}
|
||||
|
||||
|
||||
function prefersNotToSeeRating(): bool
|
||||
{
|
||||
return !((bool) $this->getRecord()->show_rating);
|
||||
}
|
||||
|
||||
|
||||
function hasPendingNumberChange(): bool
|
||||
{
|
||||
return !is_null($this->getPendingPhoneVerification());
|
||||
}
|
||||
|
||||
|
||||
function gift(User $sender, Gift $gift, ?string $comment = NULL, bool $anonymous = false): void
|
||||
{
|
||||
DatabaseConnection::i()->getContext()->table("gift_user_relations")->insert([
|
||||
|
@ -700,7 +723,7 @@ class User extends RowModel
|
|||
"sent" => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
function ban(string $reason, bool $deleteSubscriptions = true): void
|
||||
{
|
||||
if($deleteSubscriptions) {
|
||||
|
@ -713,42 +736,42 @@ class User extends RowModel
|
|||
);
|
||||
$subs->delete();
|
||||
}
|
||||
|
||||
|
||||
$this->setBlock_Reason($reason);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
|
||||
function verifyNumber(string $code): bool
|
||||
{
|
||||
$ver = $this->getPendingPhoneVerification();
|
||||
if(!$ver) return false;
|
||||
|
||||
|
||||
try {
|
||||
if(sodium_memcmp((string) $ver->code, $code) === -1) return false;
|
||||
} catch(\SodiumException $ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$this->setPhone($ver->number);
|
||||
$this->save();
|
||||
|
||||
|
||||
DatabaseConnection::i()->getContext()
|
||||
->table("number_verification")
|
||||
->where("user", $this->getId())
|
||||
->delete();
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function setFirst_Name(string $firstName): void
|
||||
{
|
||||
$firstName = mb_convert_case($firstName, MB_CASE_TITLE);
|
||||
if(!preg_match('%^[\p{Lu}\p{Lo}]\p{Mn}?(?:[\p{L&}\p{Lo}]\p{Mn}?){1,16}$%u', $firstName))
|
||||
throw new InvalidUserNameException;
|
||||
|
||||
|
||||
$this->stateChanges("first_name", $firstName);
|
||||
}
|
||||
|
||||
|
||||
function setLast_Name(string $lastName): void
|
||||
{
|
||||
if(!empty($lastName))
|
||||
|
@ -757,15 +780,15 @@ class User extends RowModel
|
|||
if(!preg_match('%^[\p{Lu}\p{Lo}]\p{Mn}?([\p{L&}\p{Lo}]\p{Mn}?){1,16}(\-\g<1>+)?$%u', $lastName))
|
||||
throw new InvalidUserNameException;
|
||||
}
|
||||
|
||||
|
||||
$this->stateChanges("last_name", $lastName);
|
||||
}
|
||||
|
||||
|
||||
function setNsfwTolerance(int $tolerance): void
|
||||
{
|
||||
$this->stateChanges("nsfw_tolerance", $tolerance);
|
||||
}
|
||||
|
||||
|
||||
function setPrivacySetting(string $id, int $status): void
|
||||
{
|
||||
$this->stateChanges("privacy", bmask($this->changes["privacy"] ?? $this->getRecord()->privacy, [
|
||||
|
@ -784,7 +807,7 @@ class User extends RowModel
|
|||
],
|
||||
])->set($id, $status)->toInteger());
|
||||
}
|
||||
|
||||
|
||||
function setLeftMenuItemStatus(string $id, bool $status): void
|
||||
{
|
||||
$mask = bmask($this->changes["left_menu"] ?? $this->getRecord()->left_menu, [
|
||||
|
@ -800,10 +823,10 @@ class User extends RowModel
|
|||
"poster",
|
||||
],
|
||||
])->set($id, (int) $status)->toInteger();
|
||||
|
||||
|
||||
$this->stateChanges("left_menu", $mask);
|
||||
}
|
||||
|
||||
|
||||
function setShortCode(?string $code = NULL, bool $force = false): ?bool
|
||||
{
|
||||
if(!is_null($code)) {
|
||||
|
@ -815,20 +838,20 @@ class User extends RowModel
|
|||
return false;
|
||||
if(\Chandler\MVC\Routing\Router::i()->getMatchingRoute("/$code")[0]->presenter !== "UnknownTextRouteStrategy")
|
||||
return false;
|
||||
|
||||
|
||||
$pClub = DatabaseConnection::i()->getContext()->table("groups")->where("shortcode", $code)->fetch();
|
||||
if(!is_null($pClub))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$this->stateChanges("shortcode", $code);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function setPhoneWithVerification(string $phone): string
|
||||
{
|
||||
$code = unpack("S", openssl_random_pseudo_bytes(2))[1];
|
||||
|
||||
|
||||
if($this->hasPendingNumberChange()) {
|
||||
DatabaseConnection::i()->getContext()
|
||||
->table("number_verification")
|
||||
|
@ -839,10 +862,10 @@ class User extends RowModel
|
|||
->table("number_verification")
|
||||
->insert(["user" => $this->getId(), "number" => $phone, "code" => $code]);
|
||||
}
|
||||
|
||||
|
||||
return (string) $code;
|
||||
}
|
||||
|
||||
|
||||
# KABOBSQL temporary fix
|
||||
# Tuesday, the 7th of January 2020 @ 22:43 <Menhera>: implementing quick fix to this problem and monitoring
|
||||
# NOTICE: this is an ongoing conversation, add your comments just above this line. Thanks!
|
||||
|
@ -850,10 +873,10 @@ class User extends RowModel
|
|||
{
|
||||
$this->stateChanges("shortcode", $this->getRecord()->shortcode); #fix KABOBSQL
|
||||
$this->stateChanges("online", $time);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function adminNotify(string $message): bool
|
||||
{
|
||||
$admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
|
||||
|
@ -861,12 +884,12 @@ class User extends RowModel
|
|||
return false;
|
||||
else if(is_null($admin = (new Users)->get($admId)))
|
||||
return false;
|
||||
|
||||
|
||||
$cor = new Correspondence($admin, $this);
|
||||
$msg = new Message;
|
||||
$msg->setContent($message);
|
||||
$cor->sendMessage($msg, true);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -893,7 +916,7 @@ class User extends RowModel
|
|||
case 2:
|
||||
return 2;
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
return 0;
|
||||
break;
|
||||
|
|
|
@ -14,6 +14,8 @@ class Video extends Media
|
|||
|
||||
protected $tableName = "videos";
|
||||
protected $fileExtension = "ogv";
|
||||
|
||||
protected $processingPlaceholder = "video/rendering";
|
||||
|
||||
protected function saveFile(string $filename, string $hash): bool
|
||||
{
|
||||
|
@ -37,11 +39,13 @@ class Video extends Media
|
|||
throw new \DomainException("$filename does not contain any meaningful video streams");
|
||||
|
||||
try {
|
||||
if(!is_dir($dirId = $this->pathFromHash($hash)))
|
||||
if(!is_dir($dirId = dirname($this->pathFromHash($hash))))
|
||||
mkdir($dirId);
|
||||
|
||||
$dir = $this->getBaseDir();
|
||||
Shell::bash(__DIR__ . "/../shell/processVideo.sh", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD
|
||||
$ext = Shell::isPowershell() ? "ps1" : "sh";
|
||||
$cmd = Shell::isPowershell() ? "powershell" : "bash";
|
||||
Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD
|
||||
} catch(ShellUnavailableException $suex) {
|
||||
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR);
|
||||
} catch(UnknownCommandException $ucex) {
|
||||
|
@ -51,7 +55,23 @@ class Video extends Media
|
|||
usleep(200100);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected function checkIfFileIsProcessed(): bool
|
||||
{
|
||||
if($this->getType() != Video::TYPE_DIRECT)
|
||||
return true;
|
||||
|
||||
if(!file_exists($this->getFileName())) {
|
||||
if((time() - $this->getRecord()->last_checked) > 3600) {
|
||||
// TODO notify that video processor is probably dead
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getName(): string
|
||||
{
|
||||
return $this->getRecord()->name;
|
||||
|
@ -81,6 +101,9 @@ class Video extends Media
|
|||
function getThumbnailURL(): string
|
||||
{
|
||||
if($this->getType() === Video::TYPE_DIRECT) {
|
||||
if(!$this->isProcessed())
|
||||
return "/assets/packages/static/openvk/video/rendering.apng";
|
||||
|
||||
return preg_replace("%\.[A-z]++$%", ".gif", $this->getURL());
|
||||
} else {
|
||||
return $this->getVideoDriver()->getThumbnailURL();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ class Clubs
|
|||
|
||||
function getPopularClubs(): \Traversable
|
||||
{
|
||||
$query = "SELECT ROW_NUMBER() OVER (ORDER BY `subscriptions` DESC) as `place`, `target` as `id`, COUNT(`follower`) as `subscriptions` FROM `subscriptions` WHERE `model` = \"openvk\\\Web\\\Models\\\Entities\\\Club\" GROUP BY `target` ORDER BY `subscriptions` DESC, `id` LIMIT 10;";
|
||||
$query = "SELECT ROW_NUMBER() OVER (ORDER BY `subscriptions` DESC) as `place`, `target` as `id`, COUNT(`follower`) as `subscriptions` FROM `subscriptions` WHERE `model` = \"openvk\\\Web\\\Models\\\Entities\\\Club\" GROUP BY `target` ORDER BY `subscriptions` DESC, `id` LIMIT 30;";
|
||||
$entries = DatabaseConnection::i()->getConnection()->query($query);
|
||||
|
||||
foreach($entries as $entry)
|
||||
|
|
|
@ -29,7 +29,7 @@ class Notes
|
|||
function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
|
||||
foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->page($page, $perPage) as $album)
|
||||
foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->order("created DESC")->page($page, $perPage) as $album)
|
||||
yield new Note($album);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
20
Web/Models/shell/processVideo.ps1
Normal file
20
Web/Models/shell/processVideo.ps1
Normal file
|
@ -0,0 +1,20 @@
|
|||
$ovkRoot = $args[0]
|
||||
$file = $args[1]
|
||||
$dir = $args[2]
|
||||
$hash = $args[3]
|
||||
$hashT = $hash.substring(0, 2)
|
||||
$temp = [System.IO.Path]::GetTempFileName()
|
||||
$temp2 = [System.IO.Path]::GetTempFileName()
|
||||
|
||||
$shell = Get-WmiObject Win32_process -filter "ProcessId = $PID"
|
||||
$shell.SetPriority(16384)
|
||||
|
||||
Move-Item $file $temp
|
||||
|
||||
# video stub logic was implicitly deprecated, so we start processing at once
|
||||
ffmpeg -i $temp -ss 00:00:01.000 -vframes 1 "$dir$hashT/$hash.gif"
|
||||
ffmpeg -i $temp -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y $temp2
|
||||
|
||||
Move-Item $temp2 "$dir$hashT/$hash.ogv"
|
||||
Remove-Item $temp
|
||||
Remove-Item $temp2
|
|
@ -5,7 +5,7 @@ cp ../files/video/rendering.apng $3${4:0:2}/$4.gif
|
|||
cp ../files/video/rendering.ogv $3/${4:0:2}/$4.ogv
|
||||
|
||||
nice ffmpeg -i "/tmp/vid_$tmpfile.bin" -ss 00:00:01.000 -vframes 1 $3${4:0:2}/$4.gif
|
||||
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf scale=640x360,setsar=1:1 -y "/tmp/ffmOi$tmpfile.ogv"
|
||||
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y "/tmp/ffmOi$tmpfile.ogv"
|
||||
|
||||
rm -rf $3${4:0:2}/$4.ogv
|
||||
mv "/tmp/ffmOi$tmpfile.ogv" $3${4:0:2}/$4.ogv
|
||||
|
|
|
@ -126,4 +126,11 @@ final class AboutPresenter extends OpenVKPresenter
|
|||
header("Location: https://github.com/openvk/openvk#readme");
|
||||
exit;
|
||||
}
|
||||
|
||||
function renderDev(): void
|
||||
{
|
||||
header("HTTP/1.1 302 Found");
|
||||
header("Location: https://docs.openvk.su/");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ final class AdminPresenter extends OpenVKPresenter
|
|||
private function warnIfNoCommerce(): void
|
||||
{
|
||||
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
|
||||
$this->flash("warn", "Коммерция отключена системным администратором", "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния.");
|
||||
$this->flash("warn", tr("admin_commerce_disabled"), tr("admin_commerce_disabled_desc"));
|
||||
}
|
||||
|
||||
private function searchResults(object $repo, &$count)
|
||||
|
@ -346,7 +346,20 @@ final class AdminPresenter extends OpenVKPresenter
|
|||
exit(json_encode([ "error" => "User does not exist" ]));
|
||||
|
||||
$user->ban($this->queryParam("reason"));
|
||||
exit(json_encode([ "reason" => $this->queryParam("reason") ]));
|
||||
exit(json_encode([ "success" => true, "reason" => $this->queryParam("reason") ]));
|
||||
}
|
||||
|
||||
function renderQuickUnban(int $id): void
|
||||
{
|
||||
$this->assertNoCSRF();
|
||||
|
||||
$user = $this->users->get($id);
|
||||
if(!$user)
|
||||
exit(json_encode([ "error" => "User does not exist" ]));
|
||||
|
||||
$user->setBlock_Reason(null);
|
||||
$user->save();
|
||||
exit(json_encode([ "success" => true ]));
|
||||
}
|
||||
|
||||
function renderQuickWarn(int $id): void
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -17,20 +17,23 @@ final class BlobPresenter extends OpenVKPresenter
|
|||
function renderFile(/*string*/ $dir, string $name, string $format)
|
||||
{
|
||||
$dir = $this->getDirName($dir);
|
||||
$name = preg_replace("%[^a-zA-Z0-9_\-]++%", "", $name);
|
||||
$path = OPENVK_ROOT . "/storage/$dir/$name.$format";
|
||||
if(!file_exists($path)) {
|
||||
$base = realpath(OPENVK_ROOT . "/storage/$dir");
|
||||
$path = realpath(OPENVK_ROOT . "/storage/$dir/$name.$format");
|
||||
if(!$path) # Will also check if file exists since realpath fails on ENOENT
|
||||
$this->notFound();
|
||||
} else {
|
||||
if(isset($_SERVER["HTTP_IF_NONE_MATCH"]))
|
||||
else if(strpos($path, $path) !== 0) # Prevent directory traversal and storage container escape
|
||||
$this->notFound();
|
||||
|
||||
if(isset($_SERVER["HTTP_IF_NONE_MATCH"]))
|
||||
exit(header("HTTP/1.1 304 Not Modified"));
|
||||
|
||||
header("Content-Type: " . mime_content_type($path));
|
||||
header("Content-Size: " . filesize($path));
|
||||
header("Cache-Control: public, max-age=1210000");
|
||||
header("X-Accel-Expires: 1210000");
|
||||
header("ETag: W/\"" . hash_file("snefru", $path) . "\"");
|
||||
|
||||
readfile($path);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,6 +204,8 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
$this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
|
||||
$this->template->isTimezoned = Session::i()->get("_timezoneOffset");
|
||||
|
||||
$userValidated = 0;
|
||||
$cacheTime = OPENVK_ROOT_CONF["openvk"]["preferences"]["nginxCacheTime"] ?? 0;
|
||||
if(!is_null($user)) {
|
||||
$this->user = (object) [];
|
||||
$this->user->raw = $user;
|
||||
|
@ -261,6 +263,8 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
exit;
|
||||
}
|
||||
|
||||
$userValidated = 1;
|
||||
$cacheTime = 0; # Force no cache
|
||||
if ($this->user->identity->onlineStatus() == 0) {
|
||||
$this->user->identity->setOnline(time());
|
||||
$this->user->identity->save();
|
||||
|
@ -271,6 +275,8 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
|
||||
}
|
||||
|
||||
header("X-OpenVK-User-Validated: $userValidated");
|
||||
header("X-Accel-Expires: $cacheTime");
|
||||
setlocale(LC_TIME, ...(explode(";", tr("__locale"))));
|
||||
|
||||
parent::onStartup();
|
||||
|
|
|
@ -72,6 +72,8 @@ final class PhotosPresenter extends OpenVKPresenter
|
|||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if(empty($this->postParam("name")))
|
||||
$this->flashFail("err", tr("error"), tr("error_segmentation"));
|
||||
else if(strlen($this->postParam("name")) > 36)
|
||||
$this->flashFail("err", tr("error"), tr("error_data_too_big", "name", 36, "bytes"));
|
||||
|
||||
$album = new Album;
|
||||
$album->setOwner(isset($club) ? $club->getId() * -1 : $this->user->id);
|
||||
|
@ -100,6 +102,9 @@ final class PhotosPresenter extends OpenVKPresenter
|
|||
$this->template->album = $album;
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if(strlen($this->postParam("name")) > 36)
|
||||
$this->flashFail("err", tr("error"), tr("error_data_too_big", "name", 36, "bytes"));
|
||||
|
||||
$album->setName(empty($this->postParam("name")) ? $album->getName() : $this->postParam("name"));
|
||||
$album->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
|
||||
$album->setEdited(time());
|
||||
|
@ -276,6 +281,8 @@ final class PhotosPresenter extends OpenVKPresenter
|
|||
|
||||
$photo->isolate();
|
||||
$photo->delete();
|
||||
exit("Фотография успешно удалена!");
|
||||
|
||||
$this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена.");
|
||||
$this->redirect("/id0", static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" ];
|
||||
|
|
|
@ -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 ]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ final class UserPresenter extends OpenVKPresenter
|
|||
{
|
||||
$user = $this->users->get($id);
|
||||
if(!$user || $user->isDeleted())
|
||||
$this->notFound();
|
||||
$this->template->_template = "User/deleted.xml";
|
||||
else {
|
||||
if($user->getShortCode())
|
||||
if(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) !== "/" . $user->getShortCode())
|
||||
|
@ -285,7 +285,6 @@ final class UserPresenter extends OpenVKPresenter
|
|||
$photo->setCreated(time());
|
||||
$photo->save();
|
||||
} catch(ISE $ex) {
|
||||
$name = $album->getName();
|
||||
$this->flashFail("err", tr("error"), tr("error_upload_failed"));
|
||||
}
|
||||
|
||||
|
@ -482,6 +481,22 @@ final class UserPresenter extends OpenVKPresenter
|
|||
$this->flashFail("succ", tr("information_-1"), tr("two_factor_authentication_disabled_message"));
|
||||
}
|
||||
|
||||
function renderResetThemepack(): void
|
||||
{
|
||||
$this->assertNoCSRF();
|
||||
|
||||
$this->setSessionTheme(Themepacks::DEFAULT_THEME_ID);
|
||||
|
||||
if($this->user) {
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$this->user->identity->setStyle(Themepacks::DEFAULT_THEME_ID);
|
||||
$this->user->identity->save();
|
||||
}
|
||||
|
||||
$this->redirect("/", static::REDIRECT_TEMPORARY_PRESISTENT);
|
||||
}
|
||||
|
||||
function renderCoinsTransfer(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -45,7 +45,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
function renderWall(int $user, bool $embedded = false): void
|
||||
{
|
||||
if(false)
|
||||
exit("Ошибка доступа: " . (string) random_int(0, 255));
|
||||
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
|
||||
|
||||
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
|
||||
if(is_null($this->user)) {
|
||||
|
@ -54,7 +54,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
if(!$owner->isBanned())
|
||||
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
|
||||
else
|
||||
$this->flashFail("err", tr("error"), "Ошибка доступа");
|
||||
$this->flashFail("err", tr("error"), tr("forbidden"));
|
||||
} else if($user < 0) {
|
||||
if($owner->canBeModifiedBy($this->user->identity))
|
||||
$canPost = true;
|
||||
|
@ -89,7 +89,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
function renderRSS(int $user): void
|
||||
{
|
||||
if(false)
|
||||
exit("Ошибка доступа: " . (string) random_int(0, 255));
|
||||
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
|
||||
|
||||
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
|
||||
if(is_null($this->user)) {
|
||||
|
@ -98,7 +98,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
if(!$owner->isBanned())
|
||||
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
|
||||
else
|
||||
$this->flashFail("err", tr("error"), "Ошибка доступа");
|
||||
$this->flashFail("err", tr("error"), tr("forbidden"));
|
||||
} else if($user < 0) {
|
||||
if($owner->canBeModifiedBy($this->user->identity))
|
||||
$canPost = true;
|
||||
|
@ -213,12 +213,12 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$this->willExecuteWriteAction();
|
||||
|
||||
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
|
||||
?? $this->flashFail("err", "Не удалось опубликовать пост", "Такого пользователя не существует.");
|
||||
?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
|
||||
if($wall > 0) {
|
||||
if(!$wallOwner->isBanned())
|
||||
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
|
||||
else
|
||||
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
|
||||
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
|
||||
} else if($wall < 0) {
|
||||
if($wallOwner->canBeModifiedBy($this->user->identity))
|
||||
$canPost = true;
|
||||
|
@ -229,7 +229,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
}
|
||||
|
||||
if(!$canPost)
|
||||
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
|
||||
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
|
||||
|
||||
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
|
||||
if($wallOwner instanceof Club && $this->postParam("as_group") === "on" && $this->postParam("force_sign") !== "on" && $anon) {
|
||||
|
@ -265,10 +265,18 @@ final class WallPresenter extends OpenVKPresenter
|
|||
} catch(ISE $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Файл медиаконтента повреждён или слишком велик.");
|
||||
}
|
||||
|
||||
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"], $anon);
|
||||
}
|
||||
} catch(\DomainException $ex) {
|
||||
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted"));
|
||||
} catch(ISE $ex) {
|
||||
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted_or_too_large"));
|
||||
}
|
||||
|
||||
if(empty($this->postParam("text")) && empty($photos))
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Пост пустой или слишком большой.");
|
||||
|
||||
if(empty($this->postParam("text")) && !$photo && !$video)
|
||||
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
|
||||
|
||||
try {
|
||||
$post = new Post;
|
||||
|
@ -281,7 +289,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$post->setNsfw($this->postParam("nsfw") === "on");
|
||||
$post->save();
|
||||
} catch (\LengthException $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Пост слишком большой.");
|
||||
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
|
||||
}
|
||||
|
||||
foreach($photos as $photo) {
|
||||
|
@ -300,8 +308,6 @@ final class WallPresenter extends OpenVKPresenter
|
|||
|
||||
function renderPost(int $wall, int $post_id): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$post = $this->posts->getPostById($wall, $post_id);
|
||||
if(!$post || $post->isDeleted())
|
||||
$this->notFound();
|
||||
|
@ -313,7 +319,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$this->template->wallOwner = (new Users)->get($post->getTargetWall());
|
||||
$this->template->isWallOfGroup = false;
|
||||
if($this->template->wallOwner->isBanned())
|
||||
$this->flashFail("err", tr("error"), "Ошибка доступа");
|
||||
$this->flashFail("err", tr("error"), tr("forbidden"));
|
||||
} else {
|
||||
$this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall()));
|
||||
$this->template->isWallOfGroup = true;
|
||||
|
@ -377,7 +383,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$user = $this->user->id;
|
||||
|
||||
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
|
||||
?? $this->flashFail("err", "Не удалось удалить пост", "Такого пользователя не существует.");
|
||||
?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
|
||||
|
||||
if($wall < 0) $canBeDeletedByOtherUser = $wallOwner->canBeModifiedBy($this->user->identity);
|
||||
else $canBeDeletedByOtherUser = false;
|
||||
|
@ -388,7 +394,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$post->delete();
|
||||
}
|
||||
} else {
|
||||
$this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт.");
|
||||
$this->flashFail("err", tr("failed_to_delete_post"), tr("login_required_error_comment"));
|
||||
}
|
||||
|
||||
$this->redirect($wall < 0 ? "/club".($wall*-1) : "/id".$wall, static::REDIRECT_TEMPORARY);
|
||||
|
@ -405,7 +411,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$this->notFound();
|
||||
|
||||
if(!$post->canBePinnedBy($this->user->identity))
|
||||
$this->flashFail("err", "Ошибка доступа", "Вам нельзя закреплять этот пост.");
|
||||
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
|
||||
|
||||
if(($this->queryParam("act") ?? "pin") === "pin") {
|
||||
$post->pin();
|
||||
|
@ -414,6 +420,6 @@ final class WallPresenter extends OpenVKPresenter
|
|||
}
|
||||
|
||||
// TODO localize message based on language and ?act=(un)pin
|
||||
$this->flashFail("succ", "Операция успешна", "Операция успешна.");
|
||||
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
</div>
|
||||
|
||||
<div class="container_gray">
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
|
||||
{if sizeof($data) > 0}
|
||||
<div class="content" n:foreach="$data as $dat">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{block wrap}
|
||||
<div class="ovk-lw-container">
|
||||
<div class="ovk-lw--list">
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
|
||||
{if sizeof($data) > 0}
|
||||
<table n:foreach="$data as $dat" border="0" style="font-size:11px;" class="post">
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{var instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
|
||||
|
||||
{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{var instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
|
||||
|
||||
{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
|
||||
{if !isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'}
|
||||
<!DOCTYPE html>
|
||||
<html n:if="!isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'">
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
{ifset title}{include title} - {/ifset}{$instance_name}
|
||||
|
@ -98,12 +98,12 @@
|
|||
|
||||
<div class="layout">
|
||||
<div id="xhead" class="dm"></div>
|
||||
<div class="page_header {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}page_custom_header{/if}">
|
||||
<a href="/" class="home_button {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}home_button_custom{/if}" title="{$instance_name}">{$instance_name}</a>
|
||||
<div class="page_header{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME} page_custom_header{/if}">
|
||||
<a href="/" class="home_button{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME} home_button_custom{/if}" title="{$instance_name}">{if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}{$instance_name}{/if}</a>
|
||||
<div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
|
||||
{ifset $thisUser}
|
||||
<div class="link">
|
||||
<a href="/">{_header_home}</a>
|
||||
<a href="/" title="[Alt+Shift+,]" accesskey=",">{_header_home}</a>
|
||||
</div>
|
||||
<div class="link">
|
||||
<a href="/search?type=groups">{_header_groups}</a>
|
||||
|
@ -122,14 +122,14 @@
|
|||
</div>
|
||||
<div class="link">
|
||||
<form action="/search" method="get">
|
||||
<input type="search" name="query" placeholder="{_header_search}" style="height: 20px;background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" />
|
||||
<input type="search" name="query" placeholder="{_header_search}" style="height: 20px;background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" title="{_header_search} [Alt+Shift+F]" accesskey="f" />
|
||||
</form>
|
||||
</div>
|
||||
{else}
|
||||
<div class="link">
|
||||
<a href="/login">{_header_login}</a>
|
||||
</div>
|
||||
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" class="link">
|
||||
<div class="link">
|
||||
<a href="/reg">{_header_registration}</a>
|
||||
</div>
|
||||
<div class="link">
|
||||
|
@ -144,7 +144,7 @@
|
|||
{ifset $thisUser}
|
||||
{if !$thisUser->isBanned() XOR !$thisUser->isActivated()}
|
||||
<a href="/edit" class="link edit-button">{_edit_button}</a>
|
||||
<a href="{$thisUser->getURL()}" class="link">{_my_page}</a>
|
||||
<a href="{$thisUser->getURL()}" class="link" title="{_my_page} [Alt+Shift+.]" accesskey=".">{_my_page}</a>
|
||||
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends}
|
||||
<object type="internal/link" n:if="$thisUser->getFollowersCount() > 0">
|
||||
<a href="/friends{$thisUser->getId()}?act=incoming">
|
||||
|
@ -161,19 +161,19 @@
|
|||
</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('notes')" href="/notes{$thisUser->getId()}" class="link">{_my_notes}</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('groups')" href="/groups{$thisUser->getId()}" class="link">{_my_groups}</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link">{_my_feed}</a>
|
||||
<a href="/notifications" class="link">{_my_feedback}
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link" title="{_my_feed} [Alt+Shift+W]" accesskey="w">{_my_feed}</a>
|
||||
<a href="/notifications" class="link" title="{_my_feedback} [Alt+Shift+N]" accesskey="n">{_my_feedback}
|
||||
{if $thisUser->getNotificationsCount() > 0}
|
||||
(<b>{$thisUser->getNotificationsCount()}</b>)
|
||||
{/if}
|
||||
</a>
|
||||
<a href="/settings" class="link">{_my_settings}</a>
|
||||
|
||||
{var canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
|
||||
{var canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
||||
{var menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
|
||||
{var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
|
||||
{var $canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
||||
{var $menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
|
||||
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
|
||||
<a href="/admin" class="link" n:if="$canAccessAdminPanel">Админ-панель</a>
|
||||
<a href="/admin" class="link" n:if="$canAccessAdminPanel" title="{_admin} [Alt+Shift+A]" accesskey="a">{_admin}</a>
|
||||
<a href="/support/tickets" class="link" n:if="$canAccessHelpdesk">Helpdesk
|
||||
{if $helpdeskTicketNotAnsweredCount > 0}
|
||||
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
|
||||
|
@ -186,6 +186,14 @@
|
|||
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
|
||||
<a n:foreach="$thisUser->getPinnedClubs() as $club" href="{$club->getURL()}" class="link group_link">{$club->getName()}</a>
|
||||
</div>
|
||||
|
||||
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && $thisUser->getCoins() != 0" id="votesBalance">
|
||||
{tr("you_still_have_x_points", $thisUser->getCoins())|noescape}
|
||||
<br /><br />
|
||||
|
||||
<a href="/settings?act=finance">{_top_up_your_account} »</a>
|
||||
</div>
|
||||
|
||||
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable'] && $thisUser->getLeftMenuItemStatus('poster')" href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
|
||||
<img src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}" alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}" class="psa-poster" style="max-width: 100%; margin-top: 50px;" />
|
||||
</a>
|
||||
|
@ -208,7 +216,7 @@
|
|||
<input type="hidden" name="jReturnTo" value="{$_SERVER['REQUEST_URI']}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_log_in}" class="button" style="display: inline-block;" />
|
||||
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" href="/reg" class="button" style="display: inline-block;">{_registration}</a><br><br>
|
||||
<a href="/reg" class="button" style="display: inline-block;">{_registration}</a><br><br>
|
||||
<a href="/restore">{_forgot_password}</a>
|
||||
</form>
|
||||
{/ifset}
|
||||
|
@ -251,7 +259,7 @@
|
|||
</div>
|
||||
|
||||
<div class="page_footer">
|
||||
{var dbVersion = \Chandler\Database\DatabaseConnection::i()->getConnection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION)}
|
||||
{var $dbVersion = \Chandler\Database\DatabaseConnection::i()->getConnection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION)}
|
||||
|
||||
<div class="navigation_footer">
|
||||
<a href="/about" class="link">{_footer_about_instance}</a>
|
||||
|
@ -288,12 +296,44 @@
|
|||
{/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}
|
||||
{/ifset}
|
||||
</body>
|
||||
</html>
|
||||
{/if}
|
||||
|
||||
{if isset($parentModule) && substr($parentModule, 0, 21) !== 'libchandler:absolute.'}
|
||||
<!-- INCLUDING TEMPLATE FROM PARENTMODULE: {$parentModule} -->
|
||||
|
|
|
@ -1,70 +1,74 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block wrap}
|
||||
<div class="page_wrap">
|
||||
<div n:ifset="tabs" n:ifcontent class="tabs">
|
||||
{include tabs}
|
||||
</div>
|
||||
<div class="wrap2">
|
||||
<div class="wrap1">
|
||||
<div class="page_wrap padding_top">
|
||||
<div n:ifset="tabs" class="tabs">
|
||||
{include tabs}
|
||||
</div>
|
||||
|
||||
{ifset size}
|
||||
{include size, x => $dat}
|
||||
{/ifset}
|
||||
|
||||
{ifset specpage}
|
||||
{include specpage, x => $dat}
|
||||
{else}
|
||||
<div class="container_gray">
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
{ifset size}
|
||||
{include size, x => $dat}
|
||||
{/ifset}
|
||||
|
||||
{if sizeof($data) > 0}
|
||||
<div class="content" n:foreach="$data as $dat">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<a href="{include link, x => $dat}">
|
||||
{include preview, x => $dat}
|
||||
</a>
|
||||
</td>
|
||||
<td valign="top" style="width: 100%">
|
||||
{ifset infoTable}
|
||||
{include infoTable, x => $dat}
|
||||
{else}
|
||||
{ifset specpage}
|
||||
{include specpage, x => $dat}
|
||||
{else}
|
||||
<div class="container_gray">
|
||||
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
|
||||
{if sizeof($data) > 0}
|
||||
<div class="content" n:foreach="$data as $dat">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<a href="{include link, x => $dat}">
|
||||
{include preview, x => $dat}
|
||||
</a>
|
||||
</td>
|
||||
<td valign="top" style="width: 100%">
|
||||
{ifset infotable}
|
||||
{include infotable, x => $dat}
|
||||
{else}
|
||||
<a href="{include link, x => $dat}">
|
||||
<b>
|
||||
{include name, x => $dat}
|
||||
</b>
|
||||
</a>
|
||||
<br/>
|
||||
{include description, x => $dat}
|
||||
{/ifset}
|
||||
</td>
|
||||
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px; text-transform: lowercase;">
|
||||
{include actions, x => $dat}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{include "components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
"atBottom" => true,
|
||||
]}
|
||||
{else}
|
||||
{ifset customErrorMessage}
|
||||
{include customErrorMessage}
|
||||
{include description, x => $dat}
|
||||
{/ifset}
|
||||
</td>
|
||||
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px; text-transform: lowercase;">
|
||||
{include actions, x => $dat}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{include "components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
"atBottom" => true,
|
||||
]}
|
||||
{else}
|
||||
{include "components/nothing.xml"}
|
||||
{/ifset}
|
||||
{/if}
|
||||
</div>
|
||||
{/ifset}
|
||||
{ifset customErrorMessage}
|
||||
{include customErrorMessage}
|
||||
{else}
|
||||
{include "components/nothing.xml"}
|
||||
{/ifset}
|
||||
{/if}
|
||||
</div>
|
||||
{/ifset}
|
||||
|
||||
{ifset bottom}
|
||||
{include bottom}
|
||||
{/ifset}
|
||||
{ifset bottom}
|
||||
{include bottom}
|
||||
{/ifset}
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr valign="top">
|
||||
<td width="250" {if sizeof($admins) > 0}style="padding-right: 10px;"{/if}>
|
||||
<td width="250"{if sizeof($admins) > 0} style="padding-right: 10px;"{/if}>
|
||||
<h4>{_statistics}</h4>
|
||||
<div style="margin-top: 5px;">
|
||||
{_on_this_instance_are}
|
||||
|
@ -21,6 +21,15 @@
|
|||
<li><span>{tr("about_wall_posts", $postsCount)|noescape}</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{if OPENVK_ROOT_CONF['openvk']['preferences']['about']['links']}
|
||||
<h4>{_about_links}</h4>
|
||||
<div style="margin-top: 5px;">
|
||||
{_instance_links}
|
||||
<ul>
|
||||
<li n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['about']['links'] as $aboutLink"><a href="{$aboutLink['url']}" target="_blank" class="link">{$aboutLink["name"]}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td n:if="sizeof($admins) > 0">
|
||||
<h4>{_administrators}</h4>
|
||||
|
@ -44,14 +53,23 @@
|
|||
|
||||
{if sizeof($popularClubs) !== 0}
|
||||
<h4>{_most_popular_groups}</h4>
|
||||
<ol>
|
||||
<li n:foreach="$popularClubs as $entry" style="margin-top: 5px;">
|
||||
<a href="{$entry->club->getURL()}">{$entry->club->getName()}</a>
|
||||
<div>
|
||||
{tr("participants", $entry->subscriptions)}
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
{var $entries = array_chunk($popularClubs, 10, true)}
|
||||
<table width="100%" cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr valign="top">
|
||||
<td n:foreach="$entries as $chunk">
|
||||
<ol>
|
||||
<li value="{$num+1}" style="margin-top: 5px;" n:foreach="$chunk as $num => $club">
|
||||
<a href="{$club->club->getURL()}">{$club->club->getName()}</a>
|
||||
<div>
|
||||
{tr("participants", $club->subscriptions)}
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
|
||||
<h4>{_rules}</h4>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{presenter "openvk!Support->knowledgeBaseArticle", "about"}
|
||||
<center>
|
||||
<a class="button" style="margin-right: 5px;cursor: pointer;" href="/login">{_"log_in"}</a>
|
||||
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" class="button" style="cursor: pointer;" href="/reg">{_"registration"}</a>
|
||||
<a class="button" style="cursor: pointer;" href="/reg">{_"registration"}</a>
|
||||
</center>
|
||||
{* TO-DO: Add statistics about this instance as on mastodon.social *}
|
||||
{/block}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
|
||||
<style>
|
||||
{var css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")}
|
||||
{var $css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")}
|
||||
{str_replace("fonts/", "/assets/packages/static/openvk/js/node_modules/@atlassian/aui/dist/aui/fonts/", $css)|noescape}
|
||||
</style>
|
||||
<title>{include title} - Админ-панель {=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</title>
|
||||
<title>{include title} - {_admin} {$instance_name}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
@ -16,23 +17,15 @@
|
|||
<div class="aui-header-primary">
|
||||
<h1 id="logo" class="aui-header-logo aui-header-logo-textonly">
|
||||
<a href="/admin">
|
||||
<span class="aui-header-logo-device">{=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</span>
|
||||
<span class="aui-header-logo-device">{$instance_name}</span>
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div n:if="$search ?? false" class="aui-header-secondary">
|
||||
<ul class="aui-nav">
|
||||
<form class="aui-quicksearch dont-default-focus ajs-dirty-warning-exempt">
|
||||
<input
|
||||
id="quickSearchInput"
|
||||
autocomplete="off"
|
||||
class="search"
|
||||
type="text"
|
||||
placeholder="{include searchTitle}"
|
||||
value="{$_GET['q'] ?? ''}"
|
||||
name="q"
|
||||
accesskey="Q" />
|
||||
<input type="hidden" value=1 name=p />
|
||||
<input id="quickSearchInput" autocomplete="off" class="search" type="text" placeholder="{include searchTitle}" value="{$_GET['q'] ?? ''}" name="q" accesskey="Q" />
|
||||
<input type="hidden" value=1 name=p />
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -46,83 +39,64 @@
|
|||
<div class="aui-navgroup-inner">
|
||||
<div class="aui-navgroup-primary">
|
||||
<div class="aui-nav-heading">
|
||||
<strong>Обзор</strong>
|
||||
<strong>{_admin_overview}</strong>
|
||||
</div>
|
||||
<ul class="aui-nav">
|
||||
<li>
|
||||
<a href="/admin">
|
||||
Сводка
|
||||
</a>
|
||||
<a href="/admin">{_admin_overview_summary}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="aui-nav-heading">
|
||||
<strong>Пользовательский контент</strong>
|
||||
<strong>{_admin_content}</strong>
|
||||
</div>
|
||||
<ul class="aui-nav">
|
||||
<li>
|
||||
<a href="/admin/users">
|
||||
Пользователи
|
||||
</a>
|
||||
<a href="/admin/users">{_users}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/clubs">
|
||||
Группы
|
||||
</a>
|
||||
<a href="/admin/clubs">{_groups}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="aui-nav-heading">
|
||||
<strong>Платные услуги</strong>
|
||||
<strong>{_admin_services}</strong>
|
||||
</div>
|
||||
<ul class="aui-nav">
|
||||
<li>
|
||||
<a href="/admin/vouchers">
|
||||
{_vouchers}
|
||||
</a>
|
||||
<a href="/admin/vouchers">{_vouchers}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/gifts">
|
||||
Подарки
|
||||
</a>
|
||||
<a href="/admin/gifts">{_gifts}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="aui-nav-heading">
|
||||
<strong>Настройки</strong>
|
||||
<strong>{_admin_settings}</strong>
|
||||
</div>
|
||||
<ul class="aui-nav">
|
||||
<li>
|
||||
<a href="/admin/settings/tuning">
|
||||
Общие
|
||||
</a>
|
||||
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings/appearance">
|
||||
Внешний вид
|
||||
</a>
|
||||
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings/security">
|
||||
Безопасность
|
||||
</a>
|
||||
<a href="/admin/settings/security">{_admin_settings_security}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings/integrations">
|
||||
Интеграции
|
||||
</a>
|
||||
<a href="/admin/settings/integrations">{_admin_settings_integrations}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings/system">
|
||||
Система
|
||||
</a>
|
||||
<a href="/admin/settings/system">{_admin_settings_system}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="aui-nav-heading">
|
||||
<strong>Об OpenVK</strong>
|
||||
<strong>{_admin_about}</strong>
|
||||
</div>
|
||||
<ul class="aui-nav">
|
||||
<li>
|
||||
<a href="/about:openvk">
|
||||
Версия
|
||||
</a>
|
||||
<a href="/about:openvk">{_admin_about_version}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about">{_admin_about_instance}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -131,7 +105,7 @@
|
|||
</div>
|
||||
<section class="aui-page-panel-content">
|
||||
{ifset $flashMessage}
|
||||
{var type = ["err" => "error", "warn" => "warning", "info" => "basic", "succ" => "success"][$flashMessage->type]}
|
||||
{var $type = ["err" => "error", "warn" => "warning", "info" => "basic", "succ" => "success"][$flashMessage->type]}
|
||||
<div class="aui-message aui-message-{$type}" style="margin-bottom: 15px;">
|
||||
<p class="title">
|
||||
<strong>{$flashMessage->title}</strong>
|
||||
|
@ -139,11 +113,11 @@
|
|||
<p>{$flashMessage->msg|noescape}</p>
|
||||
</div>
|
||||
{/ifset}
|
||||
|
||||
|
||||
{ifset preHeader}
|
||||
{include preHeader}
|
||||
{/ifset}
|
||||
|
||||
|
||||
<header class="aui-page-header">
|
||||
<div class="aui-page-header-inner">
|
||||
<div class="aui-page-header-main">
|
||||
|
@ -167,11 +141,11 @@
|
|||
</section>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
{script "js/node_modules/jquery/dist/jquery.min.js"}
|
||||
{script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"}
|
||||
<script>AJS.tabs.setup();</script>
|
||||
|
||||
|
||||
{ifset scripts}
|
||||
{include scripts}
|
||||
{/ifset}
|
||||
|
|
|
@ -1,191 +1,157 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Редактировать {$club->getCanonicalName()}
|
||||
{_edit} {$club->getCanonicalName()}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{$club->getCanonicalName()}
|
||||
{/block}
|
||||
|
||||
|
||||
{block content}
|
||||
|
||||
{var isMain = $mode === 'main'}
|
||||
{var isBan = $mode === 'ban'}
|
||||
{var isFollowers = $mode === 'followers'}
|
||||
{var $isMain = $mode === 'main'}
|
||||
{var $isBan = $mode === 'ban'}
|
||||
{var $isFollowers = $mode === 'followers'}
|
||||
|
||||
{if $isMain}
|
||||
|
||||
<!-- This main block -->
|
||||
|
||||
<div class="aui-tabs horizontal-tabs">
|
||||
<nav class="aui-navgroup aui-navgroup-horizontal">
|
||||
<div class="aui-navgroup-inner">
|
||||
<div class="aui-navgroup-primary">
|
||||
<ul class="aui-nav">
|
||||
<li class="aui-nav-selected"><a href="?act=main">Главное</a></li>
|
||||
<li><a href="?act=ban">Бан</a></li>
|
||||
<li><a href="?act=followers">Участники</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="avatar">
|
||||
Аватарка
|
||||
</label>
|
||||
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$club->getAvatarUrl()}" style="object-fit: cover;"></img>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<input class="text medium-field" type="number" id="id" disabled value="{$club->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="id_owner">
|
||||
ID владельца
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="id_owner" name="id_owner" value="{$club->getOwner()->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name">
|
||||
Название
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="name" name="name" value="{$club->getName()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="about">
|
||||
Описание
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="about" name="about" value="{$club->getDescription()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="shortcode">
|
||||
Адрес
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$club->getShortCode()}" />
|
||||
</div>
|
||||
<br/>
|
||||
<div class="group">
|
||||
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
|
||||
<label for="verify">
|
||||
Верификация
|
||||
</label>
|
||||
</div>
|
||||
<div class="group">
|
||||
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
|
||||
<label for="hide_from_global_feed">
|
||||
Не отображать записи в глобальной ленте
|
||||
</label>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="button submit" type="submit" value="Сохранить">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $isBan}
|
||||
|
||||
<!-- This ban block -->
|
||||
|
||||
<div class="aui-tabs horizontal-tabs">
|
||||
<nav class="aui-navgroup aui-navgroup-horizontal">
|
||||
<div class="aui-navgroup-inner">
|
||||
<div class="aui-navgroup-primary">
|
||||
<ul class="aui-nav">
|
||||
<li><a href="?act=main">Главное</a></li>
|
||||
<li class="aui-nav-selected"><a href="?act=ban">Бан</a></li>
|
||||
<li><a href="?act=followers">Участники</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="ban_reason">
|
||||
Причина бана
|
||||
</label>
|
||||
<input class="text" type="text" id="text-input" name="ban_reason" value="{$club->getBanReason()}" />
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="button submit" type="submit" value="Сохранить">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $isFollowers}
|
||||
|
||||
<!-- This followers block -->
|
||||
|
||||
{var followers = iterator_to_array($followers)}
|
||||
|
||||
<div class="aui-tabs horizontal-tabs">
|
||||
<nav class="aui-navgroup aui-navgroup-horizontal">
|
||||
<div class="aui-navgroup-inner">
|
||||
<div class="aui-navgroup-primary">
|
||||
<ul class="aui-nav">
|
||||
<li><a href="?act=main">Главное</a></li>
|
||||
<li><a href="?act=ban">Бан</a></li>
|
||||
<li class="aui-nav-selected"><a href="?act=followers">Участники</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<table rules="none" class="aui aui-table-list">
|
||||
<tbody>
|
||||
<tr n:foreach="$followers as $follower">
|
||||
<td>{$follower->getId()}</td>
|
||||
<td>
|
||||
<span class="aui-avatar aui-avatar-xsmall">
|
||||
{if $isMain}
|
||||
<div class="aui-tabs horizontal-tabs">
|
||||
<nav class="aui-navgroup aui-navgroup-horizontal">
|
||||
<div class="aui-navgroup-inner">
|
||||
<div class="aui-navgroup-primary">
|
||||
<ul class="aui-nav">
|
||||
<li class="aui-nav-selected"><a href="?act=main">{_admin_tab_main}</a></li>
|
||||
<li><a href="?act=ban">{_admin_tab_ban}</a></li>
|
||||
<li><a href="?act=followers">{_admin_tab_followers}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="avatar">{_avatar}</label>
|
||||
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$follower->getAvatarUrl()}" alt="{$follower->getCanonicalName()}" role="presentation" />
|
||||
<img src="{$club->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<a href="{$follower->getURL()}">{$follower->getCanonicalName()}</a>
|
||||
|
||||
<span n:if="$follower->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
|
||||
заблокирован
|
||||
</span>
|
||||
</td>
|
||||
<td>{$follower->isFemale() ? "Женский" : "Мужской"}</td>
|
||||
<td>{$follower->getShortCode() ?? "(отсутствует)"}</td>
|
||||
<td>{$follower->getRegistrationTime()}</td>
|
||||
<td>
|
||||
<a class="aui-button aui-button-primary" href="/admin/users/id{$follower->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
|
||||
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/block}
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="id">ID</label>
|
||||
<input class="text medium-field" type="number" id="id" disabled value="{$club->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="id_owner">{_admin_ownerid}</label>
|
||||
<input class="text medium-field" type="text" id="id_owner" name="id_owner" value="{$club->getOwner()->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name">{_admin_title}</label>
|
||||
<input class="text medium-field" type="text" id="name" name="name" value="{$club->getName()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="about">{_admin_description}</label>
|
||||
<input class="text medium-field" type="text" id="about" name="about" value="{$club->getDescription()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="shortcode">{_admin_shortcode}</label>
|
||||
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$club->getShortCode()}" />
|
||||
</div>
|
||||
<br/>
|
||||
<div class="group">
|
||||
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
|
||||
<label for="verify">{_admin_verification}</label>
|
||||
</div>
|
||||
<div class="group">
|
||||
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
|
||||
<label for="hide_from_global_feed">{_admin_club_excludeglobalfeed}</label>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="button submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $isBan}
|
||||
<div class="aui-tabs horizontal-tabs">
|
||||
<nav class="aui-navgroup aui-navgroup-horizontal">
|
||||
<div class="aui-navgroup-inner">
|
||||
<div class="aui-navgroup-primary">
|
||||
<ul class="aui-nav">
|
||||
<li><a href="?act=main">{_admin_tab_main}</a></li>
|
||||
<li class="aui-nav-selected"><a href="?act=ban">{_admin_tab_ban}</a></li>
|
||||
<li><a href="?act=followers">{_admin_tab_followers}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="ban_reason">{_admin_banreason}</label>
|
||||
<input class="text" type="text" id="text-input" name="ban_reason" value="{$club->getBanReason()}" />
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="button submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if $isFollowers}
|
||||
{var $followers = iterator_to_array($followers)}
|
||||
|
||||
<div class="aui-tabs horizontal-tabs">
|
||||
<nav class="aui-navgroup aui-navgroup-horizontal">
|
||||
<div class="aui-navgroup-inner">
|
||||
<div class="aui-navgroup-primary">
|
||||
<ul class="aui-nav">
|
||||
<li><a href="?act=main">{_admin_tab_main}</a></li>
|
||||
<li><a href="?act=ban">{_admin_tab_ban}</a></li>
|
||||
<li class="aui-nav-selected"><a href="?act=followers">{_admin_tab_followers}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<table rules="none" class="aui aui-table-list">
|
||||
<tbody>
|
||||
<tr n:foreach="$followers as $follower">
|
||||
<td>{$follower->getId()}</td>
|
||||
<td>
|
||||
<span class="aui-avatar aui-avatar-xsmall">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$follower->getAvatarUrl()}" alt="{$follower->getCanonicalName()}" role="presentation" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<a href="{$follower->getURL()}">{$follower->getCanonicalName()}</a>
|
||||
|
||||
<span n:if="$follower->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
|
||||
</td>
|
||||
<td>{$follower->isFemale() ? tr("female") : tr("male")}</td>
|
||||
<td>{$follower->getShortCode() ?? "(" . tr("none") . ")"}</td>
|
||||
<td>{$follower->getRegistrationTime()}</td>
|
||||
<td>
|
||||
<a class="aui-button aui-button-primary" href="/admin/users/id{$follower->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div align="right">
|
||||
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
|
||||
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/block}
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
{extends "@layout.xml"}
|
||||
{var search = true}
|
||||
{var $search = true}
|
||||
|
||||
{block title}
|
||||
Группы
|
||||
{_admin_club_search}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
Бутылки
|
||||
{_groups}
|
||||
{/block}
|
||||
|
||||
{block searchTitle}Поиск бутылок{/block}
|
||||
{block searchTitle}
|
||||
{include title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{var clubs = iterator_to_array($clubs)}
|
||||
{var amount = sizeof($clubs)}
|
||||
{var $clubs = iterator_to_array($clubs)}
|
||||
{var $amount = sizeof($clubs)}
|
||||
|
||||
<table class="aui aui-table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Имя</th>
|
||||
<th>Автор</th>
|
||||
<th>Описание</th>
|
||||
<th>Короткий адрес</th>
|
||||
<th>Действия</th>
|
||||
<th>ID</th>
|
||||
<th>{_admin_title}</th>
|
||||
<th>{_admin_author}</th>
|
||||
<th>{_admin_description}</th>
|
||||
<th>{_admin_shortcode}</th>
|
||||
<th>{_admin_actions}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -32,28 +34,28 @@
|
|||
<td>
|
||||
<span class="aui-avatar aui-avatar-xsmall">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$club->getAvatarUrl()}" alt="{$club->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
|
||||
<img src="{$club->getAvatarUrl('miniscule')}" alt="{$club->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
|
||||
</td>
|
||||
<td>
|
||||
{var user = $club->getOwner()}
|
||||
{var $user = $club->getOwner()}
|
||||
|
||||
<span class="aui-avatar aui-avatar-xsmall">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$user->getAvatarUrl()}" alt="{$user->getCanonicalName()}" role="presentation" />
|
||||
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" role="presentation" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
|
||||
</td>
|
||||
<td>{$club->getDescription() ?? "(не указано)"}</td>
|
||||
<td>{$club->getDescription() ?? "(" . tr("none") . ")"}</td>
|
||||
<td>{$club->getShortCode()}</td>
|
||||
<td>
|
||||
<a class="aui-button aui-button-primary" href="/admin/clubs/id{$club->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -61,13 +63,9 @@
|
|||
</table>
|
||||
<br/>
|
||||
<div align="right">
|
||||
{var isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
|
||||
{var $isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
|
||||
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
{block title}
|
||||
{if $form->id === 0}
|
||||
Новый подарок
|
||||
{_admin_newgift}
|
||||
{else}
|
||||
Подарок "{$form->name}"
|
||||
{_gift} "{$form->name}"
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
|||
<form class="aui" method="POST" enctype="multipart/form-data">
|
||||
<div class="field-group">
|
||||
<label for="avatar">
|
||||
Изображение
|
||||
{_admin_image}
|
||||
<span n:if="$form->id === 0" class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
{if $form->id === 0}
|
||||
|
@ -29,43 +29,39 @@
|
|||
</span>
|
||||
<input style="display: none;" id="picInput" type="file" name="pic" accept="image/jpeg,image/png,image/gif,image/webp" />
|
||||
<div class="description">
|
||||
<a id="picChange" href="javascript:false">Заменить изображение?</a>
|
||||
<a id="picChange" href="javascript:false">{_admin_image_replace}</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<label for="id">ID</label>
|
||||
<input class="text long-field" type="number" id="id" disabled="disabled" value="{$form->id}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="putin">
|
||||
Использований
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="putin" disabled="disabled" value="{$form->usages}" />
|
||||
<label for="usages">{_admin_uses}</label>
|
||||
<input class="text long-field" type="number" id="usages" disabled="disabled" value="{$form->usages}" />
|
||||
<div n:if="$form->usages > 0" class="description">
|
||||
<a href="javascript:$('#putin').value(0);">Обнулить?</a>
|
||||
<a href="javascript:$('#usages').value(0);">{_admin_uses_reset}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name">
|
||||
Внутренее имя
|
||||
{_admin_name}
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name" name="name" value="{$form->name}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="price">
|
||||
Цена
|
||||
{_admin_price}
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="price" name="price" min="0" value="{$form->price}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="limit">
|
||||
Ограничение
|
||||
{_admin_limits}
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="number" min="-1" id="limit" name="limit" value="{$form->limit}" />
|
||||
|
@ -75,13 +71,13 @@
|
|||
<div class="checkbox" resolved="">
|
||||
<input n:attr="disabled => $form->id === 0, checked => $form->id === 0" class="checkbox" type="checkbox" name="reset_limit" id="reset_limit" />
|
||||
<span class="aui-form-glyph"></span>
|
||||
|
||||
<label for="reset_limit">Сбросить счётчик ограничений</label>
|
||||
|
||||
<label for="reset_limit">{_admin_limits_reset}</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<input n:if="$form->id === 0" type="hidden" name="_cat" value="{$_GET['cat'] ?? 1}" />
|
||||
|
||||
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
|
@ -94,12 +90,12 @@
|
|||
{block scripts}
|
||||
<script>
|
||||
const TRANS_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
|
||||
|
||||
$("#picChange").click(_ => $("#picInput").click());
|
||||
$("#picInput").bind("change", e => {
|
||||
if(typeof e.target.files[0] === "undefined")
|
||||
$("#pic").prop("src", URL.createObjectURL(TRANS_GIF));
|
||||
|
||||
|
||||
$("#pic").prop("src", URL.createObjectURL(e.target.files[0]));
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Наборы подарков
|
||||
{_admin_giftsets}
|
||||
{/block}
|
||||
|
||||
{block headingWrap}
|
||||
|
@ -9,7 +9,7 @@
|
|||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>Наборы подарков</h1>
|
||||
<h1>{_admin_giftsets}</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
|
@ -27,12 +27,11 @@
|
|||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<a class="aui-button aui-button-primary" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}.meta">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
|
||||
</a>
|
||||
|
||||
<a class="aui-button" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}/">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-gallery">Открыть</span>
|
||||
Открыть
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-gallery">{_admin_open}</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -40,17 +39,13 @@
|
|||
</table>
|
||||
{else}
|
||||
<center>
|
||||
<p>Наборов подарков нету. Чтобы создать подарок, создайте набор.</p>
|
||||
<p>{_admin_giftsets_none}</p>
|
||||
</center>
|
||||
{/if}
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">«</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">»</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{block title}
|
||||
{if $form->id === 0}
|
||||
Создать набор подарков
|
||||
{_admin_giftsets_create}
|
||||
{else}
|
||||
{$form->languages["master"]->name}
|
||||
{/if}
|
||||
|
@ -14,7 +14,7 @@
|
|||
|
||||
{block content}
|
||||
<form class="aui" method="POST">
|
||||
<h3>Общие настройки</h3>
|
||||
<h3>{_admin_commonsettings}</h3>
|
||||
<fieldset>
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
|
@ -24,37 +24,37 @@
|
|||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name_master">
|
||||
Наименование
|
||||
{_admin_name}
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name_master" name="name_master" value="{$form->languages['master']->name}" />
|
||||
<div class="description">Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
|
||||
<div class="description">{_admin_giftsets_title}</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="description_master">
|
||||
Описание
|
||||
{_admin_description}
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="description_master" name="description_master" value="{$form->languages['master']->description}" />
|
||||
<div class="description">Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
|
||||
<div class="description">{_admin_giftsets_description}</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<h3>Языко-зависимые настройки</h3>
|
||||
<h3>{_admin_langsettings}</h3>
|
||||
<fieldset>
|
||||
{foreach $form->languages as $locale => $data}
|
||||
{continueIf $locale === "master"}
|
||||
|
||||
<div class="field-group">
|
||||
<label for="name_{$locale}">
|
||||
Наименование
|
||||
{_admin_name}
|
||||
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name_{$locale}" name="name_{$locale}" value="{$data->name}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="description_{$locale}">
|
||||
Описание
|
||||
{_admin_description}
|
||||
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="description_{$locale}" name="description_{$locale}" value="{$data->description}" />
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>Набор "{$cat->getName()}"</h1>
|
||||
<h1>{_admin_giftset} "{$cat->getName()}"</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
|
@ -32,11 +32,11 @@
|
|||
<td style="vertical-align: middle;">
|
||||
{$gift->getName()}
|
||||
<span n:if="$gift->isFree()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">
|
||||
бесплатный
|
||||
{_admin_price_free}
|
||||
</span>
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getPrice()} голосов
|
||||
{tr("points_amount", $gift->getPrice())}
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getUsages()} раз
|
||||
|
@ -71,12 +71,9 @@
|
|||
{/if}
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count}
|
||||
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Сводка
|
||||
{_admin_overview_summary}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
Сводка
|
||||
{_admin_overview_summary}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
Да!
|
||||
┬─┬︵/(.□.)╯
|
||||
{/block}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Редактировать {$user->getCanonicalName()}
|
||||
{_edit} {$user->getCanonicalName()}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
|
@ -10,89 +10,70 @@
|
|||
|
||||
{block content}
|
||||
<div class="aui-tabs horizontal-tabs">
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="avatar">
|
||||
Аватарка
|
||||
</label>
|
||||
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$user->getAvatarUrl()}" style="object-fit: cover;"></img>
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="avatar">{_avatar}</label>
|
||||
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$user->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<input class="text medium-field" type="number" id="id" disabled value="{$user->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="guid">
|
||||
GUID
|
||||
</label>
|
||||
<input class="text medium-field" id="guid" disabled value="{$user->getChandlerUser()->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="first_name">
|
||||
Имя
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="first_name" name="first_name" value="{$user->getFirstName()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="last_name">
|
||||
Фамилия
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="last_name" name="last_name" value="{$user->getLastName()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="nickname">
|
||||
Никнейм
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="nickname" name="nickname" value="{$user->getPseudo()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="status">
|
||||
Статус
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="status" name="status" value="{$user->getStatus()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="email">
|
||||
E-Mail
|
||||
</label>
|
||||
<input class="text medium-field" type="email" id="email" name="email" value="{$user->getEmail()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="shortcode">
|
||||
Адрес страницы
|
||||
</label>
|
||||
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$user->getShortCode()}" />
|
||||
</div>
|
||||
<hr>
|
||||
<div class="field-group">
|
||||
<label for="city">
|
||||
Верификация
|
||||
</label>
|
||||
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $user->isVerified()} checked {/if} />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="city">
|
||||
Онлайн статус
|
||||
</label>
|
||||
<select name="online" class="select">
|
||||
<option value="0" {if $user->onlineStatus() > 2}selected{/if}>По-умолчанию</option>
|
||||
<option value="1" {if $user->onlineStatus() == 1}selected{/if}>Инкогнито</option>
|
||||
<option value="2" {if $user->onlineStatus() == 2}selected{/if}>Юзер умер</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="button submit" type="submit" value="Сохранить">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="field-group">
|
||||
<label for="id">ID</label>
|
||||
<input class="text medium-field" type="number" id="id" disabled value="{$user->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="guid">GUID</label>
|
||||
<input class="text medium-field" id="guid" disabled value="{$user->getChandlerUser()->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="registration_ip">{_admin_first_known_ip}</label>
|
||||
<input class="text medium-field" id="guid" disabled value="{$user->getRegistrationIP()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="first_name">{_name}</label>
|
||||
<input class="text medium-field" type="text" id="first_name" name="first_name" value="{$user->getFirstName()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="last_name">{_surname}</label>
|
||||
<input class="text medium-field" type="text" id="last_name" name="last_name" value="{$user->getLastName()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="nickname">{_nickname}</label>
|
||||
<input class="text medium-field" type="text" id="nickname" name="nickname" value="{$user->getPseudo()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="status">{_status}</label>
|
||||
<input class="text medium-field" type="text" id="status" name="status" value="{$user->getStatus()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="email">E-Mail</label>
|
||||
<input class="text medium-field" type="email" id="email" name="email" value="{$user->getEmail()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="shortcode">{_admin_shortcode}</label>
|
||||
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$user->getShortCode()}" />
|
||||
</div>
|
||||
<hr>
|
||||
<div class="field-group">
|
||||
<label for="city">{_admin_verification}</label>
|
||||
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $user->isVerified()} checked {/if} />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="city">{_admin_user_online}</label>
|
||||
<select name="online" class="select">
|
||||
<option value="0" {if $user->onlineStatus() > 2}selected{/if}>{_admin_user_online_default}</option>
|
||||
<option value="1" {if $user->onlineStatus() == 1}selected{/if}>{_admin_user_online_incognite}</option>
|
||||
<option value="2" {if $user->onlineStatus() == 2}selected{/if}>{_admin_user_online_deceased}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="button submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
{extends "@layout.xml"}
|
||||
{var search = true}
|
||||
{var $search = true}
|
||||
|
||||
{block title}
|
||||
Пользователи
|
||||
{_admin_user_search}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
Пиздюки
|
||||
{_users}
|
||||
{/block}
|
||||
|
||||
{block searchTitle}Поиск пиздюков{/block}
|
||||
{block searchTitle}
|
||||
{include title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{var users = iterator_to_array($users)}
|
||||
{var amount = sizeof($users)}
|
||||
{var $users = iterator_to_array($users)}
|
||||
{var $amount = sizeof($users)}
|
||||
|
||||
<table class="aui aui-table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Имя</th>
|
||||
<th>Пол</th>
|
||||
<th>Короткий адрес</th>
|
||||
<th>Дата регистрации</th>
|
||||
<th>Действия</th>
|
||||
<th>ID</th>
|
||||
<th>{_admin_name}</th>
|
||||
<th>{_gender}</th>
|
||||
<th>{_admin_shortcode}</th>
|
||||
<th>{_registration_date}</th>
|
||||
<th>{_admin_actions}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -32,26 +34,24 @@
|
|||
<td>
|
||||
<span class="aui-avatar aui-avatar-xsmall">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$user->getAvatarUrl()}" alt="{$user->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
|
||||
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
|
||||
|
||||
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
|
||||
заблокирован
|
||||
</span>
|
||||
|
||||
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
|
||||
</td>
|
||||
<td>{$user->isFemale() ? "Женский" : "Мужской"}</td>
|
||||
<td>{$user->getShortCode() ?? "(отсутствует)"}</td>
|
||||
<td>{$user->isFemale() ? tr("female") : tr("male")}</td>
|
||||
<td>{$user->getShortCode() ?? "(" . tr("none") . ")"}</td>
|
||||
<td>{$user->getRegistrationTime()}</td>
|
||||
<td>
|
||||
<a class="aui-button aui-button-primary" href="/admin/users/id{$user->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
|
||||
</a>
|
||||
{if $thisUser->getChandlerUser()->can("substitute")->model('openvk\Web\Models\Entities\User')->whichBelongsTo(0)}
|
||||
<a class="aui-button" href="/setSID/{$user->getChandlerUser()->getId()}?hash={rawurlencode($csrfToken)}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-sign-in">Войти как</span>
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-sign-in">{_admin_loginas}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</td>
|
||||
|
@ -60,13 +60,9 @@
|
|||
</table>
|
||||
<br/>
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
|
||||
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
|
||||
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -5,45 +5,36 @@
|
|||
{/block}
|
||||
|
||||
{block heading}
|
||||
{_edit} №{$form->token ?? "undefined"}
|
||||
{_edit} #{$form->token ?? "undefined"}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div style="margin: 8px -8px;" class="aui-tabs horizontal-tabs">
|
||||
<ul class="tabs-menu">
|
||||
<li class="menu-item active-tab">
|
||||
<a href="#info">Информация</a>
|
||||
<a href="#info">{_admin_tab_main}</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="#activators">{_voucher_activators}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tabs-pane active-pane" id="info">
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<label for="id">ID</label>
|
||||
<input class="text long-field" type="number" id="id" name="id" disabled value="{$form->id}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="token">
|
||||
Серийный номер
|
||||
</label>
|
||||
<label for="token">{_admin_voucher_serial}</label>
|
||||
<input class="text long-field" type="text" id="token" name="token" value="{$form->token}" />
|
||||
<div class="description">Номер состоит из 24 символов, если формат неправильный или поле не заполнено, будет назначен автоматически.</div>
|
||||
<div class="description">{_admin_voucher_serial_desc}</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="coins">
|
||||
Количество голосов
|
||||
</label>
|
||||
<label for="coins">{_admin_voucher_coins}</label>
|
||||
<input class="text long-field" type="number" min="0" id="coins" name="coins" value="{$form->coins}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="rating">
|
||||
Количество рейтинга
|
||||
</label>
|
||||
<label for="rating">{_admin_voucher_rating}</label>
|
||||
<input class="text long-field" type="number" min="0" id="rating" name="rating" value="{$form->rating}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
|
@ -55,9 +46,9 @@
|
|||
{/if}
|
||||
</label>
|
||||
<input class="text long-field" type="number" min="-1" id="usages" name="usages" value="{$form->usages}" />
|
||||
<div class="description">Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет Infinity.</div>
|
||||
<div class="description">{_admin_voucher_usages_desc}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
|
@ -66,7 +57,6 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tabs-pane" id="activators">
|
||||
<table rules="none" class="aui aui-table-list">
|
||||
<tbody>
|
||||
|
@ -74,15 +64,13 @@
|
|||
<td>
|
||||
<span class="aui-avatar aui-avatar-xsmall">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$user->getAvatarUrl()}" alt="{$user->getCanonicalName()}" role="presentation" />
|
||||
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" role="presentation" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
|
||||
|
||||
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
|
||||
заблокирован
|
||||
</span>
|
||||
|
||||
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/vouchers/id0">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
|
||||
<h1>{_vouchers}</h1>
|
||||
{/block}
|
||||
|
||||
|
@ -16,13 +16,13 @@
|
|||
<table class="aui aui-table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Серийный номер</th>
|
||||
<th>Голоса</th>
|
||||
<th>Рейгтинг</th>
|
||||
<th>Осталось использований</th>
|
||||
<th>Состояние</th>
|
||||
<th>Действия</th>
|
||||
<th>ID</th>
|
||||
<th>{_admin_voucher_serial}</th>
|
||||
<th>{_coins}</th>
|
||||
<th>{_admin_voucher_rating}</th>
|
||||
<th>{_usages_left}</th>
|
||||
<th>{_admin_voucher_status}</th>
|
||||
<th>{_admin_actions}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -34,28 +34,25 @@
|
|||
<td>{$voucher->getRemainingUsages() === INF ? "∞" : $voucher->getRemainingUsages()}</td>
|
||||
<td>
|
||||
{if $voucher->isExpired()}
|
||||
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">закончился</span>
|
||||
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_voucher_status_closed}</span>
|
||||
{else}
|
||||
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">активен</span>
|
||||
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">{_admin_voucher_status_opened}</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<a class="aui-button aui-button-primary" href="/admin/vouchers/id{$voucher->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count}
|
||||
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<span>{_"gender"}: </span>
|
||||
</td>
|
||||
<td>
|
||||
{var femalePreferred = OPENVK_ROOT_CONF["openvk"]["preferences"]["femaleGenderPriority"]}
|
||||
{var $femalePreferred = OPENVK_ROOT_CONF["openvk"]["preferences"]["femaleGenderPriority"]}
|
||||
<select name="sex" required>
|
||||
<option n:attr="selected => !$femalePreferred" value="male">{_"male"}</option>
|
||||
<option n:attr="selected => $femalePreferred" value="female">{_"female"}</option>
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<span class="nobold">{_group_administrators_list}: </span>
|
||||
</td>
|
||||
<td>
|
||||
{var areAllAdminsHidden = $club->getManagersCount(true) == 0}
|
||||
{var $areAllAdminsHidden = $club->getManagersCount(true) == 0}
|
||||
<input type="radio" name="administrators_list_display" value="0" n:attr="checked => $club->getAdministratorsListDisplay() == 0, disabled => $areAllAdminsHidden" /> {_group_display_only_creator}<br>
|
||||
<input type="radio" name="administrators_list_display" value="1" n:attr="checked => $club->getAdministratorsListDisplay() == 1, disabled => $areAllAdminsHidden" /> {_group_display_all_administrators}<br>
|
||||
<input type="radio" name="administrators_list_display" value="2" n:attr="checked => $club->getAdministratorsListDisplay() == 2" /> {_group_dont_display_administrators_list}<br>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var $Manager = openvk\Web\Models\Entities\Manager::class}
|
||||
{var iterator = $onlyShowManagers ? $managers : $followers}
|
||||
{var count = $paginatorConf->count}
|
||||
{var page = $paginatorConf->page}
|
||||
{var perPage = 6}
|
||||
{var $iterator = $onlyShowManagers ? $managers : $followers}
|
||||
{var $count = $paginatorConf->count}
|
||||
{var $page = $paginatorConf->page}
|
||||
{var $perPage = 6}
|
||||
|
||||
{block title}{_followers} {$club->getCanonicalName()}{/block}
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
|||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x instanceof $Manager ? $x->getUser()->getAvatarURL() : $x->getAvatarURL()}" alt="{$x instanceof $Manager ? $x->getUser()->getCanonicalName() : $x->getCanonicalName()}" width=75 />
|
||||
<img src="{$x instanceof $Manager ? $x->getUser()->getAvatarURL() : $x->getAvatarURL('miniscule')}" alt="{$x instanceof $Manager ? $x->getUser()->getCanonicalName() : $x->getCanonicalName()}" width=75 />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
|
@ -49,8 +49,8 @@
|
|||
{/block}
|
||||
|
||||
{block description}
|
||||
{var user = $x instanceof $Manager ? $x->getUser() : $x}
|
||||
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
|
||||
{var $user = $x instanceof $Manager ? $x->getUser() : $x}
|
||||
{var $manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -106,8 +106,8 @@
|
|||
{/block}
|
||||
|
||||
{block actions}
|
||||
{var user = $x instanceof $Manager ? $x->getUser() : $x}
|
||||
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
|
||||
{var $user = $x instanceof $Manager ? $x->getUser() : $x}
|
||||
{var $manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
|
||||
{if $club->canBeModifiedBy($thisUser ?? NULL)}
|
||||
<a class="profile_link" href="/club{$club->getId()}/setAdmin?user={$user->getId()}&hash={rawurlencode($csrfToken)}" n:if="$club->getOwner()->getId() !== $user->getId()">
|
||||
{if $manager}
|
||||
|
@ -140,4 +140,4 @@
|
|||
</a>
|
||||
{/if}
|
||||
{/if}
|
||||
{/block}
|
||||
{/block}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
{block content}
|
||||
<div class="left_big_block">
|
||||
<div n:if="!is_null($alert = $club->getAlert())" class="group-alert">{strpos($alert, "@") === 0 ? tr(substr($alert, 1)) : $alert}</div>
|
||||
|
||||
<div class="content_title_expanded" onclick="hidePanel(this);">
|
||||
{_"information"}
|
||||
</div>
|
||||
|
@ -41,7 +43,7 @@
|
|||
</table>
|
||||
</div>
|
||||
<div n:if="$club->getFollowersCount() > 0">
|
||||
{var followersCount = $club->getFollowersCount()}
|
||||
{var $followersCount = $club->getFollowersCount()}
|
||||
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
|
||||
{_participants}
|
||||
|
@ -57,7 +59,7 @@
|
|||
<div class="cl_element" n:foreach="$club->getFollowers(1) as $follower">
|
||||
<div class="cl_avatar">
|
||||
<a href="{$follower->getURL()}">
|
||||
<img class="ava" src="{$follower->getAvatarUrl()}" />
|
||||
<img class="ava" src="{$follower->getAvatarUrl('miniscule')}" />
|
||||
</a>
|
||||
</div>
|
||||
<a href="{$follower->getURL()}" class="cl_name">
|
||||
|
@ -91,10 +93,10 @@
|
|||
{presenter "openvk!Wall->wallEmbedded", -$club->getId()}
|
||||
</div>
|
||||
<div class="right_small_block">
|
||||
{var avatarPhoto = $club->getAvatarPhoto()}
|
||||
{var avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())}
|
||||
{var $avatarPhoto = $club->getAvatarPhoto()}
|
||||
{var $avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())}
|
||||
<a href="{$avatarLink|nocheck}">
|
||||
<img src="{$club->getAvatarUrl()}" style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
|
||||
<img src="{$club->getAvatarUrl('normal')}" style="width: 100%; image-rendering: -webkit-optimize-contrast;" />
|
||||
</a>
|
||||
<div n:ifset="$thisUser" id="profile_links">
|
||||
{if $club->canBeModifiedBy($thisUser)}
|
||||
|
@ -132,7 +134,7 @@
|
|||
{_"creator"}
|
||||
</div>
|
||||
<div class="avatar-list-item" style="padding: 8px;">
|
||||
{var author = $club->getOwner()}
|
||||
{var $author = $club->getOwner()}
|
||||
<div class="avatar">
|
||||
<a href="{$author->getURL()}">
|
||||
<img class="ava" src="{$author->getAvatarUrl()}" />
|
||||
|
@ -149,7 +151,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div n:if="$club->getAdministratorsListDisplay() == 1">
|
||||
{var managersCount = $club->getManagersCount(true)}
|
||||
{var $managersCount = $club->getManagersCount(true)}
|
||||
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$managersCount});">
|
||||
{_"administrators"}
|
||||
|
@ -163,7 +165,7 @@
|
|||
</div>
|
||||
<div class="avatar-list">
|
||||
<div class="avatar-list-item" n:if="!$club->isOwnerHidden()">
|
||||
{var author = $club->getOwner()}
|
||||
{var $author = $club->getOwner()}
|
||||
<div class="avatar">
|
||||
<a href="{$author->getURL()}">
|
||||
<img class="ava" src="{$author->getAvatarUrl()}" />
|
||||
|
@ -175,7 +177,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="avatar-list-item" n:foreach="$club->getManagers(1, true) as $manager">
|
||||
{var user = $manager->getUser()}
|
||||
{var $user = $manager->getUser()}
|
||||
<div class="avatar">
|
||||
<a href="{$user->getURL()}">
|
||||
<img height="32" class="ava" src="{$user->getAvatarUrl()}" />
|
||||
|
@ -203,7 +205,7 @@
|
|||
<div style="padding: 5px;">
|
||||
<div class="ovk-album" style="display: inline-block;" n:foreach="$albums as $album">
|
||||
<div style="text-align: center;float: left;height: 54pt;width: 100px;">
|
||||
{var cover = $album->getCoverPhoto()}
|
||||
{var $cover = $album->getCoverPhoto()}
|
||||
|
||||
<img
|
||||
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURL()}"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<a href="/im">{_my_messages}</a> »
|
||||
<a href="{$correspondent->getURL()}">{$correspondent->getCanonicalName()}</a>
|
||||
<div n:if="($online = $correspondent->getOnline()->timestamp()) + 2505600 > time()" style="float: right;">
|
||||
{var diff = date_diff(date_create(), date_create('@' . $online))}
|
||||
{var $diff = date_diff(date_create(), date_create('@' . $online))}
|
||||
{if 5 >= $diff->i}
|
||||
<span><b>{_online}</b></span>
|
||||
{else}
|
||||
|
@ -44,7 +44,7 @@
|
|||
</div>
|
||||
<div class="messenger-app--input">
|
||||
{if $correspondent->getId() === $thisUser->getId() || $correspondent->getPrivacyPermission('messages.write', $thisUser)}
|
||||
<img class="ava" src="{$thisUser->getAvatarUrl()}" alt="{$thisUser->getCanonicalName()}" />
|
||||
<img class="ava" src="{$thisUser->getAvatarUrl('miniscule')}" alt="{$thisUser->getCanonicalName()}" />
|
||||
<div class="messenger-app--input---messagebox">
|
||||
<textarea
|
||||
data-bind="value: messageContent, event: { keydown: onTextareaKeyPress }"
|
||||
|
@ -52,7 +52,7 @@
|
|||
placeholder="Введите сообщение"></textarea>
|
||||
<button class="button" data-bind="click: sendMessage">Отправить</button>
|
||||
</div>
|
||||
<img class="ava" src="{$correspondent->getAvatarUrl()}" alt="{$correspondent->getCanonicalName()}" />
|
||||
<img class="ava" src="{$correspondent->getAvatarUrl('miniscule')}" alt="{$correspondent->getCanonicalName()}" />
|
||||
{else}
|
||||
<div class="blocked" data-localized-text="Вы не можете писать сообщения {$correspondent->getCanonicalName()} из-за его настроек приватности."></div>
|
||||
{/if}
|
||||
|
|
|
@ -21,11 +21,11 @@
|
|||
<div n:foreach="$corresps as $coresp"
|
||||
class="crp-entry"
|
||||
onmousedown="window.location.href = {$coresp->getURL()};" >
|
||||
{var recipient = $coresp->getCorrespondents()[1]}
|
||||
{var lastMsg = $coresp->getPreviewMessage()}
|
||||
{var $recipient = $coresp->getCorrespondents()[1]}
|
||||
{var $lastMsg = $coresp->getPreviewMessage()}
|
||||
|
||||
<div class="crp-entry--image">
|
||||
<img src="{$recipient->getAvatarURL()}"
|
||||
<img src="{$recipient->getAvatarURL('miniscule')}"
|
||||
alt="Фотография пользователя" />
|
||||
</div>
|
||||
<div class="crp-entry--info">
|
||||
|
@ -33,10 +33,10 @@
|
|||
<span>{$lastMsg->getSendTime()->format("%e %B %G" . tr("time_at_sp") . "%X")}</span>
|
||||
</div>
|
||||
<div n:class="crp-entry--message, $lastMsg->getUnreadState() ? unread">
|
||||
{var _author = $lastMsg->getSender()}
|
||||
{var $_author = $lastMsg->getSender()}
|
||||
|
||||
<div class="crp-entry--message---av" n:if="$_author->getId() === $thisUser->getId()">
|
||||
<img src="{$_author->getAvatarURL()}"
|
||||
<img src="{$_author->getAvatarURL('miniscule')}"
|
||||
alt="Фотография пользователя" />
|
||||
</div>
|
||||
<div class="crp-entry--message---text">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{block title}{_edit_note}{/block}
|
||||
|
||||
{block header}
|
||||
{var author = $note->getOwner()}
|
||||
{var $author = $note->getOwner()}
|
||||
<a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
|
||||
»
|
||||
<a href="/notes{$author->getId()}">{_notes}</a>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = iterator_to_array($notes)}
|
||||
{var page = $paginatorConf->page}
|
||||
{var $iterator = iterator_to_array($notes)}
|
||||
{var $page = $paginatorConf->page}
|
||||
|
||||
{block title}{_notes}{/block}
|
||||
|
||||
|
@ -34,15 +34,41 @@
|
|||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block specpage}
|
||||
<style>
|
||||
#userContent img {
|
||||
max-width: 245pt;
|
||||
max-height: 200pt;
|
||||
}
|
||||
|
||||
#userContent blockquote {
|
||||
background-color: #f3f3f3;
|
||||
border-bottom: 5px solid #969696;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
#userContent cite {
|
||||
margin-top: 1em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#userContent cite::before {
|
||||
content: "— ";
|
||||
}
|
||||
|
||||
#userContent .underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container_gray" style="background: white; border-top: none;">
|
||||
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
{var $data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
{if sizeof($data) > 0}
|
||||
|
||||
<div n:foreach="$data as $dat">
|
||||
<div class="profile_thumb">
|
||||
<a href="{$owner->getURL()}">
|
||||
<img src="{$owner->getAvatarUrl()}" style="width: 50px;">
|
||||
<img src="{$owner->getAvatarUrl('miniscule')}" style="width: 50px;">
|
||||
</a>
|
||||
</div>
|
||||
<article class="note_body" id="userContent" style="width: 540px; display: inline-block; margin-bottom: 35px;">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{block title}{$note->getName()}{/block}
|
||||
|
||||
{block header}
|
||||
{var author = $note->getOwner()}
|
||||
{var $author = $note->getOwner()}
|
||||
<a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
|
||||
»
|
||||
<a href="/notes{$author->getId()}">{_notes}</a>
|
||||
|
@ -12,7 +12,7 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
{var author = $note->getOwner()}
|
||||
{var $author = $note->getOwner()}
|
||||
<style>
|
||||
#userContent img {
|
||||
max-width: 245pt;
|
||||
|
@ -33,6 +33,10 @@
|
|||
#userContent cite::before {
|
||||
content: "— ";
|
||||
}
|
||||
|
||||
#userContent .underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
<article id="userContent" style="margin: 10px 10px 0;">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var sorting = false}
|
||||
{var $sorting = false}
|
||||
|
||||
{block title}
|
||||
{_feedback}
|
||||
|
@ -26,7 +26,7 @@
|
|||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getModel(1)->getAvatarUrl()}" width=64 />
|
||||
<img src="{$x->getModel(1)->getAvatarUrl('miniscule')}" width=64 />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{block title}Альбом {$album->getName()}{/block}
|
||||
|
||||
{block header}
|
||||
{var isClub = ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)}
|
||||
{var $isClub = ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)}
|
||||
|
||||
<a href="{$album->getOwner()->getURL()}">
|
||||
{$album->getOwner()->getCanonicalName()}
|
||||
|
|
|
@ -1,16 +1,35 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = iterator_to_array($albums)}
|
||||
{var page = $paginatorConf->page}
|
||||
{var $iterator = iterator_to_array($albums)}
|
||||
{var $page = $paginatorConf->page}
|
||||
|
||||
{block title}{_"albums"} {$owner->getCanonicalName()}{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
|
||||
» {_"albums"}
|
||||
|
||||
<div n:if="$canEdit" style="float: right;">
|
||||
{var isClub = ($owner instanceof openvk\Web\Models\Entities\Club)}
|
||||
<a href="/albums/create{$isClub ? '?gpid=' . $owner->getId() : ''}">{_"create_album"}</a>
|
||||
{if isset($thisUser) && $thisUser->getId() == $owner->getId()}
|
||||
{_my_photos}
|
||||
{else}
|
||||
<a href="{$owner->getURL()}">
|
||||
{$owner->getCanonicalName()}</a>
|
||||
»
|
||||
{_albums}
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block size}
|
||||
<div style="padding-bottom: 0px; padding-top: 0;" class="summaryBar">
|
||||
<div class="summary">
|
||||
{if !is_null($thisUser) && $owner->getId() === $thisUser->getId()}
|
||||
{tr("albums_list", $count)}
|
||||
{else}
|
||||
{tr("albums", $count)}
|
||||
{/if}
|
||||
|
||||
<span n:if="$canEdit" style="float: right;">
|
||||
|
|
||||
{var $isClub = ($owner instanceof \openvk\Web\Models\Entities\Club)}
|
||||
<a href="/albums/create{$isClub ? '?gpid=' . $owner->getId() : ''}">{_create_album}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
|
@ -25,8 +44,8 @@
|
|||
{/block}
|
||||
|
||||
{block preview}
|
||||
{var cover = $x->getCoverPhoto()}
|
||||
{var preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURL()}
|
||||
{var $cover = $x->getCoverPhoto()}
|
||||
{var $preview = is_null($cover) ? "/assets/packages/static/openvk/img/camera_200.png" : $cover->getURLBySizeId("normal")}
|
||||
|
||||
<a href="/album{$x->getPrettyId()}">
|
||||
<img src="{$preview}" alt="{$x->getName()}" style="height: 130px; width: 170px; object-fit: cover" />
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<tr>
|
||||
{if $comment->getUType() === 0}
|
||||
<td width="54" valign="top">
|
||||
<img src="{$comment->getUser()->getAvatarUrl()}" width="50" />
|
||||
<img src="{$comment->getUser()->getAvatarUrl('miniscule')}" width="50" />
|
||||
</td>
|
||||
{else}
|
||||
<td width="54" valign="top">
|
||||
|
@ -70,7 +70,7 @@
|
|||
{if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
||||
<a href="{$comment->getUser()->getURL()}">
|
||||
<span class="nobold">
|
||||
{var lastName = $comment->getUser()->getLastName()}
|
||||
{var $lastName = $comment->getUser()->getLastName()}
|
||||
{if empty(trim($lastName))}
|
||||
({$comment->getUser()->getFirstName()})
|
||||
{else}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
{var isMain = $mode === 'faq'}
|
||||
{var isNew = $mode === 'new'}
|
||||
{var isList = $mode === 'list'}
|
||||
{var $isMain = $mode === 'faq'}
|
||||
{var $isNew = $mode === 'new'}
|
||||
{var $isList = $mode === 'list'}
|
||||
|
||||
{if $thisUser}
|
||||
<div class="tabs">
|
||||
|
@ -26,16 +26,26 @@
|
|||
<br />
|
||||
|
||||
{if $isNew}
|
||||
<div class="new">
|
||||
<form action="/support" method="post" style="margin:0;">
|
||||
<center>
|
||||
<input name="name" style="width: 80%; resize: vertical;" placeholder="{_support_new_title}" /><br /><br />
|
||||
<textarea name="text" style="width: 80%; resize: vertical;" placeholder="{_support_new_content}"></textarea><br /><br />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_write}" class="button" style="margin-left: 70%;" /><br /><br />
|
||||
</center>
|
||||
</form>
|
||||
</div>
|
||||
{if !is_null($banReason)}
|
||||
<center>
|
||||
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_'banned_alt'}" style="width: 20%;" />
|
||||
</center>
|
||||
<p>
|
||||
{tr("banned_in_support_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/>
|
||||
{tr("banned_in_support_2", htmlentities($banReason))|noescape}
|
||||
</p>
|
||||
{else}
|
||||
<div class="new">
|
||||
<form action="/support" method="post" style="margin:0;">
|
||||
<center>
|
||||
<input name="name" style="width: 80%; resize: vertical;" placeholder="{_support_new_title}" /><br /><br />
|
||||
<textarea name="text" style="width: 80%; resize: vertical;" placeholder="{_support_new_content}"></textarea><br /><br />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_write}" class="button" style="margin-left: 70%;" /><br /><br />
|
||||
</center>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
{/block}
|
||||
|
||||
{block description}
|
||||
{var author = $x->getUser()}
|
||||
{var $author = $x->getUser()}
|
||||
|
||||
{ovk_proc_strtr($x->getContext(), 50)}<br/>
|
||||
<span class="nobold">{_author}: </span> <a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<tr>
|
||||
{if $comment->getUType() === 0}
|
||||
<td width="54" valign="top">
|
||||
<img src="{$comment->getUser()->getAvatarUrl()}" width="50" />
|
||||
<img src="{$comment->getUser()->getAvatarUrl('miniscule')}" width="50" />
|
||||
</td>
|
||||
{else}
|
||||
<td width="54" valign="top">
|
||||
|
@ -109,7 +109,7 @@
|
|||
|
||||
{if $comment->getUType() === 1}
|
||||
<div class="post-menu">
|
||||
{var isLikedByUser = $comment->isLikedByUser()}
|
||||
{var $isLikedByUser = $comment->isLikedByUser()}
|
||||
<strong id="markText-{$comment->getId()}">
|
||||
{if !is_null($isLikedByUser)}
|
||||
{if $comment->isLikedByUser()}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = iterator_to_array($topics)}
|
||||
{var page = $paginatorConf->page}
|
||||
{var $iterator = iterator_to_array($topics)}
|
||||
{var $page = $paginatorConf->page}
|
||||
|
||||
{block title}{_discussions} {$club->getCanonicalName()}{/block}
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
|||
<div style="float: left;">
|
||||
{tr("messages", $x->getCommentsCount())}
|
||||
</div>
|
||||
{var lastComment = $x->getLastComment()}
|
||||
{var $lastComment = $x->getLastComment()}
|
||||
<div n:if="$lastComment" class="avatar-list-item" style="float: right;">
|
||||
<div class="avatar">
|
||||
<a href="{$lastComment->getOwner()->getURL()}">
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
{block content}
|
||||
|
||||
{var isMain = $mode === 'main'}
|
||||
{var isContacts = $mode === 'contacts'}
|
||||
{var isInterests = $mode === 'interests'}
|
||||
{var isAvatar = $mode === 'avatar'}
|
||||
{var $isMain = $mode === 'main'}
|
||||
{var $isContacts = $mode === 'contacts'}
|
||||
{var $isInterests = $mode === 'interests'}
|
||||
{var $isAvatar = $mode === 'avatar'}
|
||||
<div n:if="$user->hasPendingNumberChange()" class="msg">
|
||||
<b>Подтверждение номера телефона</b><br/>
|
||||
Введите код для подтверждения смены номера: <a href="/edit/verify_phone">ввести код</a>.
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var perPage = 6} {* Why 6? Check User::_abstractRelationGenerator *}
|
||||
{var $perPage = 6} {* Why 6? Check User::_abstractRelationGenerator *}
|
||||
|
||||
{var act = $_GET["act"] ?? "friends"}
|
||||
{var $act = $_GET["act"] ?? "friends"}
|
||||
|
||||
{if $act == "incoming"}
|
||||
{var iterator = iterator_to_array($user->getFollowers($page))}
|
||||
{var count = $user->getFollowersCount()}
|
||||
{var $iterator = iterator_to_array($user->getFollowers($page))}
|
||||
{var $count = $user->getFollowersCount()}
|
||||
{elseif $act == "outcoming"}
|
||||
{var iterator = iterator_to_array($user->getSubscriptions($page))}
|
||||
{var count = $user->getSubscriptionsCount()}
|
||||
{var $iterator = iterator_to_array($user->getSubscriptions($page))}
|
||||
{var $count = $user->getSubscriptionsCount()}
|
||||
{else}
|
||||
{var iterator = iterator_to_array($user->getFriends($page))}
|
||||
{var count = $user->getFriendsCount()}
|
||||
{var $iterator = iterator_to_array($user->getFriends($page))}
|
||||
{var $count = $user->getFriendsCount()}
|
||||
{/if}
|
||||
|
||||
{block title}
|
||||
|
@ -25,13 +25,17 @@
|
|||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
{if $act == "incoming"}
|
||||
{_"incoming_req"}
|
||||
{elseif $act == "outcoming"}
|
||||
{_"outcoming_req"}
|
||||
{if isset($thisUser) && $thisUser->getId() == $user->getId()}
|
||||
{_my_friends}
|
||||
{else}
|
||||
{_"friends"}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
{if $act == "incoming"}
|
||||
{_"incoming_req"}
|
||||
{elseif $act == "outcoming"}
|
||||
{_"outcoming_req"}
|
||||
{else}
|
||||
{_"friends"}
|
||||
{/if}
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
|
@ -39,11 +43,38 @@
|
|||
<div n:attr="id => ($act === 'friends' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'friends' ? 'act_tab_a' : 'ki')" href="?">{_friends}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($act === 'incoming' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'incoming' ? 'act_tab_a' : 'ki')" href="?act=incoming">{_incoming_req}</a>
|
||||
<div n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'incoming' || $act === 'outcoming' ? 'act_tab_a' : 'ki')" href="?act=incoming">{_req}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($act === 'outcoming' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'outcoming' ? 'act_tab_a' : 'ki')" href="?act=outcoming">{_outcoming_req}</a>
|
||||
{/block}
|
||||
|
||||
{block size}
|
||||
<div n:if="$act === 'incoming' || $act === 'outcoming'" class="mb_tabs">
|
||||
<div n:attr="id => ($act === 'incoming' ? 'active' : 'ki')" class="mb_tab">
|
||||
<div>
|
||||
<a href="?act=incoming">{_incoming_req}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div n:attr="id => ($act === 'outcoming' ? 'active' : 'ki')" class="mb_tab">
|
||||
<div>
|
||||
<a href="?act=outcoming">{_outcoming_req}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding-bottom: 0px;" class="summaryBar">
|
||||
<div class="summary">
|
||||
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
|
||||
{if $act == "incoming"}
|
||||
{tr("req", $count)}
|
||||
{elseif $act == "outcoming"}
|
||||
{tr("req", $count)}
|
||||
{else}
|
||||
{tr("friends_list", $count)}
|
||||
{/if}
|
||||
{else}
|
||||
{tr("friends", $count)}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
|
@ -54,7 +85,7 @@
|
|||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
|
||||
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография пользователя" />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
|
@ -82,7 +113,7 @@
|
|||
|
||||
{block actions}
|
||||
{if $x->getId() !== $thisUser->getId()}
|
||||
{var subStatus = $x->getSubscriptionStatus($thisUser)}
|
||||
{var $subStatus = $x->getSubscriptionStatus($thisUser)}
|
||||
{if $subStatus === 0}
|
||||
<form action="/setSub/user" method="post" class="profile_link_form">
|
||||
<input type="hidden" name="act" value="add" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = $user->getClubs($page, $admin)}
|
||||
{var count = $user->getClubCount($admin)}
|
||||
{var $iterator = $user->getClubs($page, $admin)}
|
||||
{var $count = $user->getClubCount($admin)}
|
||||
|
||||
{block title}
|
||||
{_groups}
|
||||
|
@ -32,7 +32,7 @@
|
|||
{/block}
|
||||
|
||||
{block size}
|
||||
<div n:if="!is_null($thisUser) && $user->getId() === $thisUser->getId()" style="padding-bottom: 0px; border-bottom: 0;" class="summaryBar">
|
||||
<div style="padding-bottom: 0px;" class="summaryBar">
|
||||
<div class="summary">
|
||||
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
|
||||
{tr("groups_list", $thisUser->getClubCount())}
|
||||
|
@ -48,12 +48,12 @@
|
|||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
|
||||
<img src="{$x->getAvatarUrl('miniscule')}" width="75" alt="Фотография группы" />
|
||||
{/block}
|
||||
|
||||
{block name}{/block}
|
||||
|
||||
{block infoTable}
|
||||
{block infotable}
|
||||
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -73,26 +73,28 @@
|
|||
{/block}
|
||||
|
||||
{block actions}
|
||||
{var clubPinned = $thisUser->isClubPinned($x)}
|
||||
{var $clubPinned = $thisUser->isClubPinned($x)}
|
||||
{if $x->canBeModifiedBy($thisUser ?? NULL)}
|
||||
<a style="width: 140px;" class="profile_link" href="{$x->getURL()}">
|
||||
{_check_community}
|
||||
</a>
|
||||
{if ($clubPinned || $thisUser->getPinnedClubCount() <= 10)}
|
||||
<a style="width: 140px;" class="profile_link" href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
|
||||
{if $clubPinned}
|
||||
{_remove_from_left_menu}
|
||||
{else}
|
||||
{_add_to_left_menu}
|
||||
{/if}
|
||||
<div class="navigation" style="width: 140px;">
|
||||
<a class="link" href="{$x->getURL()}">
|
||||
{_check_community}
|
||||
</a>
|
||||
{/if}
|
||||
<form action="/setSub/club" method="post">
|
||||
<input type="hidden" name="act" value="rem" />
|
||||
<input type="hidden" name="id" value="{$x->getId()}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input style="width: 140px; text-transform: lowercase;" type="submit" id="profile_link" value="{_leave_community}" />
|
||||
</form>
|
||||
{if ($clubPinned || $thisUser->getPinnedClubCount() <= 10)}
|
||||
<a class="link" href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
|
||||
{if $clubPinned}
|
||||
{_remove_from_left_menu}
|
||||
{else}
|
||||
{_add_to_left_menu}
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
<form action="/setSub/club" method="post">
|
||||
<input type="hidden" name="act" value="rem" />
|
||||
<input type="hidden" name="id" value="{$x->getId()}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input style="text-transform: lowercase; width: 100%;" class="link" type="submit" value="{_"leave_community"}" />
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
{block content}
|
||||
|
||||
{var isMain = $mode === 'main'}
|
||||
{var isPrivacy = $mode === 'privacy'}
|
||||
{var isFinance = $mode === 'finance'}
|
||||
{var isFinanceTU = $mode === 'finance.top-up'}
|
||||
{var isInterface = $mode === 'interface'}
|
||||
{var $isMain = $mode === 'main'}
|
||||
{var $isPrivacy = $mode === 'privacy'}
|
||||
{var $isFinance = $mode === 'finance'}
|
||||
{var $isFinanceTU = $mode === 'finance.top-up'}
|
||||
{var $isInterface = $mode === 'interface'}
|
||||
|
||||
<div class="tabs">
|
||||
<div n:attr="id => ($isMain ? 'activetabs' : 'ki')" class="tab">
|
||||
|
@ -347,16 +347,22 @@
|
|||
<center>{tr("also_you_can_transfer_points", $thisUser->getCoins(), rawurlencode($csrfToken))|noescape}</center>
|
||||
</div>
|
||||
<div style="width: 22%; float: right;">
|
||||
<p style="margin: 0; font-size: medium; text-align: center;">
|
||||
<b>
|
||||
{_on_your_account}<br/>
|
||||
<span style="font-size: 50px;">{$thisUser->getCoins()}</span><br/>
|
||||
{_points_count}<br/><br/>
|
||||
<small><a href="?act=finance.top-up">[{_have_voucher}?]</a></small>
|
||||
</b>
|
||||
<div style="margin: 0; font-size: medium; text-align: center; font-weight: 900;">
|
||||
{_on_your_account}
|
||||
<div style="width: 100%; height: 60px; font-weight: 100;" id="balance">{$thisUser->getCoins()}</div>
|
||||
{_points_count}<br/>
|
||||
<small><a href="?act=finance.top-up">[{_have_voucher}?]</a></small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{script "js/node_modules/textfit/textFit.min.js"}
|
||||
<script>
|
||||
let balance = document.querySelector("#balance");
|
||||
|
||||
balance.style.width = Math.ceil(balance.parentNode.getBoundingClientRect().width) + "px";
|
||||
textFit(balance, { alignVert: true });
|
||||
</script>
|
||||
|
||||
{elseif $isFinanceTU}
|
||||
|
||||
<p>{_voucher_explanation} {_voucher_explanation_ex}</p>
|
||||
|
@ -407,7 +413,7 @@
|
|||
</td>
|
||||
<td>
|
||||
<select name="style_avatar">
|
||||
<option value="0" {if $user->getStyleAvatar() == 0}selected{/if}>{_"default"}</option>
|
||||
<option value="0" {if $user->getStyleAvatar() == 0}selected{/if}>{_"arbitrary_avatars"} ({_"default"})</option>
|
||||
<option value="1" {if $user->getStyleAvatar() == 1}selected{/if}>{_"cut"}</option>
|
||||
<option value="2" {if $user->getStyleAvatar() == 2}selected{/if}>{_"round_avatars"}</option>
|
||||
</select>
|
||||
|
|
|
@ -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,11 +93,21 @@
|
|||
{_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>
|
||||
|
||||
{var subStatus = $user->getSubscriptionStatus($thisUser)}
|
||||
{var $subStatus = $user->getSubscriptionStatus($thisUser)}
|
||||
{if $subStatus === 0}
|
||||
<form action="/setSub/user" method="post" class="profile_link_form">
|
||||
<input type="hidden" name="act" value="add" />
|
||||
|
@ -131,7 +141,7 @@
|
|||
<a n:if="$user->getFollowersCount() > 0" href="/friends{$user->getId()}?act=incoming" class="profile_link">{tr("followers", $user->getFollowersCount())}</a>
|
||||
</div>
|
||||
<div n:if="isset($thisUser) && !$thisUser->prefersNotToSeeRating()" class="profile-hints">
|
||||
{var completeness = $user->getProfileCompletenessReport()}
|
||||
{var $completeness = $user->getProfileCompletenessReport()}
|
||||
|
||||
<div n:class="completeness-gauge, $completeness->total >= 100 ? completeness-gauge-gold">
|
||||
<div style="width: {$completeness->percent}%"></div>
|
||||
|
@ -164,7 +174,7 @@
|
|||
</div>
|
||||
<br />
|
||||
<div n:if="$user->getFriendsCount() > 0 && $user->getPrivacyPermission('friends.read', $thisUser ?? NULL)">
|
||||
{var friendCount = $user->getFriendsCount()}
|
||||
{var $friendCount = $user->getFriendsCount()}
|
||||
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$friendCount});">
|
||||
{_"friends"}
|
||||
|
@ -180,7 +190,7 @@
|
|||
<div class="cl_element" n:foreach="$user->getFriends(1) as $friend">
|
||||
<div class="cl_avatar">
|
||||
<a href="{$friend->getURL()}">
|
||||
<img class="ava" src="{$friend->getAvatarUrl()}" />
|
||||
<img class="ava" src="{$friend->getAvatarUrl('miniscule')}" />
|
||||
</a>
|
||||
</div>
|
||||
<a href="{$friend->getURL()}" class="cl_name">
|
||||
|
@ -205,10 +215,10 @@
|
|||
<div style="padding: 5px;">
|
||||
<div class="ovk-album" style="display: inline-block;" n:foreach="$albums as $album">
|
||||
<div style="text-align: center;float: left;height: 54pt;width: 100px;">
|
||||
{var cover = $album->getCoverPhoto()}
|
||||
{var $cover = $album->getCoverPhoto()}
|
||||
|
||||
<img
|
||||
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURL()}"
|
||||
src="{is_null($cover)?'/assets/packages/static/openvk/img/camera_200.png':$cover->getURLBySizeId('small')}"
|
||||
style="max-width: 80px; max-height: 54pt;" />
|
||||
</div>
|
||||
<div style="overflow: hidden; overflow-wrap: break-word;">
|
||||
|
@ -274,7 +284,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div n:if="$user->getClubCount() > 0 && $user->getPrivacyPermission('groups.read', $thisUser ?? NULL)">
|
||||
{var clubsCount = $user->getClubCount()}
|
||||
{var $clubsCount = $user->getClubCount()}
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$clubsCount})">
|
||||
{_"groups"}
|
||||
</div>
|
||||
|
@ -293,7 +303,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div n:if="$user->getMeetingCount() > 0 && $user->getPrivacyPermission('groups.read', $thisUser ?? NULL)">
|
||||
{var meetingCount = $user->getMeetingCount()}
|
||||
{var $meetingCount = $user->getMeetingCount()}
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$meetingCount})">
|
||||
{_meetings}
|
||||
</div>
|
||||
|
@ -317,7 +327,7 @@
|
|||
<div class="right_big_block">
|
||||
<div class="page_info">
|
||||
<div n:if="!is_null($alert = $user->getAlert())" class="user-alert">{strpos($alert, "@") === 0 ? tr(substr($alert, 1)) : $alert}</div>
|
||||
{var thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
|
||||
{var $thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
|
||||
<div n:if="$thatIsThisUser" class="page_status_popup" id="status_editor" style="display: none;">
|
||||
<form name="status_popup_form" onsubmit="changeStatus(); return false;">
|
||||
<div style="margin-bottom: 10px;">
|
||||
|
@ -485,7 +495,7 @@
|
|||
</div>
|
||||
<div class="content_list long">
|
||||
<div class="cl_element" style="width: 25%;" n:foreach="$user->getGifts(1, 4) as $giftDescriptor">
|
||||
{var hideInfo = !is_null($thisUser) ? ($giftDescriptor->anon ? $thisUser->getId() !== $user->getId() : false) : false}
|
||||
{var $hideInfo = !is_null($thisUser) ? ($giftDescriptor->anon ? $thisUser->getId() !== $user->getId() : false) : false}
|
||||
<div class="cl_avatar">
|
||||
<a href="{$hideInfo ? 'javascript:false' : $giftDescriptor->sender->getURL()}">
|
||||
<img style="width: 70px; max-height: 70px;"
|
||||
|
@ -512,7 +522,7 @@
|
|||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/admin/ban/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
|
||||
xhr.onload = (function() {
|
||||
if(xhr.responseText.indexOf("reason") === -1)
|
||||
if(xhr.responseText.indexOf("success") === -1)
|
||||
MessageBox("Ошибка", "Не удалось забанить пользователя...", ["OK"], [Function.noop]);
|
||||
else
|
||||
MessageBox("Операция успешна", "Пользователь заблокирован", ["OK"], [Function.noop]);
|
||||
|
@ -526,7 +536,7 @@
|
|||
function warnUser() {
|
||||
uBanMsgTxt = "Вы собираетесь предупредить пользователя " + {$user->getCanonicalName()} + ".";
|
||||
uBanMsgTxt += "<br/>Мы отправим уведомление пользователю в личные сообщения от имени аккаунта администратора.";
|
||||
uBanMsgTxt += "<br/><br/><b>Текст предупреждения</b>: <input type='text' id='uWarnMsgInput' placeholder='придумайте что-нибудь крутое' />"
|
||||
uBanMsgTxt += "<br/><br/><b>Текст предупреждения</b>: <input type='text' id='uWarnMsgInput' placeholder='придумайте что-нибудь крутое' />";
|
||||
|
||||
MessageBox("Выдать предупреждение " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
|
||||
(function() {
|
||||
|
@ -546,6 +556,51 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<script n:if="isset($thisUser) && $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)">
|
||||
{if $user->isBannedInSupport()}
|
||||
function toggleBanInSupport() {
|
||||
uBanMsgTxt = "Вы собираетесь разблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
|
||||
uBanMsgTxt += "<br/>Сейчас он заблокирован по причине <strong>" + {$user->getBanInSupportReason()} + "</strong>.";
|
||||
|
||||
MessageBox("Разблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
|
||||
(function() {
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/admin/support/unban/" + {$user->getId()} + "?hash=" + {rawurlencode($csrfToken)}, true);
|
||||
xhr.onload = (function() {
|
||||
if(xhr.responseText.indexOf("success") === -1)
|
||||
MessageBox("Ошибка", "Не удалось разблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
|
||||
else
|
||||
MessageBox("Операция успешна", "Пользователь разблокирован в поддержке", ["OK"], [Function.noop]);
|
||||
});
|
||||
xhr.send(null);
|
||||
}),
|
||||
Function.noop
|
||||
]);
|
||||
}
|
||||
{else}
|
||||
function toggleBanInSupport() {
|
||||
uBanMsgTxt = "Вы собираетесь заблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
|
||||
uBanMsgTxt += "<br/><br/><b>Причина бана</b>: <input type='text' id='uBanMsgInput' placeholder='придумайте что-нибудь крутое' />";
|
||||
|
||||
MessageBox("Заблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
|
||||
(function() {
|
||||
res = document.querySelector("#uBanMsgInput").value;
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/admin/support/ban/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
|
||||
xhr.onload = (function() {
|
||||
if(xhr.responseText.indexOf("success") === -1)
|
||||
MessageBox("Ошибка", "Не удалось заблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
|
||||
else
|
||||
MessageBox("Операция успешна", "Пользователь заблокирован в поддержке", ["OK"], [Function.noop]);
|
||||
});
|
||||
xhr.send(null);
|
||||
}),
|
||||
Function.noop
|
||||
]);
|
||||
}
|
||||
{/if}
|
||||
</script>
|
||||
|
||||
<script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()" n:syntax="off">
|
||||
function setStatusEditorShown(shown) {
|
||||
document.getElementById("status_editor").style.display = shown ? "block" : "none";
|
||||
|
|
|
@ -4,4 +4,86 @@
|
|||
{tr("user_banned", htmlentities($user->getFirstName()))|noescape}<br/>
|
||||
{_"user_banned_comment"} <b>{$user->getBanReason()}</b>.
|
||||
</p>
|
||||
{if isset($thisUser)}
|
||||
<p n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL) || $thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)">
|
||||
<br />
|
||||
<a n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)" href="javascript:unbanUser()" class="button">{_unban_user_action}</a>
|
||||
<a n:if="$thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)" href="javascript:toggleBanInSupport()" class="button">
|
||||
{if $user->isBannedInSupport()}
|
||||
{_unban_in_support_user_action}
|
||||
{else}
|
||||
{_ban_in_support_user_action}
|
||||
{/if}
|
||||
</a>
|
||||
</p>
|
||||
{/if}
|
||||
</center>
|
||||
|
||||
{if isset($thisUser)}
|
||||
<script n:if="$thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)">
|
||||
function unbanUser() {
|
||||
uUnbanMsgTxt = "Вы собираетесь разбанить пользователя " + {$user->getCanonicalName()} + ".";
|
||||
uUnbanMsgTxt += "<br/>Сейчас он заблокирован по причине: <strong>" + {$user->getBanReason()} + "</strong>.";
|
||||
|
||||
MessageBox("Разбанить " + {$user->getFirstName()}, uUnbanMsgTxt, ["Подтвердить", "Отмена"], [
|
||||
(function() {
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/admin/unban/" + {$user->getId()} + "?hash=" + {rawurlencode($csrfToken)}, true);
|
||||
xhr.onload = (function() {
|
||||
if(xhr.responseText.indexOf("success") === -1)
|
||||
MessageBox("Ошибка", "Не удалось разблокировать пользователя...", ["OK"], [Function.noop]);
|
||||
else
|
||||
MessageBox("Операция успешна", "Пользователь разблокирован", ["OK"], [Function.noop]);
|
||||
});
|
||||
xhr.send(null);
|
||||
}),
|
||||
Function.noop
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script n:if="$thisUser->getChandlerUser()->can('write')->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)">
|
||||
{if $user->isBannedInSupport()}
|
||||
function toggleBanInSupport() {
|
||||
uBanMsgTxt = "Вы собираетесь разблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
|
||||
uBanMsgTxt += "<br/>Сейчас он заблокирован по причине <strong>" + {$user->getBanInSupportReason()} + "</strong>.";
|
||||
|
||||
MessageBox("Разблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
|
||||
(function() {
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/admin/support/unban/" + {$user->getId()} + "?hash=" + {rawurlencode($csrfToken)}, true);
|
||||
xhr.onload = (function() {
|
||||
if(xhr.responseText.indexOf("success") === -1)
|
||||
MessageBox("Ошибка", "Не удалось разблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
|
||||
else
|
||||
MessageBox("Операция успешна", "Пользователь разблокирован в поддержке", ["OK"], [Function.noop]);
|
||||
});
|
||||
xhr.send(null);
|
||||
}),
|
||||
Function.noop
|
||||
]);
|
||||
}
|
||||
{else}
|
||||
function toggleBanInSupport() {
|
||||
uBanMsgTxt = "Вы собираетесь заблокировать в поддержке пользователя " + {$user->getCanonicalName()} + ".";
|
||||
uBanMsgTxt += "<br/><br/><b>Причина бана</b>: <input type='text' id='uBanMsgInput' placeholder='придумайте что-нибудь крутое' />";
|
||||
|
||||
MessageBox("Заблокировать в поддержке " + {$user->getFirstName()}, uBanMsgTxt, ["Подтвердить", "Отмена"], [
|
||||
(function() {
|
||||
res = document.querySelector("#uBanMsgInput").value;
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/admin/support/ban/" + {$user->getId()} + "?reason=" + res + "&hash=" + {rawurlencode($csrfToken)}, true);
|
||||
xhr.onload = (function() {
|
||||
if(xhr.responseText.indexOf("success") === -1)
|
||||
MessageBox("Ошибка", "Не удалось заблокировать пользователя в поддержке...", ["OK"], [Function.noop]);
|
||||
else
|
||||
MessageBox("Операция успешна", "Пользователь заблокирован в поддержке", ["OK"], [Function.noop]);
|
||||
});
|
||||
xhr.send(null);
|
||||
}),
|
||||
Function.noop
|
||||
]);
|
||||
}
|
||||
{/if}
|
||||
</script>
|
||||
{/if}
|
||||
|
|
14
Web/Presenters/templates/User/deleted.xml
Normal file
14
Web/Presenters/templates/User/deleted.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_information}{/block}
|
||||
|
||||
{block header}
|
||||
{_information}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<center style="background: white;border: #DEDEDE solid 1px;">
|
||||
<span style="color: #707070;margin: 60px 0;display: block;">
|
||||
{_profile_not_found_text}
|
||||
</span>
|
||||
</center>
|
||||
{/block}
|
|
@ -1,7 +1,7 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = $videos}
|
||||
{var count = $paginatorConf->count}
|
||||
{var page = $paginatorConf->page}
|
||||
{var $iterator = $videos}
|
||||
{var $count = $paginatorConf->count}
|
||||
{var $page = $paginatorConf->page}
|
||||
|
||||
{block title}{_"videos"} {$user->getCanonicalName()}{/block}
|
||||
|
||||
|
@ -11,13 +11,13 @@
|
|||
{/block}
|
||||
|
||||
{block size}
|
||||
<div style="padding-bottom: 0px;border-bottom: 0; padding-top: 0;" class="summaryBar">
|
||||
<div style="padding-bottom: 0px; padding-top: 0;" class="summaryBar">
|
||||
<div class="summary">
|
||||
{tr("videos", $count)}
|
||||
<span n:if="isset($thisUser) && $thisUser->getId() == $user->getId()">
|
||||
|
|
||||
<a href="/videos/upload">{_upload_video}</a>
|
||||
</span>
|
||||
{tr("videos", $count)}
|
||||
<span n:if="isset($thisUser) && $thisUser->getId() == $user->getId()">
|
||||
|
|
||||
<a href="/videos/upload">{_upload_video}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
@ -33,9 +33,11 @@
|
|||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getThumbnailURL()}"
|
||||
alt="{$x->getName()}"
|
||||
style="max-height: 43pt; max-width: 140px; height: unset; width: unset; border-radius: unset;" />
|
||||
<div class="video-preview">
|
||||
<img src="{$x->getThumbnailURL()}"
|
||||
alt="{$x->getName()}"
|
||||
style="max-width: 170px; max-height: 127px; margin: auto;" />
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
|
@ -43,7 +45,13 @@
|
|||
{/block}
|
||||
|
||||
{block description}
|
||||
<span>{$x->getDescription() ?? ""}</span><br/>
|
||||
<p>
|
||||
<span>{$x->getDescription() ?? ""}</span>
|
||||
</p>
|
||||
<span style="color: grey;">{_"video_uploaded"} {$x->getPublicationTime()}</span><br/>
|
||||
<span style="color: grey;">{_"video_updated"} {$x->getEditTime() ?? $x->getPublicationTime()}</span>
|
||||
<p>
|
||||
<a href="/video{$x->getPrettyId()}">{_view_video}</a>
|
||||
{if $x->getCommentsCount() > 0}| <a href="/video{$x->getPrettyId()}#comments">{_"comments"} ({$x->getCommentsCount()})</a>{/if}
|
||||
</p>
|
||||
{/block}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
{if $video->getType() === 0}
|
||||
<video width="610" src="{$video->getURL()}" controls></video>
|
||||
{else}
|
||||
{var driver = $video->getVideoDriver()}
|
||||
{var $driver = $video->getVideoDriver()}
|
||||
{if !$driver}
|
||||
Эта видеозапись не поддерживается в вашей версии OpenVK.
|
||||
{else}
|
||||
|
|
|
@ -27,10 +27,9 @@
|
|||
<div style="float: left; min-height: 100px; width: 32%;">
|
||||
<h4>{_actions}</h4>
|
||||
{if isset($thisUser)}
|
||||
{var canDelete = $post->canBeDeletedBy($thisUser)}
|
||||
{var $canDelete = $post->canBeDeletedBy($thisUser)}
|
||||
{/if}
|
||||
|
||||
<a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a>
|
||||
<a class="profile_link" style="display:block;width:96%;" href="/report.pl/{$post->getId()}?type=post">{_report}</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue