Merge branch 'master' into photos-picker

This commit is contained in:
lalka2016 2023-09-17 22:45:59 +03:00
commit b1910a9ce9
198 changed files with 10474 additions and 1402 deletions

8
.idea/.gitignore vendored
View file

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

View file

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

View file

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

View file

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

View file

@ -1,41 +0,0 @@
<?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>

View file

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

View file

@ -1,8 +0,0 @@
<?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>

View file

@ -1,46 +0,0 @@
<?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>

View file

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

View file

@ -1,10 +0,0 @@
<?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>

View file

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

140
DBEntity.updated.php Normal file
View file

@ -0,0 +1,140 @@
<?php declare(strict_types=1);
namespace Chandler\Database;
use Chandler\Database\DatabaseConnection;
use Nette\Database\Table\Selection;
use Nette\Database\Table\ActiveRow;
use Nette\InvalidStateException as ISE;
use openvk\Web\Models\Repositories\CurrentUser;
use openvk\Web\Models\Repositories\Logs;
abstract class DBEntity
{
protected $record;
protected $changes;
protected $deleted;
protected $user;
protected $tableName;
function __construct(?ActiveRow $row = NULL)
{
if(is_null($row)) return;
$_table = $row->getTable()->getName();
if($_table !== $this->tableName)
throw new ISE("Invalid data supplied for model: table $_table is not compatible with table" . $this->tableName);
$this->record = $row;
}
function __call(string $fName, array $args)
{
if(substr($fName, 0, 3) === "set") {
$field = mb_strtolower(substr($fName, 3));
$this->stateChanges($field, $args[0]);
} else {
throw new \Error("Call to undefined method " . get_class($this) . "::$fName");
}
}
private function getTable(): Selection
{
return DatabaseConnection::i()->getContext()->table($this->tableName);
}
protected function getRecord(): ?ActiveRow
{
return $this->record;
}
protected function stateChanges(string $column, $value): void
{
if(!is_null($this->record))
$t = $this->record->{$column}; #Test if column exists
$this->changes[$column] = $value;
}
function getId()
{
return $this->getRecord()->id;
}
function isDeleted(): bool
{
return (bool) $this->getRecord()->deleted;
}
function unwrap(): object
{
return (object) $this->getRecord()->toArray();
}
function delete(bool $softly = true): void
{
$user = CurrentUser::i()->getUser();
$user_id = is_null($user) ? (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getId();
if(is_null($this->record))
throw new ISE("Can't delete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?");
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 2, $this->record->toArray(), $this->changes);
if($softly) {
$this->record = $this->getTable()->where("id", $this->record->id)->update(["deleted" => true]);
} else {
$this->record->delete();
$this->deleted = true;
}
}
function undelete(): void
{
if(is_null($this->record))
throw new ISE("Can't undelete a model, that hasn't been flushed to DB. Have you forgotten to call save() first?");
$user = CurrentUser::i()->getUser();
$user_id = is_null($user) ? (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getId();
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 3, $this->record->toArray(), ["deleted" => false]);
$this->getTable()->where("id", $this->record->id)->update(["deleted" => false]);
}
function save(?bool $log = true): void
{
if ($log) {
$user = CurrentUser::i();
$user_id = is_null($user) ? (int)OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"] : $user->getUser()->getId();
}
if(is_null($this->record)) {
$this->record = $this->getTable()->insert($this->changes);
if ($log && $this->getTable()->getName() !== "logs") {
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 0, $this->record->toArray(), $this->changes);
}
} else {
if ($log && $this->getTable()->getName() !== "logs") {
(new Logs)->create($user_id, $this->getTable()->getName(), get_class($this), 1, $this->record->toArray(), $this->changes);
}
if ($this->deleted) {
$this->record = $this->getTable()->insert((array)$this->record);
} else {
$this->getTable()->get($this->record->id)->update($this->changes);
$this->record = $this->getTable()->get($this->record->id);
}
}
$this->changes = [];
}
function getTableName(): string
{
return $this->getTable()->getName();
}
use \Nette\SmartObject;
}

View file

@ -66,7 +66,7 @@ Once you are done, you can login as a system administrator on the network itself
* **Password**: `admin`
* It is recommended to change the password of the built-in account or disable it.
💡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)).
💡Confused? Full installation walkthrough is available [here](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [and](https://almalinux.org/) [family](https://yum.oracle.com/oracle-linux-isos.html)).
### Looking for Docker or Kubernetes deployment?
See `install/automated/docker/README.md` and `install/automated/kubernetes/README.md` for Docker and Kubernetes deployment instructions.

View file

@ -66,7 +66,7 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* **Пароль**: `admin`
* Перед использованием встроенной учетной записи рекомендуется сменить пароль или отключить её.
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.su/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
💡Запутались? Полное руководство по установке доступно [здесь](https://docs.openvk.uk/openvk_engine/centos8_installation/) (CentOS 8 [и](https://almalinux.org/ru/) [семейство](https://yum.oracle.com/oracle-linux-isos.html)).
# Установка в Docker/Kubernetes
Подробные иструкции можно найти в `install/automated/docker/README.md` и `install/automated/kubernetes/README.md` соответственно.

39
ServiceAPI/Groups.php Normal file
View file

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Clubs;
class Groups implements Handler
{
protected $user;
protected $groups;
function __construct(?User $user)
{
$this->user = $user;
$this->groups = new Clubs;
}
function getWriteableClubs(callable $resolve, callable $reject)
{
$clubs = [];
$wclubs = $this->groups->getWriteableClubs($this->user->getId());
$count = $this->groups->getWriteableClubsCount($this->user->getId());
if(!$count) {
$reject("You don't have any groups with write access");
return;
}
foreach($wclubs as $club) {
$clubs[] = [
"name" => $club->getName(),
"id" => $club->getId(),
"avatar" => $club->getAvatarUrl() # если в овк когда-нибудь появится крутой список с аватарками, то можно использовать это поле
];
}
$resolve($clubs);
}
}

41
ServiceAPI/Notes.php Normal file
View file

@ -0,0 +1,41 @@
<?php
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Notes as NoteRepo;
class Notes implements Handler
{
protected $user;
protected $notes;
function __construct(?User $user)
{
$this->user = $user;
$this->notes = new NoteRepo;
}
function getNote(int $noteId, callable $resolve, callable $reject): void
{
$note = $this->notes->get($noteId);
if(!$note || $note->isDeleted())
$reject(83, "Note is gone");
$noteOwner = $note->getOwner();
assert($noteOwner instanceof User);
if(!$noteOwner->getPrivacyPermission("notes.read", $this->user))
$reject(160, "You don't have permission to access this note");
$resolve([
"title" => $note->getName(),
"link" => "/note" . $note->getPrettyId(),
"html" => $note->getText(),
"created" => (string) $note->getPublicationTime(),
"author" => [
"name" => $noteOwner->getCanonicalName(),
"ava" => $noteOwner->getAvatarUrl(),
"link" => $noteOwner->getURL(),
],
]);
}
}

76
ServiceAPI/Search.php Normal file
View file

@ -0,0 +1,76 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\{User, Club};
use openvk\Web\Models\Repositories\{Users, Clubs, Videos};
use Chandler\Database\DatabaseConnection;
class Search implements Handler
{
protected $user;
private $users;
private $clubs;
private $videos;
function __construct(?User $user)
{
$this->user = $user;
$this->users = new Users;
$this->clubs = new Clubs;
$this->videos = new Videos;
}
function fastSearch(string $query, string $type = "users", callable $resolve, callable $reject)
{
if($query == "" || strlen($query) < 3)
$reject(12, "No input or input < 3");
$repo;
$sort;
switch($type) {
default:
case "users":
$repo = (new Users);
$sort = "rating DESC";
break;
case "groups":
$repo = (new Clubs);
$sort = "id ASC";
break;
case "videos":
$repo = (new Videos);
$sort = "created ASC";
break;
}
$res = $repo->find($query, ["doNotSearchMe" => $this->user->getId()], $sort);
$results = array_slice(iterator_to_array($res), 0, 5);
$count = sizeof($results);
$arr = [
"count" => $count,
"items" => []
];
if(sizeof($results) < 1) {
$reject(2, "No results");
}
foreach($results as $res) {
$arr["items"][] = [
"id" => $res->getId(),
"name" => $type == "users" ? $res->getCanonicalName() : $res->getName(),
"avatar" => $type != "videos" ? $res->getAvatarUrl() : $res->getThumbnailURL(),
"url" => $type != "videos" ? $res->getUrl() : "/video".$res->getPrettyId(),
"description" => ovk_proc_strtr($res->getDescription() ?? "...", 40)
];
}
$resolve($arr);
}
}

View file

@ -2,17 +2,20 @@
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Posts;
use openvk\Web\Models\Repositories\{Posts, Notes, Videos};
class Wall implements Handler
{
protected $user;
protected $posts;
protected $notes;
function __construct(?User $user)
{
$this->user = $user;
$this->posts = new Posts;
$this->notes = new Notes;
$this->videos = new Videos;
}
function getPost(int $id, callable $resolve, callable $reject): void
@ -71,4 +74,67 @@ class Wall implements Handler
$resolve($post->getId());
}
function getMyNotes(callable $resolve, callable $reject)
{
$count = $this->notes->getUserNotesCount($this->user);
$myNotes = $this->notes->getUserNotes($this->user, 1, $count);
$arr = [
"count" => $count,
"closed" => $this->user->getPrivacySetting("notes.read"),
"items" => [],
];
foreach($myNotes as $note) {
$arr["items"][] = [
"id" => $note->getId(),
"name" => ovk_proc_strtr($note->getName(), 30),
#"preview" => $note->getPreview()
];
}
$resolve($arr);
}
function getVideos(int $page = 1, callable $resolve, callable $reject)
{
$videos = $this->videos->getByUser($this->user, $page, 8);
$count = $this->videos->getUserVideosCount($this->user);
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct()), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
function searchVideos(int $page = 1, string $query, callable $resolve, callable $reject)
{
$dbc = $this->videos->find($query);
$videos = $dbc->page($page, 8);
$count = $dbc->size();
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct()), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
}

View file

@ -66,6 +66,8 @@ final class Account extends VKAPIRequestHandler
function getCounters(string $filter = ""): object
{
$this->requireUser();
return (object) [
"friends" => $this->getUser()->getFollowersCount(),
"notifications" => $this->getUser()->getNotificationsCount(),

431
VKAPI/Handlers/Board.php Normal file
View file

@ -0,0 +1,431 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\VKAPI\Handlers\Wall;
use openvk\Web\Models\Repositories\Topics as TopicsRepo;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Entities\{Topic, Comment, User, Photo, Video};
final class Board extends VKAPIRequestHandler
{
# 13/13
function addTopic(int $group_id, string $title, string $text = "", bool $from_group = true, string $attachments = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = (new ClubsRepo)->get($group_id);
if(!$club) {
$this->fail(403, "Invalid club");
}
if(!$club->canBeModifiedBy($this->getUser()) && !$club->isEveryoneCanCreateTopics()) {
$this->fail(403, "Access to club denied");
}
$flags = 0;
if($from_group == true && $club->canBeModifiedBy($this->getUser()))
$flags |= 0b10000000;
$topic = new Topic;
$topic->setGroup($club->getId());
$topic->setOwner($this->getUser()->getId());
$topic->setTitle(ovk_proc_strtr($title, 127));
$topic->setCreated(time());
$topic->setFlags($flags);
$topic->save();
if(!empty($text)) {
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($topic));
$comment->setTarget($topic->getId());
$comment->setContent($text);
$comment->setCreated(time());
$comment->setFlags($flags);
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
# блин а мне это везде копировать типа
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
}
return $topic->getId();
}
function closeTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
if(!$topic->isClosed()) {
$topic->setClosed(1);
$topic->save();
}
return 1;
}
function createComment(int $group_id, int $topic_id, string $message = "", string $attachments = "", bool $from_group = true)
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($message) && empty($attachments)) {
$this->fail(100, "Required parameter 'message' missing.");
}
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || $topic->isDeleted() || $topic->isClosed()) {
$this->fail(100, "Topic is deleted, closed or invalid.");
}
$flags = 0;
if($from_group != 0 && !is_null($topic->getClub()) && $topic->getClub()->canBeModifiedBy($this->user))
$flags |= 0b10000000;
if(strlen($message) > 300) {
$this->fail(20, "Comment is too long.");
}
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($topic));
$comment->setTarget($topic->getId());
$comment->setContent($message);
$comment->setCreated(time());
$comment->setFlags($flags);
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
return $comment->getId();
}
function deleteComment(int $comment_id, int $group_id = 0, int $topic_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->isDeleted() || !$comment || !$comment->canBeDeletedBy($this->getUser()))
$this->fail(403, "Access to comment denied");
$comment->delete();
return 1;
}
function deleteTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || $topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
$topic->deleteTopic();
return 1;
}
function editComment(int $comment_id, int $group_id = 0, int $topic_id = 0, string $message, string $attachments)
{
/*
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->getOwner() != $this->getUser()->getId())
$this->fail(15, "Access to comment denied");
$comment->setContent($message);
$comment->setEdited(time());
$comment->save();
*/
return 1;
}
function editTopic(int $group_id, int $topic_id, string $title)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || $topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
$topic->setTitle(ovk_proc_strtr($title, 127));
$topic->save();
return 1;
}
function fixTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
$topic->setPinned(1);
$topic->save();
return 1;
}
function getComments(int $group_id, int $topic_id, bool $need_likes = false, int $start_comment_id = 0, int $offset = 0, int $count = 40, bool $extended = false, string $sort = "asc")
{
# start_comment_id ne robit
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || $topic->isDeleted()) {
$this->fail(5, "Invalid topic");
}
$arr = [
"items" => []
];
$comms = array_slice(iterator_to_array($topic->getComments(1, $count + $offset)), $offset);
foreach($comms as $comm) {
$arr["items"][] = $this->getApiBoardComment($comm, $need_likes);
if($extended) {
if($comm->getOwner() instanceof \openvk\Web\Models\Entities\User) {
$arr["profiles"][] = $comm->getOwner()->toVkApiStruct();
}
if($comm->getOwner() instanceof \openvk\Web\Models\Entities\Club) {
$arr["groups"][] = $comm->getOwner()->toVkApiStruct();
}
}
}
return $arr;
}
function getTopics(int $group_id, string $topic_ids = "", int $order = 1, int $offset = 0, int $count = 40, bool $extended = false, int $preview = 0, int $preview_length = 90)
{
# order и extended ничё не делают
$this->requireUser();
$this->willExecuteWriteAction();
$arr = [];
$club = (new ClubsRepo)->get($group_id);
$topics = array_slice(iterator_to_array((new TopicsRepo)->getClubTopics($club, 1, $count + $offset)), $offset);
$arr["count"] = (new TopicsRepo)->getClubTopicsCount($club);
$arr["items"] = [];
$arr["default_order"] = $order;
$arr["can_add_topics"] = $club->canBeModifiedBy($this->getUser()) ? true : $club->isEveryoneCanCreateTopics() ? true : false;
$arr["profiles"] = [];
if(empty($topic_ids)) {
foreach($topics as $topic) {
if($topic->isDeleted()) continue;
$arr["items"][] = $topic->toVkApiStruct($preview, $preview_length > 1 ? $preview_length : 90);
}
} else {
$topics = explode(',', $topic_ids);
foreach($topics as $topic) {
$id = explode("_", $topic);
$topicy = (new TopicsRepo)->getTopicById((int)$id[0], (int)$id[1]);
if($topicy && !$topicy->isDeleted()) {
$arr["items"][] = $topicy->toVkApiStruct($preview, $preview_length > 1 ? $preview_length : 90);
}
}
}
return $arr;
}
function openTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->isDeleted() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
if($topic->isClosed()) {
$topic->setClosed(0);
$topic->save();
}
return 1;
}
function restoreComment(int $group_id, int $topic_id, int $comment_id)
{
$this->fail(501, "Not implemented");
}
function unfixTopic(int $group_id, int $topic_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$topic = (new TopicsRepo)->getTopicById($group_id, $topic_id);
if(!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0;
}
if($topic->isPinned()) {
$topic->setClosed(0);
$topic->save();
}
$topic->setPinned(0);
$topic->save();
return 1;
}
private function getApiBoardComment(?Comment $comment, bool $need_likes = false)
{
$res = (object) [];
$res->id = $comment->getId();
$res->from_id = $comment->getOwner()->getId();
$res->date = $comment->getPublicationTime()->timestamp();
$res->text = $comment->getText(false);
$res->attachments = [];
$res->likes = [];
if($need_likes) {
$res->likes = [
"count" => $comment->getLikesCount(),
"user_likes" => (int) $comment->hasLikeFrom($this->getUser()),
"can_like" => 1 # а чё типо не может ахахаххахах
];
}
foreach($comment->getChildren() as $attachment) {
if($attachment->isDeleted())
continue;
$res->attachments[] = $attachment->toVkApiStruct();
}
return $res;
}
}

174
VKAPI/Handlers/Gifts.php Normal file
View file

@ -0,0 +1,174 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Gifts as GiftsRepo;
use openvk\Web\Models\Entities\Notifications\GiftNotification;
final class Gifts extends VKAPIRequestHandler
{
function get(int $user_id, int $count = 10, int $offset = 0)
{
$this->requireUser();
$i = 0;
$i += $offset;
$user = (new UsersRepo)->get($user_id);
if(!$user || $user->isDeleted())
$this->fail(177, "Invalid user");
$gift_item = [];
$userGifts = array_slice(iterator_to_array($user->getGifts(1, $count, false)), $offset);
if(sizeof($userGifts) < 0) {
return NULL;
}
foreach($userGifts as $gift) {
if($i < $count) {
$gift_item[] = [
"id" => $i,
"from_id" => $gift->anon == true ? 0 : $gift->sender->getId(),
"message" => $gift->caption == NULL ? "" : $gift->caption,
"date" => $gift->sent->timestamp(),
"gift" => [
"id" => $gift->gift->getId(),
"thumb_256" => $gift->gift->getImage(2),
"thumb_96" => $gift->gift->getImage(2),
"thumb_48" => $gift->gift->getImage(2)
],
"privacy" => 0
];
}
$i+=1;
}
return $gift_item;
}
function send(int $user_ids, int $gift_id, string $message = "", int $privacy = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$user = (new UsersRepo)->get((int) $user_ids);
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
if(!$user || $user->isDeleted())
$this->fail(177, "Invalid user");
$gift = (new GiftsRepo)->get($gift_id);
if(!$gift)
$this->fail(165, "Invalid gift");
$price = $gift->getPrice();
$coinsLeft = $this->getUser()->getCoins() - $price;
if(!$gift->canUse($this->getUser()))
return (object)
[
"success" => 0,
"user_ids" => $user_ids,
"error" => "You don't have any more of these gifts."
];
if($coinsLeft < 0)
return (object)
[
"success" => 0,
"user_ids" => $user_ids,
"error" => "You don't have enough voices."
];
$user->gift($this->getUser(), $gift, $message);
$gift->used();
$this->getUser()->setCoins($coinsLeft);
$this->getUser()->save();
$notification = new GiftNotification($user, $this->getUser(), $gift, $message);
$notification->emit();
return (object)
[
"success" => 1,
"user_ids" => $user_ids,
"withdraw_votes" => $price
];
}
function delete()
{
$this->requireUser();
$this->willExecuteWriteAction();
$this->fail(501, "Not implemented");
}
# этих методов не было в ВК, но я их добавил чтобы можно было отобразить список подарков
function getCategories(bool $extended = false, int $page = 1)
{
$cats = (new GiftsRepo)->getCategories($page);
$categ = [];
$i = 0;
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
foreach($cats as $cat) {
$categ[$i] = [
"name" => $cat->getName(),
"description" => $cat->getDescription(),
"id" => $cat->getId(),
"thumbnail" => $cat->getThumbnailURL(),
];
if($extended == true) {
$categ[$i]["localizations"] = [];
foreach(getLanguages() as $lang) {
$code = $lang["code"];
$categ[$i]["localizations"][$code] =
[
"name" => $cat->getName($code),
"desc" => $cat->getDescription($code),
];
}
}
$i++;
}
return $categ;
}
function getGiftsInCategory(int $id, int $page = 1)
{
$this->requireUser();
if(!OPENVK_ROOT_CONF['openvk']['preferences']['commerce'])
$this->fail(105, "Commerce is disabled on this instance");
if(!(new GiftsRepo)->getCat($id))
$this->fail(177, "Category not found");
$giftz = ((new GiftsRepo)->getCat($id))->getGifts($page);
$gifts = [];
foreach($giftz as $gift) {
$gifts[] = [
"name" => $gift->getName(),
"image" => $gift->getImage(2),
"usages_left" => (int)$gift->getUsagesLeft($this->getUser()),
"price" => $gift->getPrice(), # голосов
"is_free" => $gift->isFree()
];
}
return $gifts;
}
}

View file

@ -2,6 +2,7 @@
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Entities\Club;
final class Groups extends VKAPIRequestHandler
{
@ -263,4 +264,272 @@ final class Groups extends VKAPIRequestHandler
return 1;
}
function create(string $title, string $description = "", string $type = "group", int $public_category = 1, int $public_subcategory = 1, int $subtype = 1)
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = new Club;
$club->setName($title);
$club->setAbout($description);
$club->setOwner($this->getUser()->getId());
$club->save();
$club->toggleSubscription($this->getUser());
return $this->getById((string)$club->getId());
}
function edit(
int $group_id,
string $title = NULL,
string $description = NULL,
string $screen_name = NULL,
string $website = NULL,
int $wall = NULL,
int $topics = NULL,
int $adminlist = NULL,
int $topicsAboveWall = NULL,
int $hideFromGlobalFeed = NULL)
{
$this->requireUser();
$this->willExecuteWriteAction();
$club = (new ClubsRepo)->get($group_id);
if(!$club) $this->fail(203, "Club not found");
if(!$club || !$club->canBeModifiedBy($this->getUser())) $this->fail(15, "You can't modify this group.");
if(!empty($screen_name) && !$club->setShortcode($screen_name)) $this->fail(103, "Invalid shortcode.");
!is_null($title) ? $club->setName($title) : NULL;
!is_null($description) ? $club->setAbout($description) : NULL;
!is_null($screen_name) ? $club->setShortcode($screen_name) : NULL;
!is_null($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL;
!is_null($wall) ? $club->setWall($wall) : NULL;
!is_null($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL;
!is_null($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
!is_null($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
!is_null($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL;
$club->save();
return 1;
}
function getMembers(string $group_id, string $sort = "id_asc", int $offset = 0, int $count = 100, string $fields = "", string $filter = "any")
{
# bdate,can_post,can_see_all_posts,can_see_audio,can_write_private_message,city,common_count,connections,contacts,country,domain,education,has_mobile,last_seen,lists,online,online_mobile,photo_100,photo_200,photo_200_orig,photo_400_orig,photo_50,photo_max,photo_max_orig,relation,relatives,schools,sex,site,status,universities
$club = (new ClubsRepo)->get((int) $group_id);
if(!$club)
$this->fail(125, "Invalid group id");
$sorter = "follower ASC";
switch($sort) {
default:
case "time_asc":
case "id_asc":
$sorter = "follower ASC";
break;
case "time_desc":
case "id_desc":
$sorter = "follower DESC";
break;
}
$members = array_slice(iterator_to_array($club->getFollowers(1, $count, $sorter)), $offset);
$arr = (object) [
"count" => count($members),
"items" => array()];
$filds = explode(",", $fields);
$i = 0;
foreach($members as $member) {
if($i > $count) {
break;
}
$arr->items[] = (object) [
"id" => $member->getId(),
"first_name" => $member->getFirstName(),
"last_name" => $member->getLastName(),
];
foreach($filds as $fild) {
switch($fild) {
case "bdate":
$arr->items[$i]->bdate = $member->getBirthday()->format('%e.%m.%Y');
break;
case "can_post":
$arr->items[$i]->can_post = $club->canBeModifiedBy($member);
break;
case "can_see_all_posts":
$arr->items[$i]->can_see_all_posts = 1;
break;
case "can_see_audio":
$arr->items[$i]->can_see_audio = 0;
break;
case "can_write_private_message":
$arr->items[$i]->can_write_private_message = 0;
break;
case "common_count":
$arr->items[$i]->common_count = 420;
break;
case "connections":
$arr->items[$i]->connections = 1;
break;
case "contacts":
$arr->items[$i]->contacts = $member->getContactEmail();
break;
case "country":
$arr->items[$i]->country = 1;
break;
case "domain":
$arr->items[$i]->domain = "";
break;
case "education":
$arr->items[$i]->education = "";
break;
case "has_mobile":
$arr->items[$i]->has_mobile = false;
break;
case "last_seen":
$arr->items[$i]->last_seen = $member->getOnline()->timestamp();
break;
case "lists":
$arr->items[$i]->lists = "";
break;
case "online":
$arr->items[$i]->online = $member->isOnline();
break;
case "online_mobile":
$arr->items[$i]->online_mobile = $member->getOnlinePlatform() == "android" || $member->getOnlinePlatform() == "iphone" || $member->getOnlinePlatform() == "mobile";
break;
case "photo_100":
$arr->items[$i]->photo_100 = $member->getAvatarURL("tiny");
break;
case "photo_200":
$arr->items[$i]->photo_200 = $member->getAvatarURL("normal");
break;
case "photo_200_orig":
$arr->items[$i]->photo_200_orig = $member->getAvatarURL("normal");
break;
case "photo_400_orig":
$arr->items[$i]->photo_400_orig = $member->getAvatarURL("normal");
break;
case "photo_max":
$arr->items[$i]->photo_max = $member->getAvatarURL("original");
break;
case "photo_max_orig":
$arr->items[$i]->photo_max_orig = $member->getAvatarURL();
break;
case "relation":
$arr->items[$i]->relation = $member->getMaritalStatus();
break;
case "relatives":
$arr->items[$i]->relatives = 0;
break;
case "schools":
$arr->items[$i]->schools = 0;
break;
case "sex":
$arr->items[$i]->sex = $member->isFemale() ? 1 : 2;
break;
case "site":
$arr->items[$i]->site = $member->getWebsite();
break;
case "status":
$arr->items[$i]->status = $member->getStatus();
break;
case "universities":
$arr->items[$i]->universities = 0;
break;
}
}
$i++;
}
return $arr;
}
function getSettings(string $group_id)
{
$this->requireUser();
$club = (new ClubsRepo)->get((int)$group_id);
if(!$club || !$club->canBeModifiedBy($this->getUser()))
$this->fail(15, "You can't get settings of this group.");
$arr = (object) [
"title" => $club->getName(),
"description" => $club->getDescription() != NULL ? $club->getDescription() : "",
"address" => $club->getShortcode(),
"wall" => $club->canPost() == true ? 1 : 0,
"photos" => 1,
"video" => 0,
"audio" => 0,
"docs" => 0,
"topics" => $club->isEveryoneCanCreateTopics() == true ? 1 : 0,
"wiki" => 0,
"messages" => 0,
"obscene_filter" => 0,
"obscene_stopwords" => 0,
"obscene_words" => "",
"access" => 1,
"subject" => 1,
"subject_list" => [
0 => "в",
1 => "опенвк",
2 => "нет",
3 => "категорий",
4 => "групп",
],
"rss" => "/club".$club->getId()."/rss",
"website" => $club->getWebsite(),
"age_limits" => 0,
"market" => [],
];
return $arr;
}
function isMember(string $group_id, int $user_id, string $user_ids = "", bool $extended = false)
{
$this->requireUser();
$id = $user_id != NULL ? $user_id : explode(",", $user_ids);
if($group_id < 0)
$this->fail(228, "Remove the minus from group_id");
$club = (new ClubsRepo)->get((int)$group_id);
$usver = (new UsersRepo)->get((int)$id);
if(!$club || $group_id == 0)
$this->fail(203, "Invalid club");
if(!$usver || $usver->isDeleted() || $user_id == 0)
$this->fail(30, "Invalid user");
if($extended == false) {
return $club->getSubscriptionStatus($usver) ? 1 : 0;
} else {
return (object)
[
"member" => $club->getSubscriptionStatus($usver) ? 1 : 0,
"request" => 0,
"invitation" => 0,
"can_invite" => 0,
"can_recall" => 0
];
}
}
function remove(int $group_id, int $user_id)
{
$this->requireUser();
$this->fail(501, "Not implemented");
}
}

View file

@ -54,11 +54,7 @@ final class Likes extends VKAPIRequestHandler
case "post":
$user = (new UsersRepo)->get($user_id);
if (is_null($user))
return (object) [
"liked" => 0,
"copied" => 0,
"sex" => 0
];
$this->fail(100, "One of the parameters specified was missing or invalid: user not found");
$post = (new PostsRepo)->getPostById($owner_id, $item_id);
if (is_null($post))

View file

@ -7,7 +7,7 @@ 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, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0)
function get(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0)
{
$this->requireUser();
@ -33,6 +33,8 @@ final class Newsfeed extends VKAPIRequestHandler
->where("wall IN (?)", $ids)
->where("deleted", 0)
->where("id < (?)", empty($start_from) ? PHP_INT_MAX : $start_from)
->where("? <= created", empty($start_time) ? 0 : $start_time)
->where("? >= created", empty($end_time) ? PHP_INT_MAX : $end_time)
->order("created DESC");
$rposts = [];
@ -45,7 +47,7 @@ final class Newsfeed extends VKAPIRequestHandler
return $response;
}
function getGlobal(string $fields = "", int $start_from = 0, int $offset = 0, int $count = 30, int $extended = 0)
function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0)
{
$this->requireUser();
@ -55,7 +57,9 @@ final class Newsfeed extends VKAPIRequestHandler
$queryBase .= " AND `nsfw` = 0";
$start_from = empty($start_from) ? PHP_INT_MAX : $start_from;
$posts = DatabaseConnection::i()->getConnection()->query("SELECT `posts`.`id` " . $queryBase . " AND `posts`.`id` < " . $start_from . " ORDER BY `created` DESC LIMIT " . $count . " OFFSET " . $offset);
$start_time = empty($start_time) ? 0 : $start_time;
$end_time = empty($end_time) ? PHP_INT_MAX : $end_time;
$posts = DatabaseConnection::i()->getConnection()->query("SELECT `posts`.`id` " . $queryBase . " AND `posts`.`id` <= " . $start_from . " AND " . $start_time . " <= `posts`.`created` AND `posts`.`created` <= " . $end_time . " ORDER BY `created` DESC LIMIT " . $count . " OFFSET " . $offset);
$rposts = [];
$ids = [];

283
VKAPI/Handlers/Notes.php Normal file
View file

@ -0,0 +1,283 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Notes as NotesRepo;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Entities\{Note, Comment};
final class Notes extends VKAPIRequestHandler
{
function add(string $title, string $text, int $privacy = 0, int $comment_privacy = 0, string $privacy_view = "", string $privacy_comment = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = new Note;
$note->setOwner($this->getUser()->getId());
$note->setCreated(time());
$note->setName($title);
$note->setSource($text);
$note->setEdited(time());
$note->save();
return $note->getVirtualId();
}
function createComment(string $note_id, int $owner_id, string $message, int $reply_to = 0, string $attachments = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = (new NotesRepo)->getNoteById((int)$owner_id, (int)$note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if($note->getOwner()->isDeleted())
$this->fail(403, "Owner is deleted");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "No access");
if(empty($message) && empty($attachments))
$this->fail(100, "Required parameter 'message' missing.");
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($note));
$comment->setTarget($note->getId());
$comment->setContent($message);
$comment->setCreated(time());
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
return $comment->getId();
}
function delete(string $note_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = (new NotesRepo)->get((int)$note_id);
if(!$note)
$this->fail(180, "Note not found");
if(!$note->canBeModifiedBy($this->getUser()))
$this->fail(15, "Access to note denied");
$note->delete();
return 1;
}
function deleteComment(int $comment_id, int $owner_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if(!$comment || !$comment->canBeDeletedBy($this->getUser()))
$this->fail(403, "Access to comment denied");
$comment->delete();
return 1;
}
function edit(string $note_id, string $title = "", string $text = "", int $privacy = 0, int $comment_privacy = 0, string $privacy_view = "", string $privacy_comment = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$note = (new NotesRepo)->getNoteById($this->getUser()->getId(), (int)$note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if(!$note->canBeModifiedBy($this->getUser()))
$this->fail(403, "No access");
!empty($title) ? $note->setName($title) : NULL;
!empty($text) ? $note->setSource($text) : NULL;
$note->setCached_Content(NULL);
$note->setEdited(time());
$note->save();
return 1;
}
function editComment(int $comment_id, string $message, int $owner_id = NULL)
{
/*
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->getOwner() != $this->getUser()->getId())
$this->fail(15, "Access to comment denied");
$comment->setContent($message);
$comment->setEdited(time());
$comment->save();
*/
return 1;
}
function get(int $user_id, string $note_ids = "", int $offset = 0, int $count = 10, int $sort = 0)
{
$this->requireUser();
$user = (new UsersRepo)->get($user_id);
if(!$user || $user->isDeleted())
$this->fail(15, "Invalid user");
if(!$user->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(43, "Access denied: this user chose to hide his notes");
if(empty($note_ids)) {
$notes = array_slice(iterator_to_array((new NotesRepo)->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset);
$nodez = (object) [
"count" => (new NotesRepo)->getUserNotesCount((new UsersRepo)->get($user_id)),
"notes" => []
];
foreach($notes as $note) {
if($note->isDeleted()) continue;
$nodez->notes[] = $note->toVkApiStruct();
}
} else {
$notes = explode(',', $note_ids);
foreach($notes as $note)
{
$id = explode("_", $note);
$items = [];
$note = (new NotesRepo)->getNoteById((int)$id[0], (int)$id[1]);
if($note && !$note->isDeleted()) {
$nodez->notes[] = $note->toVkApiStruct();
}
}
}
return $nodez;
}
function getById(int $note_id, int $owner_id, bool $need_wiki = false)
{
$this->requireUser();
$note = (new NotesRepo)->getNoteById($owner_id, $note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if(!$note->getOwner() || $note->getOwner()->isDeleted())
$this->fail(177, "Owner does not exists");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(40, "Access denied: this user chose to hide his notes");
return $note->toVkApiStruct();
}
function getComments(int $note_id, int $owner_id, int $sort = 1, int $offset = 0, int $count = 100)
{
$this->requireUser();
$note = (new NotesRepo)->getNoteById($owner_id, $note_id);
if(!$note)
$this->fail(180, "Note not found");
if($note->isDeleted())
$this->fail(189, "Note is deleted");
if(!$note->getOwner())
$this->fail(177, "Owner does not exists");
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(14, "No access");
$arr = (object) [
"count" => $note->getCommentsCount(),
"comments" => []];
$comments = array_slice(iterator_to_array($note->getComments(1, $count + $offset)), $offset);
foreach($comments as $comment) {
$arr->comments[] = $comment->toVkApiStruct($this->getUser(), false, false, $note);
}
return $arr;
}
function getFriendsNotes(int $offset = 0, int $count = 0)
{
$this->fail(501, "Not implemented");
}
function restoreComment(int $comment_id = 0, int $owner_id = 0)
{
$this->fail(501, "Not implemented");
}
}

View file

@ -3,9 +3,12 @@ namespace openvk\VKAPI\Handlers;
use Nette\InvalidStateException;
use Nette\Utils\ImageException;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Entities\{Photo, Album, Comment};
use openvk\Web\Models\Repositories\Albums;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
final class Photos extends VKAPIRequestHandler
{
@ -58,7 +61,7 @@ final class Photos extends VKAPIRequestHandler
}
return (object) [
"upload_url" => $this->getPhotoUploadUrl("photo", isset($club) ? 0 : $club->getId()),
"upload_url" => $this->getPhotoUploadUrl("photo", !isset($club) ? 0 : $club->getId()),
];
}
@ -227,4 +230,504 @@ final class Photos extends VKAPIRequestHandler
"items" => $images,
];
}
function createAlbum(string $title, int $group_id = 0, string $description = "", int $privacy = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($group_id != 0) {
$club = (new Clubs)->get((int) $group_id);
if(!$club || !$club->canBeModifiedBy($this->getUser())) {
$this->fail(20, "Invalid club");
}
}
$album = new Album;
$album->setOwner(isset($club) ? $club->getId() * -1 : $this->getUser()->getId());
$album->setName($title);
$album->setDescription($description);
$album->setCreated(time());
$album->save();
return $album->toVkApiStruct($this->getUser());
}
function editAlbum(int $album_id, int $owner_id, string $title, string $description = "", int $privacy = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id);
if(!$album || $album->isDeleted()) {
$this->fail(2, "Invalid album");
}
if(empty($title)) {
$this->fail(25, "Title is empty");
}
if($album->isCreatedBySystem()) {
$this->fail(40, "You can't change system album");
}
if(!$album->canBeModifiedBy($this->getUser())) {
$this->fail(2, "Access to album denied");
}
$album->setName($title);
$album->setDescription($description);
$album->save();
return $album->toVkApiStruct($this->getUser());
}
function getAlbums(int $owner_id, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
$res = [];
if(empty($album_ids)) {
if($owner_id > 0) {
$user = (new UsersRepo)->get($owner_id);
$res = [
"count" => (new Albums)->getUserAlbumsCount($user),
"items" => []
];
if(!$user || $user->isDeleted())
$this->fail(2, "Invalid user");
if(!$user->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his albums.");
$albums = array_slice(iterator_to_array((new Albums)->getUserAlbums($user, 1, $count + $offset)), $offset);
foreach($albums as $album) {
if(!$need_system && $album->isCreatedBySystem()) continue;
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
}
else {
$club = (new Clubs)->get($owner_id * -1);
$res = [
"count" => (new Albums)->getClubAlbumsCount($club),
"items" => []
];
if(!$club)
$this->fail(2, "Invalid club");
$albums = array_slice(iterator_to_array((new Albums)->getClubAlbums($club, 1, $count + $offset)), $offset);
foreach($albums as $album) {
if(!$need_system && $album->isCreatedBySystem()) continue;
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
}
} else {
$albums = explode(',', $album_ids);
$res = [
"count" => sizeof($albums),
"items" => []
];
foreach($albums as $album)
{
$id = explode("_", $album);
$album = (new Albums)->getAlbumByOwnerAndId((int)$id[0], (int)$id[1]);
if($album && !$album->isDeleted()) {
if(!$need_system && $album->isCreatedBySystem()) continue;
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
}
}
return $res;
}
function getAlbumsCount(int $user_id = 0, int $group_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($user_id == 0 && $group_id == 0 || $user_id > 0 && $group_id > 0) {
$this->fail(21, "Select user_id or group_id");
}
if($user_id > 0) {
$us = (new UsersRepo)->get($user_id);
if(!$us || $us->isDeleted()) {
$this->fail(21, "Invalid user");
}
if(!$us->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
return (new Albums)->getUserAlbumsCount($us);
}
if($group_id > 0)
{
$cl = (new Clubs)->get($group_id);
if(!$cl) {
$this->fail(21, "Invalid club");
}
return (new Albums)->getClubAlbumsCount($cl);
}
}
function getById(string $photos, bool $extended = false, bool $photo_sizes = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
$phts = explode(",", $photos);
$res = [];
foreach($phts as $phota) {
$ph = explode("_", $phota);
$photo = (new PhotosRepo)->getByOwnerAndVID((int)$ph[0], (int)$ph[1]);
if(!$photo || $photo->isDeleted()) {
$this->fail(21, "Invalid photo");
}
if($photo->getOwner()->isDeleted()) {
$this->fail(21, "Owner of this photo is deleted");
}
if(!$photo->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his photos.");
}
$res[] = $photo->toVkApiStruct($photo_sizes, $extended);
}
return $res;
}
function get(int $owner_id, int $album_id, string $photo_ids = "", bool $extended = false, bool $photo_sizes = false, int $offset = 0, int $count = 10)
{
$this->requireUser();
$this->willExecuteWriteAction();
$res = [];
if(empty($photo_ids)) {
$album = (new Albums)->getAlbumByOwnerAndId($owner_id, $album_id);
if(!$album->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
if(!$album || $album->isDeleted()) {
$this->fail(21, "Invalid album");
}
$photos = array_slice(iterator_to_array($album->getPhotos(1, $count + $offset)), $offset);
$res["count"] = sizeof($photos);
foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue;
$res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended);
}
} else {
$photos = explode(',', $photo_ids);
$res = [
"count" => sizeof($photos),
"items" => []
];
foreach($photos as $photo)
{
$id = explode("_", $photo);
$phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]);
if($phot && !$phot->isDeleted()) {
$res["items"][] = $phot->toVkApiStruct($photo_sizes, $extended);
}
}
}
return $res;
}
function deleteAlbum(int $album_id, int $group_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$album = (new Albums)->get($album_id);
if(!$album || $album->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Invalid album");
}
if($album->isDeleted()) {
$this->fail(22, "Album already deleted");
}
$album->delete();
return 1;
}
function edit(int $owner_id, int $photo_id, string $caption = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if(!$photo) {
$this->fail(21, "Invalid photo");
}
if($photo->isDeleted()) {
$this->fail(21, "Photo is deleted");
}
if(!empty($caption)) {
$photo->setDescription($caption);
$photo->save();
}
return 1;
}
function delete(int $owner_id, int $photo_id, string $photos = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($photos)) {
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if($this->getUser()->getId() !== $photo->getOwner()->getId()) {
$this->fail(21, "You can't delete another's photo");
}
if(!$photo) {
$this->fail(21, "Invalid photo");
}
if($photo->isDeleted()) {
$this->fail(21, "Photo already deleted");
}
$photo->delete();
} else {
$photozs = explode(',', $photos);
foreach($photozs as $photo)
{
$id = explode("_", $photo);
$phot = (new PhotosRepo)->getByOwnerAndVID((int)$id[0], (int)$id[1]);
if($this->getUser()->getId() !== $phot->getOwner()->getId()) {
$this->fail(21, "You can't delete another's photo");
}
if(!$phot) {
$this->fail(21, "Invalid photo");
}
if($phot->isDeleted()) {
$this->fail(21, "Photo already deleted");
}
$phot->delete();
}
}
return 1;
}
function getAllComments(int $owner_id, int $album_id, bool $need_likes = false, int $offset = 0, int $count = 100)
{
$this->fail(501, "Not implemented");
}
function deleteComment(int $comment_id, int $owner_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if(!$comment) {
$this->fail(21, "Invalid comment");
}
if(!$comment->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Forbidden");
}
if($comment->isDeleted()) {
$this->fail(4, "Comment already deleted");
}
$comment->delete();
return 1;
}
function createComment(int $owner_id, int $photo_id, string $message = "", string $attachments = "", bool $from_group = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($message) && empty($attachments)) {
$this->fail(100, "Required parameter 'message' missing.");
}
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
if(!$photo)
$this->fail(180, "Photo not found");
if($photo->isDeleted())
$this->fail(189, "Photo is deleted");
$comment = new Comment;
$comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($photo));
$comment->setTarget($photo->getId());
$comment->setContent($message);
$comment->setCreated(time());
$comment->save();
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this photo");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this video");
$comment->attach($attacc);
}
}
}
return $comment->getId();
}
function getAll(int $owner_id, bool $extended = false, int $offset = 0, int $count = 100, bool $photo_sizes = false)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($owner_id < 0) {
$this->fail(4, "This method doesn't works with clubs");
}
$user = (new UsersRepo)->get($owner_id);
if(!$user) {
$this->fail(4, "Invalid user");
}
if(!$user->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
$photos = array_slice(iterator_to_array((new PhotosRepo)->getEveryUserPhoto($user, 1, $count + $offset)), $offset);
$res = [];
foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue;
$res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended);
}
return $res;
}
function getComments(int $owner_id, int $photo_id, bool $need_likes = false, int $offset = 0, int $count = 100, bool $extended = false, string $fields = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
$photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $photo_id);
$comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset);
if(!$photo) {
$this->fail(4, "Invalid photo");
}
if(!$photo->getAlbum()->getOwner()->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his photos.");
}
if($photo->isDeleted()) {
$this->fail(4, "Photo is deleted");
}
$res = [
"count" => sizeof($comms),
"items" => []
];
foreach($comms as $comment) {
$res["items"][] = $comment->toVkApiStruct($this->getUser(), $need_likes, $extended);
if($extended) {
if($comment->getOwner() instanceof \openvk\Web\Models\Entities\User) {
$res["profiles"][] = $comment->getOwner()->toVkApiStruct();
}
}
}
return $res;
}
}

View file

@ -75,7 +75,7 @@ final class Polls extends VKAPIRequestHandler
try {
$poll->vote($this->getUser(), explode(",", $answers_ids));
return 0;
return 1;
} catch(AlreadyVotedException $ex) {
return 0;
} catch(PollLockedException $ex) {
@ -97,7 +97,7 @@ final class Polls extends VKAPIRequestHandler
try {
$poll->revokeVote($this->getUser());
return 0;
return 1;
} catch(PollLockedException $ex) {
$this->fail(15, "Access denied: Poll is locked or isn't revotable");
} catch(InvalidOptionException $ex) {

35
VKAPI/Handlers/Status.php Normal file
View file

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\Users as UsersRepo;
final class Status extends VKAPIRequestHandler
{
function get(int $user_id = 0, int $group_id = 0)
{
$this->requireUser();
if($user_id == 0 && $group_id == 0) {
return $this->getUser()->getStatus();
} else {
if($group_id > 0)
$this->fail(501, "Group statuses are not implemented");
else
return (new UsersRepo)->get($user_id)->getStatus();
}
}
function set(string $text, int $group_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
if($group_id > 0) {
$this->fail(501, "Group statuses are not implemented");
} else {
$this->getUser()->setStatus($text);
$this->getUser()->save();
return 1;
}
}
}

View file

@ -32,7 +32,15 @@ final class Users extends VKAPIRequestHandler
"first_name" => "DELETED",
"last_name" => "",
"deactivated" => "deleted"
];
];
} else if($usr->isBanned()) {
$response[$i] = (object)[
"id" => $usr->getId(),
"first_name" => $usr->getFirstName(),
"last_name" => $usr->getLastName(),
"deactivated" => "banned",
"ban_reason" => $usr->getBanReason()
];
} else if($usrs[$i] == NULL) {
} else {
@ -150,7 +158,10 @@ final class Users extends VKAPIRequestHandler
break;
case "interests":
$response[$i]->interests = $usr->getInterests();
break;
break;
case "rating":
$response[$i]->rating = $usr->getRating();
break;
}
}
@ -188,19 +199,94 @@ final class Users extends VKAPIRequestHandler
];
}
function search(string $q, string $fields = "", int $offset = 0, int $count = 100)
function search(string $q,
string $fields = "",
int $offset = 0,
int $count = 100,
string $city = "",
string $hometown = "",
int $sex = 2,
int $status = 0, # это про marital status
bool $online = false,
# дальше идут параметры которых нету в vkapi но есть на сайте
string $profileStatus = "", # а это уже нормальный статус
int $sort = 0,
int $before = 0,
int $politViews = 0,
int $after = 0,
string $interests = "",
string $fav_music = "",
string $fav_films = "",
string $fav_shows = "",
string $fav_books = "",
string $fav_quotes = ""
)
{
$users = new UsersRepo;
$sortg = "id ASC";
$nfilds = $fields;
switch($sort) {
case 0:
$sortg = "id DESC";
break;
case 1:
$sortg = "id ASC";
break;
case 2:
$sortg = "first_name DESC";
break;
case 3:
$sortg = "first_name ASC";
break;
case 4:
$sortg = "rating DESC";
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break;
case 5:
$sortg = "rating DESC";
if(!str_contains($nfilds, "rating")) {
$nfilds .= "rating";
}
break;
}
$array = [];
$find = $users->find($q);
$parameters = [
"city" => !empty($city) ? $city : NULL,
"hometown" => !empty($hometown) ? $hometown : NULL,
"gender" => $sex < 2 ? $sex : NULL,
"maritalstatus" => (bool)$status ? $status : NULL,
"politViews" => (bool)$politViews ? $politViews : NULL,
"is_online" => $online ? 1 : NULL,
"status" => !empty($profileStatus) ? $profileStatus : NULL,
"before" => $before != 0 ? $before : NULL,
"after" => $after != 0 ? $after : NULL,
"interests" => !empty($interests) ? $interests : NULL,
"fav_music" => !empty($fav_music) ? $fav_music : NULL,
"fav_films" => !empty($fav_films) ? $fav_films : NULL,
"fav_shows" => !empty($fav_shows) ? $fav_shows : NULL,
"fav_books" => !empty($fav_books) ? $fav_books : NULL,
"fav_quotes" => !empty($fav_quotes) ? $fav_quotes : NULL,
];
$find = $users->find($q, $parameters, $sortg);
foreach ($find as $user)
$array[] = $user->getId();
return (object) [
"count" => $find->size(),
"items" => $this->get(implode(',', $array), $fields, $offset, $count)
"items" => $this->get(implode(',', $array), $nfilds, $offset, $count)
];
}
}

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\{Users, Clubs};
final class Utils extends VKAPIRequestHandler
{
@ -7,4 +8,39 @@ final class Utils extends VKAPIRequestHandler
{
return time();
}
function resolveScreenName(string $screen_name): object
{
if(\Chandler\MVC\Routing\Router::i()->getMatchingRoute("/$screen_name")[0]->presenter !== "UnknownTextRouteStrategy") {
if(substr($screen_name, 0, strlen("id")) === "id") {
return (object) [
"object_id" => (int) substr($screen_name, strlen("id")),
"type" => "user"
];
} else if(substr($screen_name, 0, strlen("club")) === "club") {
return (object) [
"object_id" => (int) substr($screen_name, strlen("club")),
"type" => "group"
];
}
} else {
$user = (new Users)->getByShortURL($screen_name);
if($user) {
return (object) [
"object_id" => $user->getId(),
"type" => "user"
];
}
$club = (new Clubs)->getByShortURL($screen_name);
if($club) {
return (object) [
"object_id" => $club->getId(),
"type" => "group"
];
}
return (object) [];
}
}
}

View file

@ -9,13 +9,19 @@ use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Entities\Comment;
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Entities\Video;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Entities\Note;
use openvk\Web\Models\Repositories\Notes as NotesRepo;
final class Wall extends VKAPIRequestHandler
{
function get(int $owner_id, string $domain = "", int $offset = 0, int $count = 30, int $extended = 0): object
{
$this->requireUser();
$posts = new PostsRepo;
$items = [];
@ -44,12 +50,14 @@ final class Wall extends VKAPIRequestHandler
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
if($attachment->isDeleted())
continue;
$attachments[] = $this->getApiPhoto($attachment);
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
$attachments[] = $this->getApiPoll($attachment, $this->getUser());
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
$repostAttachments = [];
@ -57,7 +65,7 @@ final class Wall extends VKAPIRequestHandler
if($repostAttachment instanceof \openvk\Web\Models\Entities\Photo) {
if($repostAttachment->isDeleted())
continue;
$repostAttachments[] = $this->getApiPhoto($repostAttachment);
/* Рекурсии, сука! Заказывали? */
}
@ -69,7 +77,7 @@ final class Wall extends VKAPIRequestHandler
$profiles[] = $attachment->getOwner()->getId();
$post_source = [];
if($attachment->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
@ -93,7 +101,7 @@ final class Wall extends VKAPIRequestHandler
}
$post_source = [];
if($post->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
@ -117,6 +125,7 @@ final class Wall extends VKAPIRequestHandler
"can_archive" => false, # TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(),
"attachments" => $attachments,
"post_source" => $post_source,
"comments" => (object)[
@ -134,7 +143,7 @@ final class Wall extends VKAPIRequestHandler
"user_reposted" => 0
]
];
if ($from_id > 0)
$profiles[] = $from_id;
else
@ -162,7 +171,8 @@ final class Wall extends VKAPIRequestHandler
"screen_name" => $user->getShortCode(),
"photo_50" => $user->getAvatarUrl(),
"photo_100" => $user->getAvatarUrl(),
"online" => $user->isOnline()
"online" => $user->isOnline(),
"verified" => $user->isVerified()
];
}
@ -177,6 +187,7 @@ final class Wall extends VKAPIRequestHandler
"photo_50" => $group->getAvatarUrl(),
"photo_100" => $group->getAvatarUrl(),
"photo_200" => $group->getAvatarUrl(),
"verified" => $group->isVerified()
];
}
@ -209,7 +220,7 @@ final class Wall extends VKAPIRequestHandler
foreach($psts as $pst) {
$id = explode("_", $pst);
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]));
if($post) {
if($post && !$post->isDeleted()) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments = [];
$repost = []; // чел высрал семь сигарет 😳 помянем 🕯
@ -220,6 +231,8 @@ final class Wall extends VKAPIRequestHandler
$attachments[] = $this->getApiPoll($attachment, $user);
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
$repostAttachments = [];
@ -227,19 +240,19 @@ final class Wall extends VKAPIRequestHandler
if($repostAttachment instanceof \openvk\Web\Models\Entities\Photo) {
if($attachment->isDeleted())
continue;
$repostAttachments[] = $this->getApiPhoto($repostAttachment);
/* Рекурсии, сука! Заказывали? */
}
}
}
if ($attachment->isPostedOnBehalfOfGroup())
$groups[] = $attachment->getOwner()->getId();
else
$profiles[] = $attachment->getOwner()->getId();
$post_source = [];
if($attachment->getPlatform(true) === NULL) {
$post_source = (object)["type" => "vk"];
} else {
@ -287,6 +300,7 @@ final class Wall extends VKAPIRequestHandler
"can_archive" => false, # TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(),
"post_source" => $post_source,
"attachments" => $attachments,
"comments" => (object)[
@ -304,7 +318,7 @@ final class Wall extends VKAPIRequestHandler
"user_reposted" => 0
]
];
if ($from_id > 0)
$profiles[] = $from_id;
else
@ -334,7 +348,8 @@ final class Wall extends VKAPIRequestHandler
"screen_name" => $user->getShortCode(),
"photo_50" => $user->getAvatarUrl(),
"photo_100" => $user->getAvatarUrl(),
"online" => $user->isOnline()
"online" => $user->isOnline(),
"verified" => $user->isVerified()
];
}
@ -349,6 +364,7 @@ final class Wall extends VKAPIRequestHandler
"photo_50" => $group->getAvatarUrl(),
"photo_100" => $group->getAvatarUrl(),
"photo_200" => $group->getAvatarUrl(),
"verified" => $group->isVerified()
];
}
@ -363,13 +379,13 @@ final class Wall extends VKAPIRequestHandler
];
}
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = ""): object
{
$this->requireUser();
$this->willExecuteWriteAction();
$owner_id = intval($owner_id);
$wallOwner = ($owner_id > 0 ? (new UsersRepo)->get($owner_id) : (new ClubsRepo)->get($owner_id * -1))
?? $this->fail(18, "User was deleted or banned");
if($owner_id > 0)
@ -380,7 +396,7 @@ final class Wall extends VKAPIRequestHandler
else
$canPost = $wallOwner->canPost();
else
$canPost = false;
$canPost = false;
if($canPost == false) $this->fail(15, "Access denied");
@ -401,27 +417,7 @@ final class Wall extends VKAPIRequestHandler
if($signed == 1)
$flags |= 0b01000000;
# TODO: Compatible implementation of this
try {
$photo = NULL;
$video = NULL;
if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId())
$album = (new AlbumsRepo)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->getUser()->getId(), $message, $_FILES["photo"], $album, $anon);
}
if($_FILES["video"]["error"] === UPLOAD_ERR_OK)
$video = Video::fastMake($this->getUser()->getId(), $message, $_FILES["video"], $anon);
} catch(\DomainException $ex) {
$this->fail(-156, "The media file is corrupted");
} catch(ISE $ex) {
$this->fail(-156, "The media file is corrupted or too large ");
}
if(empty($message) && !$photo && !$video)
if(empty($message) && empty($attachments))
$this->fail(100, "Required parameter 'message' missing.");
try {
@ -437,11 +433,60 @@ final class Wall extends VKAPIRequestHandler
$this->fail(100, "One of the parameters specified was missing or invalid");
}
if(!is_null($photo))
$post->attach($photo);
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
# Аттачи такого вида: [тип][id владельца]_[id вложения]
# Пример: photo1_1
if(!is_null($video))
$post->attach($video);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
elseif(str_contains($attac, "note"))
$attachmentType = "note";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Invalid photo");
if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(43, "Access to photo denied");
$post->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(43, "Access to video denied");
$post->attach($attacc);
} elseif($attachmentType == "note") {
$attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Note does not exist");
if(!$attacc->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(11, "Access to note denied");
$post->attach($attacc);
}
}
}
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
@ -449,7 +494,7 @@ final class Wall extends VKAPIRequestHandler
return (object)["post_id" => $post->getVirtualId()];
}
function repost(string $object, string $message = "") {
function repost(string $object, string $message = "", int $group_id = 0) {
$this->requireUser();
$this->willExecuteWriteAction();
@ -462,14 +507,27 @@ final class Wall extends VKAPIRequestHandler
$nPost = new Post;
$nPost->setOwner($this->user->getId());
$nPost->setWall($this->user->getId());
if($group_id > 0) {
$club = (new ClubsRepo)->get($group_id);
if(!$club)
$this->fail(42, "Invalid group");
if(!$club->canBeModifiedBy($this->user))
$this->fail(16, "Access to group denied");
$nPost->setWall($group_id * -1);
} else {
$nPost->setWall($this->user->getId());
}
$nPost->setContent($message);
$nPost->setApi_Source_Name($this->getPlatform());
$nPost->save();
$nPost->attach($post);
if($post->getOwner(false)->getId() !== $this->user->getId() && !($post->getOwner() instanceof Club))
(new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit();
(new RepostNotification($post->getOwner(false), $post, $this->user))->emit();
return (object) [
"success" => 1, // 👍
@ -479,6 +537,7 @@ final class Wall extends VKAPIRequestHandler
];
}
function getComments(int $owner_id, int $post_id, bool $need_likes = true, int $offset = 0, int $count = 10, string $fields = "sex,screen_name,photo_50,photo_100,online_info,online", string $sort = "asc", bool $extended = false) {
$this->requireUser();
@ -486,7 +545,7 @@ final class Wall extends VKAPIRequestHandler
if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid");
$comments = (new CommentsRepo)->getCommentsByTarget($post, $offset+1, $count, $sort == "desc" ? "DESC" : "ASC");
$items = [];
$profiles = [];
@ -501,9 +560,11 @@ final class Wall extends VKAPIRequestHandler
foreach($comment->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment);
} elseif($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = $attachment->toVkApiStruct();
}
}
$item = [
"id" => $comment->getId(),
"from_id" => $oid,
@ -529,7 +590,7 @@ final class Wall extends VKAPIRequestHandler
"user_likes" => (int) $comment->hasLikeFrom($this->getUser()),
"can_publish" => 1
];
$items[] = $item;
if($extended == true)
$profiles[] = $comment->getOwner()->getId();
@ -558,12 +619,12 @@ final class Wall extends VKAPIRequestHandler
function getComment(int $owner_id, int $comment_id, bool $extended = false, string $fields = "sex,screen_name,photo_50,photo_100,online_info,online") {
$this->requireUser();
$comment = (new CommentsRepo)->get($comment_id); // один хуй айди всех комментов общий
$comment = (new CommentsRepo)->get($comment_id); # один хуй айди всех комментов общий
$profiles = [];
$attachments = [];
foreach($comment->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment);
@ -593,7 +654,7 @@ final class Wall extends VKAPIRequestHandler
"groups_can_post" => false,
]
];
if($extended == true)
$profiles[] = $comment->getOwner()->getId();
@ -609,25 +670,29 @@ final class Wall extends VKAPIRequestHandler
$response['profiles'] = (!empty($profiles) ? (new Users)->get(implode(',', $profiles), $fields) : []);
}
return $response;
}
function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0) {
function createComment(int $owner_id, int $post_id, string $message = "", int $from_group = 0, string $attachments = "") {
$this->requireUser();
$this->willExecuteWriteAction();
$post = (new PostsRepo)->getPostById($owner_id, $post_id);
if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid");
if(!$post || $post->isDeleted()) $this->fail(100, "Invalid post");
if($post->getTargetWall() < 0)
$club = (new ClubsRepo)->get(abs($post->getTargetWall()));
if(empty($message) && empty($attachments)) {
$this->fail(100, "Required parameter 'message' missing.");
}
$flags = 0;
if($from_group != 0 && !is_null($club) && $club->canBeModifiedBy($this->user))
$flags |= 0b10000000;
try {
$comment = new Comment;
$comment->setOwner($this->user->getId());
@ -640,11 +705,54 @@ final class Wall extends VKAPIRequestHandler
} catch (\LengthException $ex) {
$this->fail(1, "ошибка про то что коммент большой слишком");
}
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(11, "Access to photo denied");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(11, "Access to video denied");
$comment->attach($attacc);
}
}
}
if($post->getOwner()->getId() !== $this->user->getId())
if(($owner = $post->getOwner()) instanceof User)
(new CommentNotification($owner, $comment, $post, $this->user))->emit();
return (object) [
"comment_id" => $comment->getId(),
"parents_stack" => []
@ -659,12 +767,12 @@ final class Wall extends VKAPIRequestHandler
if(!$comment) $this->fail(100, "One of the parameters specified was missing or invalid");;
if(!$comment->canBeDeletedBy($this->user))
$this->fail(7, "Access denied");
$comment->delete();
return 1;
}
private function getApiPhoto($attachment) {
return [
"type" => "photo",
@ -690,7 +798,7 @@ final class Wall extends VKAPIRequestHandler
"votes" => $answer->votes
];
}
$userVote = array();
foreach($attachment->getUserVote($user) as $vote)
$userVote[] = $vote[0];

View file

@ -5,7 +5,7 @@ exceptions. It is still a work-in-progress functionality.
**Note**: requests to API are routed through
openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers.
[Documentation for API clients](https://docs.openvk.su/openvk_engine/api/description/)
[Documentation for API clients](https://docs.openvk.uk/openvk_engine/api/description/)
## Implementing API methods

View file

@ -66,4 +66,31 @@ class Album extends MediaCollection
{
return $this->has($photo);
}
function toVkApiStruct(?User $user = NULL, bool $need_covers = false, bool $photo_sizes = false): object
{
$res = (object) [];
$res->id = $this->getPrettyId();
$res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0;
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->description = $this->getDescription();
$res->created = $this->getCreationTime()->timestamp();
$res->updated = $this->getEditTime() ? $this->getEditTime()->timestamp() : NULL;
$res->size = $this->size();
$res->privacy_comment = 1;
$res->upload_by_admins_only = 1;
$res->comments_disabled = 0;
$res->can_upload = $this->canBeModifiedBy($user); # thisUser недоступен в entities
if($need_covers) {
$res->thumb_src = $this->getCoverURL();
if($photo_sizes) {
$res->sizes = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getVkApiSizes() : NULL;
}
}
return $res;
}
}

View file

@ -306,11 +306,14 @@ class Application extends RowModel
function delete(bool $softly = true): void
{
if($softly)
throw new \UnexpectedValueException("Can't delete apps softly.");
throw new \UnexpectedValueException("Can't delete apps softly."); // why
$cx = DatabaseConnection::i()->getContext();
$cx->table("app_users")->where("app", $this->getId())->delete();
parent::delete(false);
}
function getPublicationTime(): string
{ return tr("recently"); }
}

View file

@ -0,0 +1,66 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;
use openvk\Web\Models\Repositories\{Users};
use Nette\Database\Table\ActiveRow;
class Ban extends RowModel
{
protected $tableName = "bans";
function getId(): int
{
return $this->getRecord()->id;
}
function getReason(): ?string
{
return $this->getRecord()->reason;
}
function getUser(): ?User
{
return (new Users)->get($this->getRecord()->user);
}
function getInitiator(): ?User
{
return (new Users)->get($this->getRecord()->initiator);
}
function getStartTime(): int
{
return $this->getRecord()->iat;
}
function getEndTime(): int
{
return $this->getRecord()->exp;
}
function getTime(): int
{
return $this->getRecord()->time;
}
function isPermanent(): bool
{
return $this->getEndTime() === 0;
}
function isRemovedManually(): bool
{
return (bool) $this->getRecord()->removed_manually;
}
function isOver(): bool
{
return $this->isRemovedManually();
}
function whoRemoved(): ?User
{
return (new Users)->get($this->getRecord()->removed_by);
}
}

View file

@ -160,7 +160,7 @@ class Club extends RowModel
function canPost(): bool
{
return (bool) $this->getRecord()->wall;
return (bool) $this->getRecord()->wall;
}
@ -224,7 +224,7 @@ class Club extends RowModel
"shape" => "spline",
"color" => "#597da3",
],
"name" => $unique ? "Полный охват" : "Все просмотры",
"name" => $unique ? tr("full_coverage") : tr("all_views"),
],
"subs" => [
"x" => array_reverse(range(1, 7)),
@ -235,7 +235,7 @@ class Club extends RowModel
"color" => "#b05c91",
],
"fill" => "tozeroy",
"name" => $unique ? "Охват подписчиков" : "Просмотры подписчиков",
"name" => $unique ? tr("subs_coverage") : tr("subs_views"),
],
"viral" => [
"x" => array_reverse(range(1, 7)),
@ -246,7 +246,7 @@ class Club extends RowModel
"color" => "#4d9fab",
],
"fill" => "tozeroy",
"name" => $unique ? "Виральный охват" : "Виральные просмотры",
"name" => $unique ? tr("viral_coverage") : tr("viral_views"),
],
];
}
@ -262,17 +262,17 @@ class Club extends RowModel
return $subbed && ($this->getOpennesStatus() === static::CLOSED ? $this->isSubscriptionAccepted($user) : true);
}
function getFollowersQuery(): GroupedSelection
function getFollowersQuery(string $sort = "follower ASC"): GroupedSelection
{
$query = $this->getRecord()->related("subscriptions.target");
if($this->getOpennesStatus() === static::OPEN) {
$query = $query->where("model", "openvk\\Web\\Models\\Entities\\Club");
$query = $query->where("model", "openvk\\Web\\Models\\Entities\\Club")->order($sort);
} else {
return false;
}
return $query;
return $query->group("follower");
}
function getFollowersCount(): int
@ -280,9 +280,9 @@ class Club extends RowModel
return sizeof($this->getFollowersQuery());
}
function getFollowers(int $page = 1): \Traversable
function getFollowers(int $page = 1, int $perPage = 6, string $sort = "follower ASC"): \Traversable
{
$rels = $this->getFollowersQuery()->page($page, 6);
$rels = $this->getFollowersQuery($sort)->page($page, $perPage);
foreach($rels as $rel) {
$rel = (new Users)->get($rel->follower);
@ -351,15 +351,56 @@ class Club extends RowModel
}
function getWebsite(): ?string
{
return $this->getRecord()->website;
}
{
return $this->getRecord()->website;
}
function ban(string $reason): void
{
$this->setBlock_Reason($reason);
$this->save();
}
function unban(): void
{
$this->setBlock_Reason(null);
$this->save();
}
function getAlert(): ?string
{
return $this->getRecord()->alert;
}
function toVkApiStruct(?User $user = NULL): object
{
$res = [];
$res->id = $this->getId();
$res->name = $this->getName();
$res->screen_name = $this->getShortCode();
$res->is_closed = 0;
$res->deactivated = NULL;
$res->is_admin = $this->canBeModifiedBy($user);
if($this->canBeModifiedBy($user)) {
$res->admin_level = 3;
}
$res->is_member = $this->getSubscriptionStatus($user) ? 1 : 0;
$res->type = "group";
$res->photo_50 = $this->getAvatarUrl("miniscule");
$res->photo_100 = $this->getAvatarUrl("tiny");
$res->photo_200 = $this->getAvatarUrl("normal");
$res->can_create_topic = $this->canBeModifiedBy($user) ? 1 : ($this->isEveryoneCanCreateTopics() ? 1 : 0);
$res->can_post = $this->canBeModifiedBy($user) ? 1 : ($this->canPost() ? 1 : 0);
return (object) $res;
}
use Traits\TBackDrops;
use Traits\TSubscribable;
}

View file

@ -2,6 +2,7 @@
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Note};
class Comment extends Post
{
@ -52,4 +53,49 @@ class Comment extends Post
$this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) ||
$this->getTarget() instanceof Topic && $this->getTarget()->canBeModifiedBy($user);
}
function toVkApiStruct(?User $user = NULL, bool $need_likes = false, bool $extended = false, ?Note $note = NULL): object
{
$res = (object) [];
$res->id = $this->getId();
$res->from_id = $this->getOwner()->getId();
$res->date = $this->getPublicationTime()->timestamp();
$res->text = $this->getText(false);
$res->attachments = [];
$res->parents_stack = [];
if(!is_null($note)) {
$res->uid = $this->getOwner()->getId();
$res->nid = $note->getId();
$res->oid = $note->getOwner()->getId();
}
foreach($this->getChildren() as $attachment) {
if($attachment->isDeleted())
continue;
$res->attachments[] = $attachment->toVkApiStruct();
}
if($need_likes) {
$res->count = $this->getLikesCount();
$res->user_likes = (int)$this->hasLikeFrom($user);
$res->can_like = 1;
}
return $res;
}
function getURL(): string
{
return "/wall" . $this->getTarget()->getPrettyId() . "#_comment" . $this->getId();
}
function canBeEditedBy(?User $user = NULL): bool
{
if(!$user)
return false;
return $user->getId() == $this->getOwner(false)->getId();
}
}

View file

@ -92,7 +92,7 @@ class IP extends RowModel
$this->stateChanges("rate_limit_counter", $aCounter);
$this->stateChanges("rate_limit_violation_counter_start", $vCounterSessionStart);
$this->stateChanges("rate_limit_violation_counter", $vCounter);
$this->save();
$this->save(false);
}
}
@ -105,11 +105,11 @@ class IP extends RowModel
$this->stateChanges("ip", $ip);
}
function save(): void
function save($log): void
{
if(is_null($this->getRecord()))
$this->stateChanges("first_seen", time());
parent::save();
parent::save($log);
}
}

View file

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;
use openvk\Web\Models\Repositories\{Users};
use Nette\Database\Table\ActiveRow;
class NoSpamLog extends RowModel
{
protected $tableName = "noSpam_templates";
function getId(): int
{
return $this->getRecord()->id;
}
function getUser(): ?User
{
return (new Users)->get($this->getRecord()->user);
}
function getModel(): string
{
return $this->getRecord()->model;
}
function getRegex(): ?string
{
return $this->getRecord()->regex;
}
function getRequest(): ?string
{
return $this->getRecord()->request;
}
function getCount(): int
{
return $this->getRecord()->count;
}
function getTime(): DateTime
{
return new DateTime($this->getRecord()->time);
}
function getItems(): ?array
{
return explode(",", $this->getRecord()->items);
}
function getTypeRaw(): int
{
return $this->getRecord()->ban_type;
}
function getType(): string
{
switch ($this->getTypeRaw()) {
case 1: return "О";
case 2: return "Б";
case 3: return "ОБ";
default: return (string) $this->getTypeRaw();
}
}
function isRollbacked(): bool
{
return !is_null($this->getRecord()->rollback);
}
}

View file

@ -118,4 +118,24 @@ class Note extends Postable
{
return $this->getRecord()->source;
}
function toVkApiStruct(): object
{
$res = (object) [];
$res->type = "note";
$res->id = $this->getVirtualId();
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->text = $this->getText();
$res->date = $this->getPublicationTime()->timestamp();
$res->comments = $this->getCommentsCount();
$res->read_comments = $this->getCommentsCount();
$res->view_url = "/note".$this->getOwner()->getId()."_".$this->getId();
$res->privacy_view = 1;
$res->can_comment = 1;
$res->text_wiki = "r";
return $res;
}
}

View file

@ -296,25 +296,35 @@ class Photo extends Media
return (new Albums)->getAlbumByPhotoId($this);
}
function toVkApiStruct(): object
function toVkApiStruct(bool $photo_sizes = true, bool $extended = false): object
{
$res = (object) [];
$res->id = $res->pid = $this->getId();
$res->owner_id = $res->user_id = $this->getOwner()->getId()->getId();
$res->id = $res->pid = $this->getVirtualId();
$res->owner_id = $res->user_id = $this->getOwner()->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");
if($photo_sizes) {
$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");
}
if($extended) {
$res->likes = $this->getLikesCount(); # их нету но пусть будут
$res->comments = $this->getCommentsCount();
$res->tags = 0;
$res->can_comment = 1;
$res->can_repost = 0;
}
return $res;
}

View file

@ -99,6 +99,11 @@ class Post extends Postable
return (($this->getRecord()->flags & 0b00100000) > 0) && ($this->getRecord()->owner > 0);
}
function isUpdateAvatarMessage(): bool
{
return (($this->getRecord()->flags & 0b00010000) > 0) && ($this->getRecord()->owner > 0);
}
function isExplicit(): bool
{
return (bool) $this->getRecord()->nsfw;
@ -240,6 +245,20 @@ class Post extends Postable
$this->unwire();
$this->save();
}
function canBeEditedBy(?User $user = NULL): bool
{
if(!$user)
return false;
if($this->isDeactivationMessage() || $this->isUpdateAvatarMessage())
return false;
if($this->getTargetWall() > 0)
return $this->getPublicationTime()->timestamp() + WEEK > time() && $user->getId() == $this->getOwner(false)->getId();
return $user->getId() == $this->getOwner(false)->getId();
}
use Traits\TRichText;
}

View file

@ -34,7 +34,8 @@ abstract class Postable extends Attachable
$oid = (int) $this->getRecord()->owner;
if(!$real && $this->isAnonymous())
$oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"];
$oid = abs($oid);
if($oid > 0)
return (new Users)->get($oid);
else
@ -84,7 +85,7 @@ abstract class Postable extends Attachable
return sizeof(DB::i()->getContext()->table("likes")->where([
"model" => static::class,
"target" => $this->getRecord()->id,
]));
])->group("origin"));
}
# TODO add pagination
@ -166,9 +167,9 @@ abstract class Postable extends Attachable
$this->stateChanges("created", time());
$this->stateChanges("virtual_id", $pCount + 1);
} else {
} /*else {
$this->stateChanges("edited", time());
}
}*/
parent::save();
}

View file

@ -0,0 +1,154 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime;
use Nette\Database\Table\ActiveRow;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\Club;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\{Applications, Comments, Notes, Reports, Users, Posts, Photos, Videos, Clubs};
use Chandler\Database\DatabaseConnection as DB;
use Nette\InvalidStateException as ISE;
use Nette\Database\Table\Selection;
class Report extends RowModel
{
protected $tableName = "reports";
function getId(): int
{
return $this->getRecord()->id;
}
function getStatus(): int
{
return $this->getRecord()->status;
}
function getContentType(): string
{
return $this->getRecord()->type;
}
function getReason(): string
{
return $this->getRecord()->reason;
}
function getTime(): DateTime
{
return new DateTime($this->getRecord()->date);
}
function isDeleted(): bool
{
if ($this->getRecord()->deleted === 0)
{
return false;
} elseif ($this->getRecord()->deleted === 1) {
return true;
}
}
function authorId(): int
{
return $this->getRecord()->user_id;
}
function getUser(): User
{
return (new Users)->get((int) $this->getRecord()->user_id);
}
function getContentId(): int
{
return (int) $this->getRecord()->target_id;
}
function getContentObject()
{
if ($this->getContentType() == "post") return (new Posts)->get($this->getContentId());
else if ($this->getContentType() == "photo") return (new Photos)->get($this->getContentId());
else if ($this->getContentType() == "video") return (new Videos)->get($this->getContentId());
else if ($this->getContentType() == "group") return (new Clubs)->get($this->getContentId());
else if ($this->getContentType() == "comment") return (new Comments)->get($this->getContentId());
else if ($this->getContentType() == "note") return (new Notes)->get($this->getContentId());
else if ($this->getContentType() == "app") return (new Applications)->get($this->getContentId());
else if ($this->getContentType() == "user") return (new Users)->get($this->getContentId());
else return null;
}
function getAuthor(): RowModel
{
return (new Posts)->get($this->getContentId())->getOwner();
}
function getReportAuthor(): User
{
return (new Users)->get($this->getRecord()->user_id);
}
function banUser($initiator)
{
$reason = $this->getContentType() !== "user" ? ("**content-" . $this->getContentType() . "-" . $this->getContentId() . "**") : ("Подозрительная активность");
$this->getAuthor()->ban($reason, false, time() + $this->getAuthor()->getNewBanTime(), $initiator);
}
function deleteContent()
{
if ($this->getContentType() !== "user") {
$pubTime = $this->getContentObject()->getPublicationTime();
if (method_exists($this->getContentObject(), "getName")) {
$name = $this->getContentObject()->getName();
$placeholder = "$pubTime ($name)";
} else {
$placeholder = "$pubTime";
}
if ($this->getAuthor() instanceof Club) {
$name = $this->getAuthor()->getName();
$this->getAuthor()->getOwner()->adminNotify("Ваш контент, который опубликовали $placeholder в созданной вами группе \"$name\" был удалён модераторами инстанса. За повторные или серьёзные нарушения группу могут заблокировать.");
} else {
$this->getAuthor()->adminNotify("Ваш контент, который вы опубликовали $placeholder был удалён модераторами инстанса. За повторные или серьёзные нарушения вас могут заблокировать.");
}
$this->getContentObject()->delete($this->getContentType() !== "app");
}
$this->delete();
}
function getDuplicates(): \Traversable
{
return (new Reports)->getDuplicates($this->getContentType(), $this->getContentId(), $this->getId());
}
function getDuplicatesCount(): int
{
return count(iterator_to_array($this->getDuplicates()));
}
function hasDuplicates(): bool
{
return $this->getDuplicatesCount() > 0;
}
function getContentName(): string
{
if (method_exists($this->getContentObject(), "getCanonicalName"))
return $this->getContentObject()->getCanonicalName();
return $this->getContentType() . " #" . $this->getContentId();
}
public function delete(bool $softly = true): void
{
if ($this->hasDuplicates()) {
foreach ($this->getDuplicates() as $duplicate) {
$duplicate->setDeleted(1);
$duplicate->save();
}
}
$this->setDeleted(1);
$this->save();
}
}

View file

@ -68,6 +68,12 @@ class Topic extends Postable
return isset($array[0]) ? $array[0] : NULL;
}
function getFirstComment(): ?Comment
{
$array = iterator_to_array($this->getComments(1));
return $array[0] ?? NULL;
}
function getUpdateTime(): DateTime
{
$lastComment = $this->getLastComment();
@ -83,4 +89,40 @@ class Topic extends Postable
$this->unwire();
$this->save();
}
function toVkApiStruct(int $preview = 0, int $preview_length = 90): object
{
$res = (object)[];
$res->id = $this->getId();
$res->title = $this->getTitle();
$res->created = $this->getPublicationTime()->timestamp();
if($this->getOwner() instanceof User) {
$res->created_by = $this->getOwner()->getId();
} else {
$res->created_by = $this->getOwner()->getId() * -1;
}
$res->updated = $this->getUpdateTime()->timestamp();
if($this->getLastComment()) {
if($this->getLastComment()->getOwner() instanceof User) {
$res->updated_by = $this->getLastComment()->getOwner()->getId();
} else {
$res->updated_by = $this->getLastComment()->getOwner()->getId() * -1;
}
}
$res->is_closed = (int)$this->isClosed();
$res->is_fixed = (int)$this->isPinned();
$res->comments = $this->getCommentsCount();
if($preview == 1) {
$res->first_comment = $this->getFirstComment() ? ovk_proc_strtr($this->getFirstComment()->getText(false), $preview_length) : NULL;
$res->last_comment = $this->getLastComment() ? ovk_proc_strtr($this->getLastComment()->getText(false), $preview_length) : NULL;
}
return $res;
}
}

View file

@ -5,7 +5,7 @@ use openvk\Web\Themes\{Themepack, Themepacks};
use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
use openvk\Web\Models\Repositories\{Photos, Users, Clubs, Albums, Gifts, Notifications};
use openvk\Web\Models\Repositories\{Applications, Bans, Comments, Notes, Posts, Users, Clubs, Albums, Gifts, Notifications, Videos, Photos};
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
@ -39,11 +39,14 @@ class User extends RowModel
$query = "SELECT id FROM\n" . file_get_contents(__DIR__ . "/../sql/$filename.tsql");
$query .= "\n LIMIT " . $limit . " OFFSET " . ( ($page - 1) * $limit );
$ids = [];
$rels = DatabaseConnection::i()->getConnection()->query($query, $id, $id);
foreach($rels as $rel) {
$rel = (new Users)->get($rel->id);
if(!$rel) continue;
if(in_array($rel->getId(), $ids)) continue;
$ids[] = $rel->getId();
yield $rel;
}
}
@ -238,11 +241,60 @@ class User extends RowModel
return $this->getRecord()->alert;
}
function getBanReason(): ?string
function getTextForContentBan(string $type): string
{
switch ($type) {
case "post": return "за размещение от Вашего лица таких <b>записей</b>:";
case "photo": return "за размещение от Вашего лица таких <b>фотографий</b>:";
case "video": return "за размещение от Вашего лица таких <b>видеозаписей</b>:";
case "group": return "за подозрительное вступление от Вашего лица <b>в группу:</b>";
case "comment": return "за размещение от Вашего лица таких <b>комментариев</b>:";
case "note": return "за размещение от Вашего лица таких <b>заметок</b>:";
case "app": return "за создание от Вашего имени <b>подозрительных приложений</b>.";
default: return "за размещение от Вашего лица такого <b>контента</b>:";
}
}
function getRawBanReason(): ?string
{
return $this->getRecord()->block_reason;
}
function getBanReason(?string $for = null)
{
$ban = (new Bans)->get((int) $this->getRecord()->block_reason);
if (!$ban || $ban->isOver()) return null;
$reason = $ban->getReason();
preg_match('/\*\*content-(post|photo|video|group|comment|note|app|noSpamTemplate|user)-(\d+)\*\*$/', $reason, $matches);
if (sizeof($matches) === 3) {
$content_type = $matches[1]; $content_id = (int) $matches[2];
if (in_array($content_type, ["noSpamTemplate", "user"])) {
$reason = "Подозрительная активность";
} else {
if ($for !== "banned") {
$reason = "Подозрительная активность";
} else {
$reason = [$this->getTextForContentBan($content_type), $content_type];
switch ($content_type) {
case "post": $reason[] = (new Posts)->get($content_id); break;
case "photo": $reason[] = (new Photos)->get($content_id); break;
case "video": $reason[] = (new Videos)->get($content_id); break;
case "group": $reason[] = (new Clubs)->get($content_id); break;
case "comment": $reason[] = (new Comments)->get($content_id); break;
case "note": $reason[] = (new Notes)->get($content_id); break;
case "app": $reason[] = (new Applications)->get($content_id); break;
case "user": $reason[] = (new Users)->get($content_id); break;
default: $reason[] = null;
}
}
}
}
return $reason;
}
function getBanInSupportReason(): ?string
{
return $this->getRecord()->block_in_support_reason;
@ -410,6 +462,7 @@ class User extends RowModel
"news",
"links",
"poster",
"apps"
],
])->get($id);
}
@ -830,7 +883,7 @@ class User extends RowModel
]);
}
function ban(string $reason, bool $deleteSubscriptions = true, ?int $unban_time = NULL): void
function ban(string $reason, bool $deleteSubscriptions = true, $unban_time = NULL, ?int $initiator = NULL): void
{
if($deleteSubscriptions) {
$subs = DatabaseConnection::i()->getContext()->table("subscriptions");
@ -843,8 +896,33 @@ class User extends RowModel
$subs->delete();
}
$this->setBlock_Reason($reason);
$this->setUnblock_time($unban_time);
$iat = time();
$ban = new Ban;
$ban->setUser($this->getId());
$ban->setReason($reason);
$ban->setInitiator($initiator);
$ban->setIat($iat);
$ban->setExp($unban_time !== "permanent" ? $unban_time : 0);
$ban->setTime($unban_time === "permanent" ? 0 : ($unban_time ? ($unban_time - $iat) : 0));
$ban->save();
$this->setBlock_Reason($ban->getId());
// $this->setUnblock_time($unban_time);
$this->save();
}
function unban(int $removed_by): void
{
$ban = (new Bans)->get((int) $this->getRawBanReason());
if (!$ban || $ban->isOver())
return;
$ban->setRemoved_Manually(true);
$ban->setRemoved_By($removed_by);
$ban->save();
$this->setBlock_Reason(NULL);
// $user->setUnblock_time(NULL);
$this->save();
}
@ -949,6 +1027,7 @@ class User extends RowModel
"news",
"links",
"poster",
"apps"
],
])->set($id, (int) $status)->toInteger();
@ -1013,7 +1092,7 @@ class User extends RowModel
{
$this->setOnline(time());
$this->setClient_name($platform);
$this->save();
$this->save(false);
return true;
}
@ -1031,7 +1110,7 @@ class User extends RowModel
function adminNotify(string $message): bool
{
$admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
$admId = (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
if(!$admId)
return false;
else if(is_null($admin = (new Users)->get($admId)))
@ -1096,7 +1175,11 @@ class User extends RowModel
function getUnbanTime(): ?string
{
return !is_null($this->getRecord()->unblock_time) ? date('d.m.Y', $this->getRecord()->unblock_time) : NULL;
$ban = (new Bans)->get((int) $this->getRecord()->block_reason);
if (!$ban || $ban->isOver() || $ban->isPermanent()) return null;
if ($this->canUnbanThemself()) return tr("today");
return date('d.m.Y', $ban->getEndTime());
}
function canUnbanThemself(): bool
@ -1104,10 +1187,57 @@ class User extends RowModel
if (!$this->isBanned())
return false;
if ($this->getRecord()->unblock_time > time() || $this->getRecord()->unblock_time == 0)
return false;
$ban = (new Bans)->get((int) $this->getRecord()->block_reason);
if (!$ban || $ban->isOver() || $ban->isPermanent()) return false;
return true;
return $ban->getEndTime() <= time() && !$ban->isPermanent();
}
function getNewBanTime()
{
$bans = iterator_to_array((new Bans)->getByUser($this->getid()));
if (!$bans || count($bans) === 0)
return 0;
$last_ban = end($bans);
if (!$last_ban) return 0;
if ($last_ban->isPermanent()) return "permanent";
$values = [0, 3600, 7200, 86400, 172800, 604800, 1209600, 3024000, 9072000];
$response = 0;
$i = 0;
foreach ($values as $value) {
$i++;
if ($last_ban->getTime() === 0 && $value === 0) continue;
if ($last_ban->getTime() < $value) {
$response = $value;
break;
} else if ($last_ban->getTime() >= $value) {
if ($i < count($values)) continue;
$response = "permanent";
break;
}
}
return $response;
}
function toVkApiStruct(): object
{
$res = (object) [];
$res->id = $this->getId();
$res->first_name = $this->getFirstName();
$res->last_name = $this->getLastName();
$res->deactivated = $this->isDeactivated();
$res->photo_50 = $this->getAvatarURL();
$res->photo_100 = $this->getAvatarURL("tiny");
$res->photo_200 = $this->getAvatarURL("normal");
$res->photo_id = !is_null($this->getAvatarPhoto()) ? $this->getAvatarPhoto()->getPrettyId() : NULL;
# TODO: Perenesti syuda vsyo ostalnoyie
return $res;
}
use Traits\TBackDrops;

View file

@ -117,6 +117,7 @@ class Video extends Media
function getApiStructure(): object
{
$fromYoutube = $this->getType() == Video::TYPE_EMBED;
return (object)[
"type" => "video",
"video" => [
@ -145,10 +146,11 @@ class Video extends Media
"user_id" => $this->getOwner()->getId(),
"title" => $this->getName(),
"is_favorite" => false,
"player" => $this->getURL(),
"files" => [
"player" => !$fromYoutube ? $this->getURL() : $this->getVideoDriver()->getURL(),
"files" => !$fromYoutube ? [
"mp4_480" => $this->getURL()
],
] : NULL,
"platform" => $fromYoutube ? "youtube" : NULL,
"added" => 0,
"repeat" => 0,
"type" => "video",
@ -165,6 +167,11 @@ class Video extends Media
];
}
function toVkApiStruct(): object
{
return $this->getApiStructure();
}
function setLink(string $link): string
{
if(preg_match(file_get_contents(__DIR__ . "/../VideoDrivers/regex/youtube.txt"), $link, $matches)) {
@ -195,11 +202,14 @@ class Video extends Media
$this->save();
}
static function fastMake(int $owner, string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
{
if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
exit(VIDEOS_FRIENDLY_ERROR);
$video = new Video;
$video->setOwner($owner);
$video->setName("Unnamed Video.ogv");
$video->setName(ovk_proc_strtr($name, 61));
$video->setDescription(ovk_proc_strtr($description, 300));
$video->setAnonymous($anon);
$video->setCreated(time());

View file

@ -123,4 +123,14 @@ class Albums
return $dbalbum->collection ? $this->get($dbalbum->collection) : null;
}
function getAlbumByOwnerAndId(int $owner, int $id)
{
$album = $this->albums->where([
"owner" => $owner,
"id" => $id
])->fetch();
return new Album($album);
}
}

View file

@ -66,4 +66,12 @@ class Applications
{
return sizeof($this->appRels->where("user", $user->getId()));
}
function find(string $query, array $pars = [], string $sort = "id"): Util\EntityStream
{
$query = "%$query%";
$result = $this->apps->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("enabled", 1);
return new Util\EntityStream("Application", $result->order("$sort"));
}
}

View file

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection as DB;
use Nette\Database\Table\{ActiveRow, Selection};
use openvk\Web\Models\Entities\Ban;
class Bans
{
private $context;
private $bans;
function __construct()
{
$this->context = DB::i()->getContext();
$this->bans = $this->context->table("bans");
}
function toBan(?ActiveRow $ar): ?Ban
{
return is_null($ar) ? NULL : new Ban($ar);
}
function get(int $id): ?Ban
{
return $this->toBan($this->bans->get($id));
}
function getByUser(int $user_id): \Traversable
{
foreach ($this->bans->where("user", $user_id) as $ban)
yield new Ban($ban);
}
}

View file

@ -28,7 +28,8 @@ class ChandlerUsers
function getById(string $UUID): ?ChandlerUser
{
return new ChandlerUser($this->users->where("id", $UUID)->fetch());
$user = $this->users->where("id", $UUID)->fetch();
return $user ? new ChandlerUser($user) : NULL;
}
function getList(int $page = 1): \Traversable

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Repositories\Aliases;
use openvk\Web\Models\Entities\{Club, Manager};
use openvk\Web\Models\Repositories\{Aliases, Users};
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
@ -9,11 +9,13 @@ class Clubs
{
private $context;
private $clubs;
private $coadmins;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->clubs = $this->context->table("groups");
$this->context = DatabaseConnection::i()->getContext();
$this->clubs = $this->context->table("groups");
$this->coadmins = $this->context->table("group_coadmins");
}
private function toClub(?ActiveRow $ar): ?Club
@ -41,12 +43,12 @@ class Clubs
return $this->toClub($this->clubs->get($id));
}
function find(string $query, int $page = 1, ?int $perPage = NULL): \Traversable
function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable
{
$query = "%$query%";
$result = $this->clubs->where("name LIKE ? OR about LIKE ?", $query, $query);
return new Util\EntityStream("Club", $result);
return new Util\EntityStream("Club", $result->order($sort));
}
function getCount(): int
@ -70,6 +72,26 @@ class Clubs
];
*/
}
function getWriteableClubs(int $id): \Traversable
{
$result = $this->clubs->where("owner", $id);
$coadmins = $this->coadmins->where("user", $id);
foreach($result as $entry) {
yield new Club($entry);
}
foreach($coadmins as $coadmin) {
$cl = new Manager($coadmin);
yield $cl->getClub();
}
}
function getWriteableClubsCount(int $id): int
{
return sizeof($this->clubs->where("owner", $id)) + sizeof($this->coadmins->where("user", $id));
}
use \Nette\SmartObject;
}

View file

@ -59,4 +59,35 @@ class Comments
"deleted" => false,
]));
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->comments->where("content LIKE ?", $query)->where("deleted", 0);
$nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
case "after":
$result->where("created > ?", $paramValue);
break;
}
}
}
return new Util\EntityStream("Comment", $result->order("$sort"));
}
}

View file

@ -0,0 +1,49 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\User;
class CurrentUser
{
private static $instance = null;
private $user;
private $ip;
private $useragent;
public function __construct(?User $user = NULL, ?string $ip = NULL, ?string $useragent = NULL)
{
if ($user)
$this->user = $user;
if ($ip)
$this->ip = $ip;
if ($useragent)
$this->useragent = $useragent;
}
public static function get($user, $ip, $useragent)
{
if (self::$instance === null) self::$instance = new self($user, $ip, $useragent);
return self::$instance;
}
public function getUser(): User
{
return $this->user;
}
public function getIP(): string
{
return $this->ip;
}
public function getUserAgent(): string
{
return $this->useragent;
}
public static function i()
{
return self::$instance;
}
}

View file

@ -24,7 +24,7 @@ class IPs
if(!$res) {
$res = new IP;
$res->setIp($ip);
$res->save();
$res->save(false);
return $res;
}

View file

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\NoSpamLog;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
class NoSpamLogs
{
private $context;
private $noSpamLogs;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->noSpamLogs = $this->context->table("noSpam_templates");
}
private function toNoSpamLog(?ActiveRow $ar): ?NoSpamLog
{
return is_null($ar) ? NULL : new NoSpamLog($ar);
}
function get(int $id): ?NoSpamLog
{
return $this->toNoSpamLog($this->noSpamLogs->get($id));
}
function getList(array $filter = []): \Traversable
{
foreach ($this->noSpamLogs->where($filter)->order("`id` DESC") as $log)
yield new NoSpamLog($log);
}
}

View file

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

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Entities\{Photo, User};
use Chandler\Database\DatabaseConnection;
class Photos
@ -32,4 +32,15 @@ class Photos
return new Photo($photo);
}
function getEveryUserPhoto(User $user): \Traversable
{
$photos = $this->photos->where([
"owner" => $user->getId()
]);
foreach($photos as $photo) {
yield new Photo($photo);
}
}
}

View file

@ -99,7 +99,39 @@ class Posts
return NULL;
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0);
$nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
case "after":
$result->where("created > ?", $paramValue);
break;
}
}
}
return new Util\EntityStream("Post", $result->order("$sort"));
}
function getPostCountOnUserWall(int $user): int
{
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0]));

View file

@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Report;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
class Reports
{
private $context;
private $reports;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->reports = $this->context->table("reports");
}
private function toReport(?ActiveRow $ar): ?Report
{
return is_null($ar) ? NULL : new Report($ar);
}
function getReports(int $state = 0, int $page = 1, ?string $type = NULL, ?bool $pagination = true): \Traversable
{
$filter = ["deleted" => 0];
if ($type) $filter["type"] = $type;
$reports = $this->reports->where($filter)->order("created DESC")->group("target_id, type");
if ($pagination)
$reports = $reports->page($page, 15);
foreach($reports as $t)
yield new Report($t);
}
function getReportsCount(int $state = 0): int
{
return sizeof($this->reports->where(["deleted" => 0, "type" => $state])->group("target_id, type"));
}
function get(int $id): ?Report
{
return $this->toReport($this->reports->get($id));
}
function getByContentId(int $id): ?Report
{
$post = $this->reports->where(["deleted" => 0, "content_id" => $id])->fetch();
if($post)
return new Report($post);
else
return null;
}
function getDuplicates(string $type, int $target_id, ?int $orig = NULL, ?int $user_id = NULL): \Traversable
{
$filter = ["deleted" => 0, "type" => $type, "target_id" => $target_id];
if ($orig) $filter[] = "id != $orig";
if ($user_id) $filter["user_id"] = $user_id;
foreach ($this->reports->where($filter) as $report)
yield new Report($report);
}
use \Nette\SmartObject;
}

View file

@ -44,17 +44,96 @@ class Users
return $alias->getUser();
}
function getByChandlerUser(ChandlerUser $user): ?User
function getByChandlerUser(?ChandlerUser $user): ?User
{
return $this->toUser($this->users->where("user", $user->getId())->fetch());
return $user ? $this->toUser($this->users->where("user", $user->getId())->fetch()) : NULL;
}
function find(string $query): Util\EntityStream
function find(string $query, array $pars = [], string $sort = "id DESC"): Util\EntityStream
{
$query = "%$query%";
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo, shortcode) LIKE ?", $query)->where("deleted", 0);
return new Util\EntityStream("User", $result);
$notNullParams = [];
$nnparamsCount = 0;
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after" && $paramName != "gender" && $paramName != "maritalstatus" && $paramName != "politViews" && $paramName != "doNotSearchMe")
$paramValue != NULL ? $notNullParams += ["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams += ["$paramName" => "$paramValue"] : NULL;
$nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "hometown":
$result->where("hometown LIKE ?", $paramValue);
break;
case "city":
$result->where("city LIKE ?", $paramValue);
break;
case "maritalstatus":
$result->where("marital_status ?", $paramValue);
break;
case "status":
$result->where("status LIKE ?", $paramValue);
break;
case "politViews":
$result->where("polit_views ?", $paramValue);
break;
case "email":
$result->where("email_contact LIKE ?", $paramValue);
break;
case "telegram":
$result->where("telegram LIKE ?", $paramValue);
break;
case "site":
$result->where("telegram LIKE ?", $paramValue);
break;
case "address":
$result->where("address LIKE ?", $paramValue);
break;
case "is_online":
$result->where("online >= ?", time() - 900);
break;
case "interests":
$result->where("interests LIKE ?", $paramValue);
break;
case "fav_mus":
$result->where("fav_music LIKE ?", $paramValue);
break;
case "fav_films":
$result->where("fav_films LIKE ?", $paramValue);
break;
case "fav_shows":
$result->where("fav_shows LIKE ?", $paramValue);
break;
case "fav_books":
$result->where("fav_books LIKE ?", $paramValue);
break;
case "fav_quote":
$result->where("fav_quote LIKE ?", $paramValue);
break;
case "before":
$result->where("UNIX_TIMESTAMP(since) < ?", $paramValue);
break;
case "after":
$result->where("UNIX_TIMESTAMP(since) > ?", $paramValue);
break;
case "gender":
$result->where("sex ?", $paramValue);
break;
case "doNotSearchMe":
$result->where("id !=", $paramValue);
break;
}
}
}
return new Util\EntityStream("User", $result->order($sort));
}
function getStatistics(): object

View file

@ -45,4 +45,36 @@ class Videos
{
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0]));
}
function find(string $query = "", array $pars = [], string $sort = "id"): Util\EntityStream
{
$query = "%$query%";
$notNullParams = [];
foreach($pars as $paramName => $paramValue)
if($paramName != "before" && $paramName != "after")
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0);
$nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) {
foreach($notNullParams as $paramName => $paramValue) {
switch($paramName) {
case "before":
$result->where("created < ?", $paramValue);
break;
case "after":
$result->where("created > ?", $paramValue);
break;
}
}
}
return new Util\EntityStream("Video", $result->order("$sort"));
}
}

View file

@ -1,4 +1,4 @@
(SELECT follower AS __id FROM
(SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
LEFT JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -1,4 +1,4 @@
(SELECT follower AS __id FROM
(SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
INNER JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -1,4 +1,4 @@
(SELECT follower AS __id FROM
(SELECT DISTINCT(follower) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
INNER JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -1,4 +1,4 @@
(SELECT target AS __id FROM
(SELECT DISTINCT(target) AS __id FROM
(SELECT follower FROM subscriptions WHERE target=? AND model="openvk\\Web\\Models\\Entities\\User") u0
RIGHT JOIN
(SELECT target FROM subscriptions WHERE follower=? AND model="openvk\\Web\\Models\\Entities\\User") u1

View file

@ -141,6 +141,6 @@ final class AboutPresenter extends OpenVKPresenter
function renderDev(): void
{
$this->redirect("https://docs.openvk.su/");
$this->redirect("https://docs.openvk.uk/");
}
}

View file

@ -1,7 +1,9 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use Chandler\Database\Log;
use Chandler\Database\Logs;
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink};
use openvk\Web\Models\Repositories\{ChandlerGroups, ChandlerUsers, Users, Clubs, Vouchers, Gifts, BannedLinks};
use openvk\Web\Models\Repositories\{Bans, ChandlerGroups, ChandlerUsers, Photos, Posts, Users, Clubs, Videos, Vouchers, Gifts, BannedLinks};
use Chandler\Database\DatabaseConnection;
final class AdminPresenter extends OpenVKPresenter
@ -12,6 +14,7 @@ final class AdminPresenter extends OpenVKPresenter
private $gifts;
private $bannedLinks;
private $chandlerGroups;
private $logs;
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts, BannedLinks $bannedLinks, ChandlerGroups $chandlerGroups)
{
@ -21,6 +24,7 @@ final class AdminPresenter extends OpenVKPresenter
$this->gifts = $gifts;
$this->bannedLinks = $bannedLinks;
$this->chandlerGroups = $chandlerGroups;
$this->logs = DatabaseConnection::i()->getContext()->table("ChandlerLogs");
parent::__construct();
}
@ -86,6 +90,9 @@ final class AdminPresenter extends OpenVKPresenter
$query = "INSERT INTO `ChandlerACLRelations` (`user`, `group`) VALUES ('" . $user->getChandlerGUID() . "', '" . $this->postParam("add-to-group") . "')";
DatabaseConnection::i()->getConnection()->query($query);
}
if($this->postParam("password")) {
$user->getChandlerUser()->updatePassword($this->postParam("password"));
}
$user->save();
@ -125,7 +132,8 @@ final class AdminPresenter extends OpenVKPresenter
$club->save();
break;
case "ban":
$club->setBlock_reason($this->postParam("ban_reason"));
$reason = mb_strlen(trim($this->postParam("ban_reason"))) > 0 ? $this->postParam("ban_reason") : NULL;
$club->setBlock_reason($reason);
$club->save();
break;
}
@ -275,7 +283,7 @@ final class AdminPresenter extends OpenVKPresenter
$this->notFound();
$gift->delete();
$this->flashFail("succ", "Gift moved successfully", "This gift will now be in <b>Recycle Bin</b>.");
$this->flashFail("succ", tr("admin_gift_moved_successfully"), tr("admin_gift_moved_to_recycle"));
break;
case "copy":
case "move":
@ -294,7 +302,7 @@ final class AdminPresenter extends OpenVKPresenter
$catTo->addGift($gift);
$name = $catTo->getName();
$this->flash("succ", "Gift moved successfully", "This gift will now be in <b>$name</b>.");
$this->flash("succ", tr("admin_gift_moved_successfully"), "This gift will now be in <b>$name</b>.");
$this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/");
break;
default:
@ -325,10 +333,10 @@ final class AdminPresenter extends OpenVKPresenter
$gift->setUsages((int) $this->postParam("usages"));
if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) {
if(!$gift->setImage($_FILES["pic"]["tmp_name"]))
$this->flashFail("err", "Не удалось сохранить подарок", "Изображение подарка кривое.");
$this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_bad_image"));
} else if($gen) {
# If there's no gift pic but it's newly created
$this->flashFail("err", "Не удалось сохранить подарок", "Пожалуйста, загрузите изображение подарка.");
$this->flashFail("err", tr("error_when_saving_gift"), tr("error_when_saving_gift_no_image"));
}
$gift->save();
@ -352,13 +360,19 @@ final class AdminPresenter extends OpenVKPresenter
{
$this->assertNoCSRF();
$unban_time = strtotime($this->queryParam("date")) ?: NULL;
if (str_contains($this->queryParam("reason"), "*"))
exit(json_encode([ "error" => "Incorrect reason" ]));
$unban_time = strtotime($this->queryParam("date")) ?: "permanent";
$user = $this->users->get($id);
if(!$user)
exit(json_encode([ "error" => "User does not exist" ]));
$user->ban($this->queryParam("reason"), true, $unban_time);
if ($this->queryParam("incr"))
$unban_time = time() + $user->getNewBanTime();
$user->ban($this->queryParam("reason"), true, $unban_time, $this->user->identity->getId());
exit(json_encode([ "success" => true, "reason" => $this->queryParam("reason") ]));
}
@ -369,9 +383,17 @@ final class AdminPresenter extends OpenVKPresenter
$user = $this->users->get($id);
if(!$user)
exit(json_encode([ "error" => "User does not exist" ]));
$ban = (new Bans)->get((int)$user->getRawBanReason());
if (!$ban || $ban->isOver())
exit(json_encode([ "error" => "User is not banned" ]));
$ban->setRemoved_Manually(true);
$ban->setRemoved_By($this->user->identity->getId());
$ban->save();
$user->setBlock_Reason(NULL);
$user->setUnblock_time(NULL);
// $user->setUnblock_time(NULL);
$user->save();
exit(json_encode([ "success" => true ]));
}
@ -457,6 +479,14 @@ final class AdminPresenter extends OpenVKPresenter
$this->redirect("/admin/bannedLinks");
}
function renderBansHistory(int $user_id) :void
{
$user = (new Users)->get($user_id);
if (!$user) $this->notFound();
$this->template->bans = (new Bans)->getByUser($user_id);
}
function renderChandlerGroups(): void
{
$this->template->groups = (new ChandlerGroups)->getList();
@ -547,4 +577,38 @@ final class AdminPresenter extends OpenVKPresenter
$this->redirect("/admin/users/id" . $user->getId());
}
function renderLogs(): void
{
$filter = [];
if ($this->queryParam("id")) {
$id = (int) $this->queryParam("id");
$filter["id"] = $id;
$this->template->id = $id;
}
if ($this->queryParam("type") !== NULL && $this->queryParam("type") !== "any") {
$type = in_array($this->queryParam("type"), [0, 1, 2, 3]) ? (int) $this->queryParam("type") : 0;
$filter["type"] = $type;
$this->template->type = $type;
}
if ($this->queryParam("uid")) {
$user = $this->queryParam("uid");
$filter["user"] = $user;
$this->template->user = $user;
}
if ($this->queryParam("obj_id")) {
$obj_id = (int) $this->queryParam("obj_id");
$filter["object_id"] = $obj_id;
$this->template->obj_id = $obj_id;
}
if ($this->queryParam("obj_type") !== NULL && $this->queryParam("obj_type") !== "any") {
$obj_type = "openvk\\Web\\Models\\Entities\\" . $this->queryParam("obj_type");
$filter["object_model"] = $obj_type;
$this->template->obj_type = $obj_type;
}
$this->template->logs = (new Logs)->search($filter);
$this->template->object_types = (new Logs)->getTypes();
}
}

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{IP, User, PasswordReset, EmailVerification};
use openvk\Web\Models\Repositories\{IPs, Users, Restores, Verifications};
use openvk\Web\Models\Repositories\{Bans, IPs, Users, Restores, Verifications, Logs};
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator;
use Chandler\Session\Session;
@ -110,7 +110,7 @@ final class AuthPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_register"), tr("user_already_exists"));
$user->setUser($chUser->getId());
$user->save();
$user->save(false);
if(!is_null($referer)) {
$user->toggleSubscription($referer);
@ -130,7 +130,9 @@ final class AuthPresenter extends OpenVKPresenter
}
$this->authenticator->authenticate($chUser->getId());
(new Logs)->create($user->getId(), "profiles", "openvk\\Web\\Models\\Entities\\User", 0, $user, $user, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]);
$this->redirect("/id" . $user->getId());
$user->save();
}
}
@ -207,6 +209,9 @@ final class AuthPresenter extends OpenVKPresenter
function renderFinishRestoringPassword(): void
{
if(OPENVK_ROOT_CONF['openvk']['preferences']['security']['disablePasswordRestoring'])
$this->notFound();
$request = $this->restores->getByToken(str_replace(" ", "+", $this->queryParam("key")));
if(!$request || !$request->isStillValid()) {
$this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment"));
@ -241,6 +246,9 @@ final class AuthPresenter extends OpenVKPresenter
function renderRestore(): void
{
if(OPENVK_ROOT_CONF['openvk']['preferences']['security']['disablePasswordRestoring'])
$this->notFound();
if(!is_null($this->user))
$this->redirect($this->user->identity->getURL());
@ -339,9 +347,16 @@ final class AuthPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("forbidden"));
$user = $this->users->get($this->user->id);
$ban = (new Bans)->get((int)$user->getRawBanReason());
if (!$ban || $ban->isOver() || $ban->isPermanent())
$this->flashFail("err", tr("error"), tr("forbidden"));
$ban->setRemoved_Manually(2);
$ban->setRemoved_By($this->user->identity->getId());
$ban->save();
$user->setBlock_Reason(NULL);
$user->setUnblock_Time(NULL);
// $user->setUnblock_Time(NULL);
$user->save();
$this->flashFail("succ", tr("banned_unban_title"), tr("banned_unban_description"));

View file

@ -3,6 +3,8 @@ namespace openvk\Web\Presenters;
final class BlobPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
private function getDirName($dir): string
{
if(gettype($dir) === "integer") {

View file

@ -2,7 +2,7 @@
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\Notifications\CommentNotification;
use openvk\Web\Models\Repositories\{Comments, Clubs};
use openvk\Web\Models\Repositories\{Comments, Clubs, Videos};
final class CommentPresenter extends OpenVKPresenter
{
@ -22,6 +22,9 @@ final class CommentPresenter extends OpenVKPresenter
$comment = (new Comments)->get($id);
if(!$comment || $comment->isDeleted()) $this->notFound();
if ($comment->getTarget() instanceof Post && $comment->getTarget()->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!is_null($this->user)) $comment->toggleLike($this->user->identity);
@ -47,6 +50,12 @@ final class CommentPresenter extends OpenVKPresenter
$club = (new Clubs)->get(abs($entity->getTargetWall()));
else if($entity instanceof Topic)
$club = $entity->getClub();
if ($entity instanceof Post && $entity->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), tr("video_uploads_disabled"));
$flags = 0;
if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity))
@ -57,14 +66,13 @@ final class CommentPresenter extends OpenVKPresenter
try {
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"]);
} catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать пост", "Файл изображения повреждён, слишком велик или одна сторона изображения в разы больше другой.");
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_when_publishing_comment_description"));
}
}
# TODO move to trait
try {
$photo = NULL;
$video = NULL;
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if($wall > 0 && $wall === $this->user->id)
@ -72,16 +80,31 @@ final class CommentPresenter extends OpenVKPresenter
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album);
}
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"]);
}
} catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_file_too_big"));
}
$videos = [];
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
}
}
if(empty($this->postParam("text")) && !$photo && !$video)
$this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий пустой или слишком большой.");
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty"));
try {
$comment = new Comment;
@ -93,14 +116,15 @@ final class CommentPresenter extends OpenVKPresenter
$comment->setFlags($flags);
$comment->save();
} catch (\LengthException $ex) {
$this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой.");
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big"));
}
if(!is_null($photo))
$comment->attach($photo);
if(!is_null($video))
$comment->attach($video);
if(sizeof($videos) > 0)
foreach($videos as $vid)
$comment->attach($vid);
if($entity->getOwner()->getId() !== $this->user->identity->getId())
if(($owner = $entity->getOwner()) instanceof User)
@ -115,7 +139,7 @@ final class CommentPresenter extends OpenVKPresenter
if($mentionee instanceof User)
(new MentionNotification($mentionee, $entity, $comment->getOwner(), strip_tags($comment->getText())))->emit();
$this->flashFail("succ", "Комментарий добавлен", "Ваш комментарий появится на странице.");
$this->flashFail("succ", tr("comment_is_added"), tr("comment_is_added_desc"));
}
function renderDeleteComment(int $id): void
@ -126,13 +150,15 @@ final class CommentPresenter extends OpenVKPresenter
$comment = (new Comments)->get($id);
if(!$comment) $this->notFound();
if(!$comment->canBeDeletedBy($this->user->identity))
$this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс.");
$this->throwError(403, "Forbidden", tr("error_access_denied"));
if ($comment->getTarget() instanceof Post && $comment->getTarget()->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
$comment->delete();
$this->flashFail(
"succ",
"Успешно",
"Этот комментарий больше не будет показыватся.<br/><a href='/al_comments/spam?$id'>Отметить как спам</a>?"
tr("success"),
tr("comment_will_not_appear")
);
}
}

View file

@ -49,7 +49,7 @@ final class GiftsPresenter extends OpenVKPresenter
$user = $this->users->get((int) ($this->queryParam("user") ?? 0));
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
if(!$user || !$cat)
$this->flashFail("err", "Не удалось подарить", "Пользователь или набор не существуют.");
$this->flashFail("err", tr("error_when_gifting"), tr("error_user_not_exists"));
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
$gifts = $cat->getGifts($page, null, $this->template->count);
@ -66,14 +66,14 @@ final class GiftsPresenter extends OpenVKPresenter
$gift = $this->gifts->get((int) ($this->queryParam("elid") ?? 0));
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
if(!$user || !$cat || !$gift || !$cat->hasGift($gift))
$this->flashFail("err", "Не удалось подарить", "Не удалось подтвердить права на подарок.");
$this->flashFail("err", tr("error_when_gifting"), tr("error_no_rights_gifts"));
if(!$gift->canUse($this->user->identity))
$this->flashFail("err", "Не удалось подарить", "У вас больше не осталось таких подарков.");
$this->flashFail("err", tr("error_when_gifting"), tr("error_no_more_gifts"));
$coinsLeft = $this->user->identity->getCoins() - $gift->getPrice();
if($coinsLeft < 0)
$this->flashFail("err", "Не удалось подарить", "Ору нищ не пук.");
$this->flashFail("err", tr("error_when_gifting"), tr("error_no_money"));
$this->template->_template = "Gifts/Confirm.xml";
if($_SERVER["REQUEST_METHOD"] !== "POST") {
@ -91,7 +91,7 @@ final class GiftsPresenter extends OpenVKPresenter
$user->gift($this->user->identity, $gift, $comment, !is_null($this->postParam("anonymous")));
$gift->used();
$this->flash("succ", "Подарок отправлен", "Вы отправили подарок <b>" . $user->getFirstName() . "</b> за " . $gift->getPrice() . " голосов.");
$this->flash("succ", tr("gift_sent"), tr("gift_sent_desc", $user->getFirstName(), $gift->getPrice()));
$this->redirect($user->getURL());
}

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo};
use openvk\Web\Models\Entities\{Club, Photo, Post};
use Nette\InvalidStateException;
use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics};
@ -24,10 +24,14 @@ final class GroupPresenter extends OpenVKPresenter
if(!$club) {
$this->notFound();
} else {
$this->template->albums = (new Albums)->getClubAlbums($club, 1, 3);
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
$this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
if ($club->isBanned()) {
$this->template->_template = "Group/Banned.xml";
} else {
$this->template->albums = (new Albums)->getClubAlbums($club, 1, 3);
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
$this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
}
$this->template->club = $club;
}
@ -39,7 +43,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->willExecuteWriteAction();
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!empty($this->postParam("name")))
if(!empty($this->postParam("name")) && mb_strlen(trim($this->postParam("name"))) > 0)
{
$club = new Club;
$club->setName($this->postParam("name"));
@ -50,7 +54,7 @@ final class GroupPresenter extends OpenVKPresenter
$club->save();
} catch(\PDOException $ex) {
if($ex->getCode() == 23000)
$this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору.");
$this->flashFail("err", tr("error"), tr("error_on_server_side"));
else
throw $ex;
}
@ -58,7 +62,7 @@ final class GroupPresenter extends OpenVKPresenter
$club->toggleSubscription($this->user->identity);
$this->redirect("/club" . $club->getId());
}else{
$this->flashFail("err", "Ошибка", "Вы не ввели название группы.");
$this->flashFail("err", tr("error"), tr("error_no_group_name"));
}
}
}
@ -72,6 +76,7 @@ final class GroupPresenter extends OpenVKPresenter
$club = $this->clubs->get((int) $this->postParam("id"));
if(!$club) exit("Invalid state");
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$club->toggleSubscription($this->user->identity);
@ -83,6 +88,8 @@ final class GroupPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
$this->template->club = $this->clubs->get($id);
if ($this->template->club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1";
if($this->template->onlyShowManagers) {
$this->template->followers = NULL;
@ -118,12 +125,14 @@ final class GroupPresenter extends OpenVKPresenter
$this->badRequest();
$club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$user = (new Users)->get((int) $user);
if(!$user || !$club)
$this->notFound();
if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
$this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if(!is_null($hidden)) {
if($club->getOwner()->getId() == $user->getId()) {
@ -141,9 +150,9 @@ final class GroupPresenter extends OpenVKPresenter
}
if($hidden) {
$this->flashFail("succ", "Операция успешна", "Теперь " . $user->getCanonicalName() . " будет показываться как обычный подписчик всем кроме других администраторов");
$this->flashFail("succ", tr("success_action"), tr("x_is_now_hidden", $user->getCanonicalName()));
} else {
$this->flashFail("succ", "Операция успешна", "Теперь все будут знать про то что " . $user->getCanonicalName() . " - администратор");
$this->flashFail("succ", tr("success_action"), tr("x_is_now_showed", $user->getCanonicalName()));
}
} elseif($removeComment) {
if($club->getOwner()->getId() == $user->getId()) {
@ -155,11 +164,11 @@ final class GroupPresenter extends OpenVKPresenter
$manager->save();
}
$this->flashFail("succ", "Операция успешна", "Комментарий к администратору удален");
$this->flashFail("succ", tr("success_action"), tr("comment_is_deleted"));
} elseif($comment) {
if(mb_strlen($comment) > 36) {
$commentLength = (string) mb_strlen($comment);
$this->flashFail("err", "Ошибка", "Комментарий слишком длинный ($commentLength символов вместо 36 символов)");
$this->flashFail("err", tr("error"), tr("comment_is_too_long", $commentLength));
}
if($club->getOwner()->getId() == $user->getId()) {
@ -171,16 +180,16 @@ final class GroupPresenter extends OpenVKPresenter
$manager->save();
}
$this->flashFail("succ", "Операция успешна", "Комментарий к администратору изменён");
$this->flashFail("succ", tr("success_action"), tr("comment_is_changed"));
}else{
if($club->canBeModifiedBy($user)) {
$club->removeManager($user);
$this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " более не администратор.");
$this->flashFail("succ", tr("success_action"), tr("x_no_more_admin", $user->getCanonicalName()));
} else {
$club->addManager($user);
(new ClubModeratorNotification($user, $club, $this->user->identity))->emit();
$this->flashFail("succ", "Операция успешна", $user->getCanonicalName() . " назначен(а) администратором.");
$this->flashFail("succ", tr("success_action"), tr("x_is_admin", $user->getCanonicalName()));
}
}
@ -194,6 +203,8 @@ final class GroupPresenter extends OpenVKPresenter
$club = $this->clubs->get($id);
if(!$club || !$club->canBeModifiedBy($this->user->identity))
$this->notFound();
else if ($club->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
else
$this->template->club = $club;
@ -201,7 +212,7 @@ final class GroupPresenter extends OpenVKPresenter
if(!$club->setShortcode( empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode") ))
$this->flashFail("err", tr("error"), tr("error_shorturl_incorrect"));
$club->setName(empty($this->postParam("name")) ? $club->getName() : $this->postParam("name"));
$club->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $club->getName() : $this->postParam("name"));
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
$club->setWall(empty($this->postParam("wall")) ? 0 : 1);
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
@ -234,7 +245,7 @@ final class GroupPresenter extends OpenVKPresenter
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию.");
$this->flashFail("err", tr("error"), tr("error_when_uploading_photo"));
}
}
@ -242,15 +253,59 @@ final class GroupPresenter extends OpenVKPresenter
$club->save();
} catch(\PDOException $ex) {
if($ex->getCode() == 23000)
$this->flashFail("err", "Ошибка", "Произошла ошибка на стороне сервера. Обратитесь к системному администратору.");
$this->flashFail("err", tr("error"), tr("error_on_server_side"));
else
throw $ex;
}
$this->flash("succ", "Изменения сохранены", "Новые данные появятся в вашей группе.");
$this->flash("succ", tr("changes_saved"), tr("new_changes_desc"));
}
}
function renderSetAvatar(int $id)
{
$photo = new Photo;
$club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
if($_SERVER["REQUEST_METHOD"] === "POST" && $_FILES["ava"]["error"] === UPLOAD_ERR_OK) {
try {
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($anon && $this->user->id === $club->getOwner()->getId())
$anon = $club->isOwnerHidden();
else if($anon)
$anon = $club->getManager($this->user->identity)->isHidden();
$photo->setOwner($this->user->id);
$photo->setDescription("Club image");
$photo->setFile($_FILES["ava"]);
$photo->setCreated(time());
$photo->setAnonymous($anon);
$photo->save();
(new Albums)->getClubAvatarAlbum($club)->addPhoto($photo);
$flags = 0;
$flags |= 0b00010000;
$flags |= 0b10000000;
$post = new Post;
$post->setOwner($this->user->id);
$post->setWall($club->getId()*-1);
$post->setCreated(time());
$post->setContent("");
$post->setFlags($flags);
$post->save();
$post->attach($photo);
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", tr("error"), tr("error_when_uploading_photo"));
}
}
$this->returnJson([
"url" => $photo->getURL(),
"id" => $photo->getPrettyId()
]);
}
function renderEditBackdrop(int $id): void
{
$this->assertUserLoggedIn();
@ -295,11 +350,13 @@ final class GroupPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
if(!eventdb())
$this->flashFail("err", "Ошибка подключения", "Не удалось подключится к службе телеметрии.");
$this->flashFail("err", tr("connection_error"), tr("connection_error_desc"));
$club = $this->clubs->get($id);
if(!$club->canBeModifiedBy($this->user->identity))
$this->notFound();
else if ($club->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
else
$this->template->club = $club;
@ -332,6 +389,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("incorrect_password"));
$club = $this->clubs->get($id);
if ($club->isBanned()) $this->flashFail("err", tr("error"), tr("forbidden"));
$newOwner = (new Users)->get($newOwnerId);
if($this->user->id !== $club->getOwner()->getId())
$this->flashFail("err", tr("error"), tr("forbidden"));

View file

@ -0,0 +1,384 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use Nette\Database\DriverException;
use Nette\Utils\Finder;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Entities\Comment;
use Chandler\Database\Log;
use openvk\Web\Models\Entities\NoSpamLog;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\ChandlerUsers;
use Chandler\Database\Logs;
use openvk\Web\Models\Repositories\NoSpamLogs;
use openvk\Web\Models\Repositories\Users;
final class NoSpamPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
protected $deactivationTolerant = true;
protected $presenterName = "nospam";
const ENTITIES_NAMESPACE = "openvk\\Web\\Models\\Entities";
function __construct()
{
parent::__construct();
}
function renderIndex(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$targetDir = __DIR__ . '/../Models/Entities/';
$mode = in_array($this->queryParam("act"), ["form", "templates", "rollback", "reports"]) ? $this->queryParam("act") : "form";
if ($mode === "form") {
$this->template->_template = "NoSpam/Index";
$foundClasses = [];
foreach (Finder::findFiles('*.php')->from($targetDir) as $file) {
$content = file_get_contents($file->getPathname());
$namespacePattern = '/namespace\s+([^\s;]+)/';
$classPattern = '/class\s+([^\s{]+)/';
preg_match($namespacePattern, $content, $namespaceMatches);
preg_match($classPattern, $content, $classMatches);
if (isset($namespaceMatches[1]) && isset($classMatches[1])) {
$classNamespace = trim($namespaceMatches[1]);
$className = trim($classMatches[1]);
$fullClassName = $classNamespace . '\\' . $className;
if ($classNamespace === NoSpamPresenter::ENTITIES_NAMESPACE && class_exists($fullClassName)) {
$foundClasses[] = $className;
}
}
}
$models = [];
foreach ($foundClasses as $class) {
$r = new \ReflectionClass(NoSpamPresenter::ENTITIES_NAMESPACE . "\\$class");
if (!$r->isAbstract() && $r->getName() !== NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence")
$models[] = $class;
}
$this->template->models = $models;
} else if ($mode === "templates") {
$this->template->_template = "NoSpam/Templates.xml";
$filter = [];
if ($this->queryParam("id")) {
$filter["id"] = (int)$this->queryParam("id");
}
$this->template->templates = iterator_to_array((new NoSpamLogs)->getList($filter));
} else if ($mode === "reports") {
$this->redirect("/scumfeed");
} else {
$template = (new NoSpamLogs)->get((int)$this->postParam("id"));
if (!$template || $template->isRollbacked())
$this->returnJson(["success" => false, "error" => "Шаблон не найден"]);
$model = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $template->getModel();
$items = $template->getItems();
if (count($items) > 0) {
$db = DatabaseConnection::i()->getContext();
$unbanned_ids = [];
foreach ($items as $_item) {
try {
$item = new $model;
$table_name = $item->getTableName();
$item = $db->table($table_name)->get((int)$_item);
if (!$item) continue;
$item = new $model($item);
if (key_exists("deleted", $item->unwrap()) && $item->isDeleted()) {
$item->setDeleted(0);
$item->save();
}
if (in_array($template->getTypeRaw(), [2, 3])) {
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($item, "ban")) {
$owner = $item;
} else {
foreach ($methods as $method) {
if (method_exists($item, $method)) {
$owner = $item->$method();
break;
}
}
}
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $unbanned_ids)) {
$owner->unban($this->user->id);
$unbanned_ids[] = $_id;
}
}
} catch (\Throwable $e) {
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
}
}
} else {
$this->returnJson(["success" => false, "error" => "Объекты не найдены"]);
}
$template->setRollback(true);
$template->save();
$this->returnJson(["success" => true]);
}
}
function renderSearch(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$this->assertNoCSRF();
$this->willExecuteWriteAction();
function searchByAdditionalParams(?string $table = NULL, ?string $where = NULL, ?string $ip = NULL, ?string $useragent = NULL, ?int $ts = NULL, ?int $te = NULL, $user = NULL)
{
$db = DatabaseConnection::i()->getContext();
if ($table && ($ip || $useragent || $ts || $te || $user)) {
$conditions = [];
if ($ip) $conditions[] = "`ip` REGEXP '$ip'";
if ($useragent) $conditions[] = "`useragent` REGEXP '$useragent'";
if ($ts) $conditions[] = "`ts` < $ts";
if ($te) $conditions[] = "`ts` > $te";
if ($user) {
$users = new Users;
$_user = $users->getByChandlerUser((new ChandlerUsers)->getById($user))
?? $users->get((int)$user)
?? $users->getByAddress($user)
?? NULL;
if ($_user) {
$conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'";
}
}
$whereStart = "WHERE `object_table` = '$table'";
if ($table === "profiles") {
$whereStart .= "AND `type` = 0";
}
$conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : "";
$response = [];
if ($conditions) {
$logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`");
foreach ($logs as $log) {
$log = (new Logs)->get($log->id);
$object = $log->getObject()->unwrap();
if (!$object) continue;
if ($where) {
if (str_starts_with($where, " AND")) {
$where = substr_replace($where, "", 0, strlen(" AND"));
}
$a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll();
foreach ($a as $o) {
if ($object->id == $o["id"]) {
$response[] = $object;
}
}
} else {
$response[] = $object;
}
}
}
return $response;
}
}
try {
$response = [];
$processed = 0;
$where = $this->postParam("where");
$ip = addslashes($this->postParam("ip"));
$useragent = addslashes($this->postParam("useragent"));
$searchTerm = addslashes($this->postParam("q"));
$ts = (int)$this->postParam("ts");
$te = (int)$this->postParam("te");
$user = addslashes($this->postParam("user"));
if ($where) {
$where = explode(";", $where)[0];
}
if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm && !$user)
$this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]);
$models = explode(",", $this->postParam("models"));
foreach ($models as $_model) {
$model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $_model;
if (!class_exists($model_name)) {
continue;
}
$model = new $model_name;
$c = new \ReflectionClass($model_name);
if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence") {
continue;
}
$db = DatabaseConnection::i()->getContext();
$table = $model->getTableName();
$columns = $db->getStructure()->getColumns($table);
if ($searchTerm) {
$conditions = [];
$need_deleted = false;
foreach ($columns as $column) {
if ($column["name"] == "deleted") {
$need_deleted = true;
} else {
$conditions[] = "`$column[name]` REGEXP '$searchTerm'";
}
}
$conditions = implode(" OR ", $conditions);
$where = ($this->postParam("where") ? " AND ($conditions)" : "($conditions)");
if ($need_deleted) $where .= " AND (`deleted` = 0)";
}
$rows = [];
if (str_starts_with($where, " AND")) {
if ($searchTerm && !$this->postParam("where")) {
$where = substr_replace($where, "", 0, strlen(" AND"));
} else {
$where = "(" . $this->postParam("where") . ")" . $where;
}
}
if ($ip || $useragent || $ts || $te || $user) {
$rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
} else {
if (!$where) {
$rows = [];
} else {
$result = $db->query("SELECT * FROM `$table` WHERE $where");
$rows = $result->fetchAll();
}
}
if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) {
foreach ($rows as $key => $object) {
$object = (array)$object;
$_obj = [];
foreach ($object as $key => $value) {
foreach ($columns as $column) {
if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) {
$value = "[BINARY]";
break;
}
}
$_obj[$key] = $value;
$_obj["__model_name"] = $_model;
}
$response[] = $_obj;
}
} else {
$ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$ids[] = $object->getId();
}
$log = new NoSpamLog;
$log->setUser($this->user->id);
$log->setModel($_model);
if ($searchTerm) {
$log->setRegex($searchTerm);
} else {
$log->setRequest($where);
}
$log->setBan_Type((int)$this->postParam("ban"));
$log->setCount(count($rows));
$log->setTime(time());
$log->setItems(implode(",", $ids));
$log->save();
$banned_ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($object, "ban")) {
$owner = $object;
} else {
foreach ($methods as $method) {
if (method_exists($object, $method)) {
$owner = $object->$method();
break;
}
}
}
if ($owner instanceof User && $owner->getId() === $this->user->id) {
if (count($rows) === 1) {
$this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]);
} else {
continue;
}
}
if (in_array((int)$this->postParam("ban"), [2, 3])) {
$reason = mb_strlen(trim($this->postParam("ban_reason"))) > 0 ? addslashes($this->postParam("ban_reason")) : ("**content-noSpamTemplate-" . $log->getId() . "**");
$is_forever = (string)$this->postParam("is_forever") === "true";
$unban_time = $is_forever ? 0 : (int)$this->postParam("unban_time") ?? NULL;
if ($owner) {
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $banned_ids)) {
if ($owner instanceof User) {
if (!$unban_time && !$is_forever)
$unban_time = time() + $owner->getNewBanTime();
$owner->ban($reason, false, $unban_time, $this->user->id);
} else {
$owner->ban("Подозрительная активность");
}
$banned_ids[] = $_id;
}
}
}
if (in_array((int)$this->postParam("ban"), [1, 3]))
$object->delete();
}
$processed++;
}
}
$this->returnJson(["success" => true, "processed" => $processed, "count" => count($response), "list" => $response]);
} catch (\Throwable $e) {
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
}
}
}

View file

@ -107,7 +107,7 @@ final class NotesPresenter extends OpenVKPresenter
if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted())
$this->notFound();
if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$this->template->note = $note;
if($_SERVER["REQUEST_METHOD"] === "POST") {
@ -135,11 +135,11 @@ final class NotesPresenter extends OpenVKPresenter
if(!$note) $this->notFound();
if($note->getOwner()->getId() . "_" . $note->getId() !== $owner . "_" . $id || $note->isDeleted()) $this->notFound();
if(is_null($this->user) || !$note->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$name = $note->getName();
$note->delete();
$this->flash("succ", "Заметка удалена", "Заметка \"$name\" была успешно удалена.");
$this->flash("succ", tr("note_is_deleted"), tr("note_x_is_now_deleted", $name));
$this->redirect("/notes" . $this->user->id);
}
}

9
Web/Presenters/OpenVKPresenter.php Executable file → Normal file
View file

@ -7,7 +7,7 @@ use Chandler\Security\Authenticator;
use Latte\Engine as TemplatingEngine;
use openvk\Web\Models\Entities\IP;
use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets};
use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser};
use WhichBrowser;
abstract class OpenVKPresenter extends SimplePresenter
@ -211,6 +211,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->user->id = $this->user->identity->getId();
$this->template->thisUser = $this->user->identity;
$this->template->userTainted = $user->isTainted();
CurrentUser::get($this->user->identity, $_SERVER["REMOTE_ADDR"], $_SERVER["HTTP_USER_AGENT"]);
if($this->user->identity->isDeleted() && !$this->deactivationTolerant) {
if($this->user->identity->isDeactivated()) {
@ -255,12 +256,14 @@ abstract class OpenVKPresenter extends SimplePresenter
if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
$this->user->identity->setOnline(time());
$this->user->identity->setClient_name(NULL);
$this->user->identity->save();
$this->user->identity->save(false);
}
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByUserId($this->user->id, 1);
if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0))
if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0)) {
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
$this->template->reportNotAnsweredCount = (new Reports)->getReportsCount(0);
}
}
header("X-OpenVK-User-Validated: $userValidated");

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo, Album};
use openvk\Web\Models\Entities\{Club, Photo, Album, User};
use openvk\Web\Models\Repositories\{Photos, Albums, Users, Clubs};
use Nette\InvalidStateException as ISE;
@ -27,7 +27,7 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$user) $this->notFound();
if (!$user->getPrivacyPermission('photos.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->albums = $this->albums->getUserAlbums($user, $this->queryParam("p") ?? 1);
$this->template->albums = $this->albums->getUserAlbums($user, (int)($this->queryParam("p") ?? 1));
$this->template->count = $this->albums->getUserAlbumsCount($user);
$this->template->owner = $user;
$this->template->canEdit = false;
@ -36,7 +36,7 @@ final class PhotosPresenter extends OpenVKPresenter
} else {
$club = (new Clubs)->get(abs($owner));
if(!$club) $this->notFound();
$this->template->albums = $this->albums->getClubAlbums($club, $this->queryParam("p") ?? 1);
$this->template->albums = $this->albums->getClubAlbums($club, (int)($this->queryParam("p") ?? 1));
$this->template->count = $this->albums->getClubAlbumsCount($club);
$this->template->owner = $club;
$this->template->canEdit = false;
@ -46,7 +46,7 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->paginatorConf = (object) [
"count" => $this->template->count,
"page" => $this->queryParam("p") ?? 1,
"page" => (int)($this->queryParam("p") ?? 1),
"amount" => NULL,
"perPage" => OPENVK_DEFAULT_PER_PAGE,
];
@ -66,7 +66,7 @@ final class PhotosPresenter extends OpenVKPresenter
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(empty($this->postParam("name")))
if(empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0)
$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"));
@ -94,19 +94,19 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$album) $this->notFound();
if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound();
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity) || $album->isDeleted())
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$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->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $album->getName() : $this->postParam("name"));
$album->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
$album->setEdited(time());
$album->save();
$this->flash("succ", "Изменения сохранены", "Новые данные приняты.");
$this->flash("succ", tr("changes_saved"), tr("new_data_accepted"));
}
}
@ -120,13 +120,13 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$album) $this->notFound();
if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) $this->notFound();
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$name = $album->getName();
$owner = $album->getOwner();
$album->delete();
$this->flash("succ", "Альбом удалён", "Альбом $name был успешно удалён.");
$this->flash("succ", tr("album_is_deleted"), tr("album_x_is_deleted", $name));
$this->redirect("/albums" . ($owner instanceof Club ? "-" : "") . $owner->getId());
}
@ -147,7 +147,7 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1), 20) );
$this->template->paginatorConf = (object) [
"count" => $album->getPhotosCount(),
"page" => $this->queryParam("p") ?? 1,
"page" => (int)($this->queryParam("p") ?? 1),
"amount" => sizeof($this->template->photos),
"perPage" => 20,
"atBottom" => true
@ -205,13 +205,13 @@ final class PhotosPresenter extends OpenVKPresenter
$photo = $this->photos->getByOwnerAndVID($ownerId, $photoId);
if(!$photo) $this->notFound();
if(is_null($this->user) || $this->user->id != $ownerId)
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if($_SERVER["REQUEST_METHOD"] === "POST") {
$photo->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
$photo->save();
$this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с фоткой.");
$this->flash("succ", tr("changes_saved"), tr("new_description_will_appear"));
$this->redirect("/photo" . $photo->getPrettyId());
}
@ -221,39 +221,74 @@ final class PhotosPresenter extends OpenVKPresenter
function renderUploadPhoto(): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->willExecuteWriteAction(true);
if(is_null($this->queryParam("album")))
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в <b>DELETED</b>.");
$this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true);
[$owner, $id] = explode("_", $this->queryParam("album"));
$album = $this->albums->get((int) $id);
if(!$album)
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в <b>DELETED</b>.");
$this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true);
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), 500, true);
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!isset($_FILES["blob"]))
$this->flashFail("err", "Нету фотографии", "Выберите файл.");
try {
$photo = new Photo;
$photo->setOwner($this->user->id);
$photo->setDescription($this->postParam("desc"));
$photo->setFile($_FILES["blob"]);
$photo->setCreated(time());
$photo->save();
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в <b>$name</b>.");
}
$album->addPhoto($photo);
$album->setEdited(time());
$album->save();
if($this->queryParam("act") == "finish") {
$result = json_decode($this->postParam("photos"), true);
foreach($result as $photoId => $description) {
$phot = $this->photos->get($photoId);
$this->redirect("/photo" . $photo->getPrettyId() . "?from=album" . $album->getId());
if(!$phot || $phot->isDeleted() || $phot->getOwner()->getId() != $this->user->id)
continue;
if(iconv_strlen($description) > 255)
$this->flashFail("err", tr("error"), tr("description_too_long"), 500, true);
$phot->setDescription($description);
$phot->save();
$album = $phot->getAlbum();
}
$this->returnJson(["success" => true,
"album" => $album->getId(),
"owner" => $album->getOwner() instanceof User ? $album->getOwner()->getId() : $album->getOwner()->getId() * -1]);
}
if(!isset($_FILES))
$this->flashFail("err", tr("no_photo"), tr("select_file"), 500, true);
$photos = [];
for($i = 0; $i < $this->postParam("count"); $i++) {
try {
$photo = new Photo;
$photo->setOwner($this->user->id);
$photo->setDescription("");
$photo->setFile($_FILES["photo_".$i]);
$photo->setCreated(time());
$photo->save();
$photos[] = [
"url" => $photo->getURLBySizeId("tiny"),
"id" => $photo->getId(),
"vid" => $photo->getVirtualId(),
"owner" => $photo->getOwner()->getId(),
"link" => $photo->getURL()
];
} catch(ISE $ex) {
$name = $album->getName();
$this->flashFail("err", "Неизвестная ошибка", "Не удалось сохранить фотографию в $name.", 500, true);
}
$album->addPhoto($photo);
$album->setEdited(time());
$album->save();
}
$this->returnJson(["success" => true,
"photos" => $photos]);
} else {
$this->template->album = $album;
}
@ -269,7 +304,7 @@ final class PhotosPresenter extends OpenVKPresenter
if(!$album || !$photo) $this->notFound();
if(!$album->hasPhoto($photo)) $this->notFound();
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->assertNoCSRF();
@ -277,7 +312,7 @@ final class PhotosPresenter extends OpenVKPresenter
$album->setEdited(time());
$album->save();
$this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена.");
$this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc"));
$this->redirect("/album" . $album->getPrettyId());
}
}
@ -285,18 +320,23 @@ final class PhotosPresenter extends OpenVKPresenter
function renderDeletePhoto(int $ownerId, int $photoId): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->willExecuteWriteAction($_SERVER["REQUEST_METHOD"] === "POST");
$this->assertNoCSRF();
$photo = $this->photos->getByOwnerAndVID($ownerId, $photoId);
if(!$photo) $this->notFound();
if(is_null($this->user) || $this->user->id != $ownerId)
$this->flashFail("err", "Ошибка доступа", "Недостаточно прав для модификации данного ресурса.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$redirect = $photo->getAlbum()->getOwner() instanceof User ? "/id0" : "/club" . $ownerId;
$photo->isolate();
$photo->delete();
$this->flash("succ", "Фотография удалена", "Эта фотография была успешно удалена.");
$this->redirect("/id0");
if($_SERVER["REQUEST_METHOD"] === "POST")
$this->returnJson(["success" => true]);
$this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc"));
$this->redirect($redirect);
}
}

View file

@ -0,0 +1,151 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Repositories\Reports;
use openvk\Web\Models\Repositories\Posts;
use openvk\Web\Models\Entities\Report;
final class ReportPresenter extends OpenVKPresenter
{
private $reports;
function __construct(Reports $reports)
{
$this->reports = $reports;
parent::__construct();
}
function renderList(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
if ($_SERVER["REQUEST_METHOD"] === "POST")
$this->assertNoCSRF();
$act = in_array($this->queryParam("act"), ["post", "photo", "video", "group", "comment", "note", "app", "user"]) ? $this->queryParam("act") : NULL;
if (!$this->queryParam("orig")) {
$this->template->reports = $this->reports->getReports(0, (int)($this->queryParam("p") ?? 1), $act, $_SERVER["REQUEST_METHOD"] !== "POST");
$this->template->count = $this->reports->getReportsCount();
} else {
$orig = $this->reports->get((int) $this->queryParam("orig"));
if (!$orig) $this->redirect("/scumfeed");
$this->template->reports = $orig->getDuplicates();
$this->template->count = $orig->getDuplicatesCount();
$this->template->orig = $orig->getId();
}
$this->template->paginatorConf = (object) [
"count" => $this->template->count,
"page" => $this->queryParam("p") ?? 1,
"amount" => NULL,
"perPage" => 15,
];
$this->template->mode = $act ?? "all";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$reports = [];
foreach ($this->reports->getReports(0, 0, $act, false) as $report) {
$reports[] = [
"id" => $report->getId(),
"author" => [
"id" => $report->getReportAuthor()->getId(),
"url" => $report->getReportAuthor()->getURL(),
"name" => $report->getReportAuthor()->getCanonicalName(),
"is_female" => $report->getReportAuthor()->isFemale()
],
"content" => [
"name" => $report->getContentName(),
"type" => $report->getContentType(),
"id" => $report->getContentId(),
"url" => $report->getContentType() === "user" ? (new Users)->get((int) $report->getContentId())->getURL() : NULL
],
"duplicates" => $report->getDuplicatesCount(),
];
}
$this->returnJson(["reports" => $reports]);
}
}
function renderView(int $id): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$report = $this->reports->get($id);
if(!$report || $report->isDeleted())
$this->notFound();
$this->template->report = $report;
}
function renderCreate(int $id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if(!$id)
exit(json_encode([ "error" => tr("error_segmentation") ]));
if(in_array($this->queryParam("type"), ["post", "photo", "video", "group", "comment", "note", "app", "user"])) {
if (count(iterator_to_array($this->reports->getDuplicates($this->queryParam("type"), $id, NULL, $this->user->id))) <= 0) {
$report = new Report;
$report->setUser_id($this->user->id);
$report->setTarget_id($id);
$report->setType($this->queryParam("type"));
$report->setReason($this->queryParam("reason"));
$report->setCreated(time());
$report->save();
}
exit(json_encode([ "reason" => $this->queryParam("reason") ]));
} else {
exit(json_encode([ "error" => "Unable to submit a report on this content type" ]));
}
}
function renderAction(int $id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$report = $this->reports->get($id);
if(!$report || $report->isDeleted()) $this->notFound();
if ($this->postParam("ban")) {
$report->deleteContent();
$report->banUser($this->user->identity->getId());
$this->flash("suc", tr("death"), tr("user_successfully_banned"));
} else if ($this->postParam("delete")) {
$report->deleteContent();
$this->flash("suc", tr("nehay"), tr("content_is_deleted"));
} else if ($this->postParam("ignore")) {
$report->delete();
$this->flash("suc", tr("nehay"), tr("report_is_ignored"));
} else if ($this->postParam("banClubOwner") || $this->postParam("banClub")) {
if ($report->getContentType() !== "group")
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
$club = $report->getContentObject();
if (!$club || $club->isBanned())
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if ($this->postParam("banClubOwner")) {
$club->getOwner()->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**", false, $club->getOwner()->getNewBanTime(), $this->user->identity->getId());
} else {
$club->ban("**content-" . $report->getContentType() . "-" . $report->getContentId() . "**");
}
$report->delete();
$this->flash("suc", tr("death"), ($this->postParam("banClubOwner") ? tr("group_owner_is_banned") : tr("group_is_banned")));
}
$this->redirect("/scumfeed");
}
}

View file

@ -1,18 +1,28 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{User, Club};
use openvk\Web\Models\Repositories\{Users, Clubs};
use openvk\Web\Models\Repositories\{Users, Clubs, Posts, Comments, Videos, Applications, Notes};
use Chandler\Database\DatabaseConnection;
final class SearchPresenter extends OpenVKPresenter
{
private $users;
private $clubs;
private $posts;
private $comments;
private $videos;
private $apps;
private $notes;
function __construct(Users $users, Clubs $clubs)
{
$this->users = $users;
$this->clubs = $clubs;
$this->users = $users;
$this->clubs = $clubs;
$this->posts = new Posts;
$this->comments = new Comments;
$this->videos = new Videos;
$this->apps = new Applications;
$this->notes = new Notes;
parent::__construct();
}
@ -21,6 +31,8 @@ final class SearchPresenter extends OpenVKPresenter
{
$query = $this->queryParam("query") ?? "";
$type = $this->queryParam("type") ?? "users";
$sorter = $this->queryParam("sort") ?? "id";
$invert = $this->queryParam("invert") == 1 ? "ASC" : "DESC";
$page = (int) ($this->queryParam("p") ?? 1);
$this->willExecuteWriteAction();
@ -28,11 +40,58 @@ final class SearchPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
# https://youtu.be/pSAWM5YuXx8
$repos = [ "groups" => "clubs", "users" => "users" ];
$repos = [
"groups" => "clubs",
"users" => "users",
"posts" => "posts",
"comments" => "comments",
"videos" => "videos",
"audios" => "posts",
"apps" => "apps",
"notes" => "notes"
];
switch($sorter) {
default:
case "id":
$sort = "id " . $invert;
break;
case "name":
$sort = "first_name " . $invert;
break;
case "rating":
$sort = "rating " . $invert;
break;
}
$parameters = [
"type" => $this->queryParam("type"),
"city" => $this->queryParam("city") != "" ? $this->queryParam("city") : NULL,
"maritalstatus" => $this->queryParam("maritalstatus") != 0 ? $this->queryParam("maritalstatus") : NULL,
"with_photo" => $this->queryParam("with_photo"),
"status" => $this->queryParam("status") != "" ? $this->queryParam("status") : NULL,
"politViews" => $this->queryParam("politViews") != 0 ? $this->queryParam("politViews") : NULL,
"email" => $this->queryParam("email"),
"telegram" => $this->queryParam("telegram"),
"site" => $this->queryParam("site") != "" ? "https://".$this->queryParam("site") : NULL,
"address" => $this->queryParam("address"),
"is_online" => $this->queryParam("is_online") == 1 ? 1 : NULL,
"interests" => $this->queryParam("interests") != "" ? $this->queryParam("interests") : NULL,
"fav_mus" => $this->queryParam("fav_mus") != "" ? $this->queryParam("fav_mus") : NULL,
"fav_films" => $this->queryParam("fav_films") != "" ? $this->queryParam("fav_films") : NULL,
"fav_shows" => $this->queryParam("fav_shows") != "" ? $this->queryParam("fav_shows") : NULL,
"fav_books" => $this->queryParam("fav_books") != "" ? $this->queryParam("fav_books") : NULL,
"fav_quote" => $this->queryParam("fav_quote") != "" ? $this->queryParam("fav_quote") : NULL,
"hometown" => $this->queryParam("hometown") != "" ? $this->queryParam("hometown") : NULL,
"before" => $this->queryParam("datebefore") != "" ? strtotime($this->queryParam("datebefore")) : NULL,
"after" => $this->queryParam("dateafter") != "" ? strtotime($this->queryParam("dateafter")) : NULL,
"gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL
];
$repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type.");
$results = $this->{$repo}->find($query);
$results = $this->{$repo}->find($query, $parameters, $sort);
$iterator = $results->page($page);
$count = $results->size();

View file

@ -385,7 +385,7 @@ final class SupportPresenter extends OpenVKPresenter
$agent->setNumerate((int) $this->postParam("number") ?? NULL);
$agent->setIcon($this->postParam("avatar"));
$agent->save();
$this->flashFail("succ", "Успех", "Профиль отредактирован.");
$this->flashFail("succ", tr("agent_profile_edited"));
} else {
$agent = new SupportAgent;
$agent->setAgent($this->user->identity->getId());
@ -393,7 +393,27 @@ final class SupportPresenter extends OpenVKPresenter
$agent->setNumerate((int) $this->postParam("number") ?? NULL);
$agent->setIcon($this->postParam("avatar"));
$agent->save();
$this->flashFail("succ", "Успех", "Профиль создан. Теперь пользователи видят Ваши псевдоним и аватарку вместо стандартных аватарки и номера.");
$this->flashFail("succ", tr("agent_profile_created_1"), tr("agent_profile_created_2"));
}
}
function renderCloseTicket(int $id): void
{
$this->assertUserLoggedIn();
$this->assertNoCSRF();
$this->willExecuteWriteAction();
$ticket = $this->tickets->get($id);
if($ticket->isDeleted() === 1 || $ticket->getType() === 2 || $ticket->getUserId() !== $this->user->id) {
header("HTTP/1.1 403 Forbidden");
header("Location: /support/view/" . $id);
exit;
}
$ticket->setType(2);
$ticket->save();
$this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment"));
}
}

View file

@ -84,6 +84,9 @@ final class TopicsPresenter extends OpenVKPresenter
if($this->postParam("as_group") === "on" && $club->canBeModifiedBy($this->user->identity))
$flags |= 0b10000000;
if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), "Video uploads are disabled by the system administrator.");
$topic = new Topic;
$topic->setGroup($club->getId());
$topic->setOwner($this->user->id);
@ -105,10 +108,10 @@ final class TopicsPresenter extends OpenVKPresenter
}
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"]);
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"]);
}
} catch(ISE $ex) {
$this->flash("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
$this->flash("err", tr("error_when_publishing_comment"), tr("error_comment_file_too_big"));
$this->redirect("/topic" . $topic->getPrettyId());
}
@ -123,7 +126,7 @@ final class TopicsPresenter extends OpenVKPresenter
$comment->setFlags($flags);
$comment->save();
} catch (\LengthException $ex) {
$this->flash("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой.");
$this->flash("err", tr("error_when_publishing_comment"), tr("error_comment_too_big"));
$this->redirect("/topic" . $topic->getPrettyId());
}

View file

@ -39,6 +39,7 @@ final class UserPresenter extends OpenVKPresenter
}
} else {
$this->template->albums = (new Albums)->getUserAlbums($user);
$this->template->avatarAlbum = (new Albums)->getUserAvatarAlbum($user);
$this->template->albumsCount = (new Albums)->getUserAlbumsCount($user);
$this->template->videos = (new Videos)->getByUser($user, 1, 2);
$this->template->videosCount = (new Videos)->getUserVideosCount($user);
@ -71,7 +72,7 @@ final class UserPresenter extends OpenVKPresenter
if(!is_null($this->user)) {
if($this->template->mode !== "friends" && $this->user->id !== $id) {
$name = $user->getFullName();
$this->flash("err", "Ошибка доступа", "Вы не можете просматривать полный список подписок $name.");
$this->flash("err", tr("error_access_denied_short"), tr("error_viewing_subs", $name));
$this->redirect($user->getURL());
}
@ -106,11 +107,11 @@ final class UserPresenter extends OpenVKPresenter
$this->notFound();
if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
$this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.", NULL, true);
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), NULL, true);
$isClubPinned = $this->user->identity->isClubPinned($club);
if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10)
$this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп", NULL, true);
$this->flashFail("err", tr("error"), tr("error_max_pinned_clubs"), NULL, true);
if($club->getOwner()->getId() === $this->user->identity->getId()) {
$club->setOwner_Club_Pinned(!$isClubPinned);
@ -236,7 +237,7 @@ final class UserPresenter extends OpenVKPresenter
} elseif($_GET['act'] === "status") {
if(mb_strlen($this->postParam("status")) > 255) {
$statusLength = (string) mb_strlen($this->postParam("status"));
$this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)", NULL, true);
$this->flashFail("err", tr("error"), tr("error_status_too_long", $statusLength), NULL, true);
}
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
@ -280,7 +281,7 @@ final class UserPresenter extends OpenVKPresenter
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!$user->verifyNumber($this->postParam("code") ?? 0))
$this->flashFail("err", "Ошибка", "Не удалось подтвердить номер телефона: неверный код.");
$this->flashFail("err", tr("error"), tr("invalid_code"));
$this->flash("succ", tr("changes_saved"), tr("changes_saved_comment"));
}
@ -301,7 +302,7 @@ final class UserPresenter extends OpenVKPresenter
$this->redirect($user->getURL());
}
function renderSetAvatar(): void
function renderSetAvatar()
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
@ -321,8 +322,26 @@ final class UserPresenter extends OpenVKPresenter
$album->addPhoto($photo);
$album->setEdited(time());
$album->save();
$this->flashFail("succ", tr("photo_saved"), tr("photo_saved_comment"));
$flags = 0;
$flags |= 0b00010000;
$post = new Post;
$post->setOwner($this->user->id);
$post->setWall($this->user->id);
$post->setCreated(time());
$post->setContent("");
$post->setFlags($flags);
$post->save();
$post->attach($photo);
if($this->postParam("ava", true) == (int)1) {
$this->returnJson([
"url" => $photo->getURL(),
"id" => $photo->getPrettyId()
]);
} else {
$this->flashFail("succ", tr("photo_saved"), tr("photo_saved_comment"));
}
}
function renderSettings(): void
@ -462,6 +481,7 @@ final class UserPresenter extends OpenVKPresenter
"menu_novajoj" => "news",
"menu_ligiloj" => "links",
"menu_standardo" => "poster",
"menu_aplikoj" => "apps"
];
foreach($settings as $checkbox => $setting)
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));

View file

@ -6,6 +6,7 @@ use openvk\VKAPI\Exceptions\APIErrorException;
use openvk\Web\Models\Entities\{User, APIToken};
use openvk\Web\Models\Repositories\{Users, APITokens};
use lfkeitel\phptotp\{Base32, Totp};
use WhichBrowser;
final class VKAPIPresenter extends OpenVKPresenter
{
@ -98,20 +99,21 @@ final class VKAPIPresenter extends OpenVKPresenter
function renderPhotoUpload(string $signature): void
{
$secret = CHANDLER_ROOT_CONF["security"]["secret"];
$computedSignature = hash_hmac("sha3-224", $_SERVER["QUERY_STRING"], $secret);
$secret = CHANDLER_ROOT_CONF["security"]["secret"];
$queryString = rawurldecode($_SERVER["QUERY_STRING"]);
$computedSignature = hash_hmac("sha3-224", $queryString, $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"]));
$data = unpack("vDOMAIN/Z10FIELD/vMF/vMP/PTIME/PUSER/PGROUP", base64_decode($queryString));
if((time() - $data["TIME"]) > 600) {
header("HTTP/1.1 422 Unprocessable Entity");
exit("Expired");
}
$folder = __DIR__ . "../../tmp/api-storage/photos";
$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"));
@ -283,7 +285,7 @@ final class VKAPIPresenter extends OpenVKPresenter
$token = new APIToken;
$token->setUser($user);
$token->setPlatform(is_null($platform) ? "api" : $platform);
$token->setPlatform($platform ?? (new WhichBrowser\Parser(getallheaders()))->toString());
$token->save();
$payload = json_encode([

View file

@ -56,6 +56,9 @@ final class VideosPresenter extends OpenVKPresenter
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if(OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), tr("video_uploads_disabled"));
if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!empty($this->postParam("name"))) {
@ -71,18 +74,18 @@ final class VideosPresenter extends OpenVKPresenter
else if(!empty($this->postParam("link")))
$video->setLink($this->postParam("link"));
else
$this->flashFail("err", "Нету видеозаписи", "Выберите файл или укажите ссылку.");
$this->flashFail("err", tr("no_video"), tr("no_video_desc"));
} catch(\DomainException $ex) {
$this->flashFail("err", "Произошла ошибка", "Файл повреждён или не содержит видео." );
$this->flashFail("err", tr("error_occured"), tr("error_video_damaged_file"));
} catch(ISE $ex) {
$this->flashFail("err", "Произошла ошибка", "Возможно, ссылка некорректна.");
$this->flashFail("err", tr("error_occured"), tr("error_video_incorrect_link"));
}
$video->save();
$this->redirect("/video" . $video->getPrettyId());
} else {
$this->flashFail("err", "Произошла ошибка", "Видео не может быть опубликовано без названия.");
$this->flashFail("err", tr("error_occured"), tr("error_video_no_title"));
}
}
}
@ -96,14 +99,14 @@ final class VideosPresenter extends OpenVKPresenter
if(!$video)
$this->notFound();
if(is_null($this->user) || $this->user->id !== $owner)
$this->flashFail("err", "Ошибка доступа", "Вы не имеете права редактировать этот ресурс.");
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"));
if($_SERVER["REQUEST_METHOD"] === "POST") {
$video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name"));
$video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc"));
$video->save();
$this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с видосиком.");
$this->flash("succ", tr("changes_saved"), tr("new_data_video"));
$this->redirect("/video" . $video->getPrettyId());
}
@ -125,7 +128,7 @@ final class VideosPresenter extends OpenVKPresenter
$video->deleteVideo($owner, $vid);
}
} else {
$this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт.");
$this->flashFail("err", tr("error_deleting_video"), tr("login_please"));
}
$this->redirect("/videos" . $owner);

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User};
use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments};
use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE;
use Bhaktaraz\RSSGenerator\Item;
@ -46,13 +46,13 @@ final class WallPresenter extends OpenVKPresenter
function renderWall(int $user, bool $embedded = false): void
{
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
if ($owner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(is_null($this->user)) {
$canPost = false;
} else if($user > 0) {
if(!$owner->isBanned())
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), tr("forbidden"));
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
@ -100,6 +100,8 @@ final class WallPresenter extends OpenVKPresenter
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
else if ($owner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
else
$canPost = $owner->canPost();
} else {
@ -212,11 +214,12 @@ final class WallPresenter extends OpenVKPresenter
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
if ($wallOwner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if($wall > 0) {
if(!$wallOwner->isBanned())
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
} else if($wall < 0) {
if($wallOwner->canBeModifiedBy($this->user->identity))
$canPost = true;
@ -228,7 +231,7 @@ final class WallPresenter extends OpenVKPresenter
if(!$canPost)
$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) {
$manager = $wallOwner->getManager($this->user->identity);
@ -269,8 +272,8 @@ final class WallPresenter extends OpenVKPresenter
$photos[] = Photo::fastMake($this->user->id, $this->postParam("text"), $file, $album, $anon);
}
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK)
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"], $anon);
/*if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK)
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $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) {
@ -287,8 +290,41 @@ final class WallPresenter extends OpenVKPresenter
} catch(\UnexpectedValueException $e) {
$this->flashFail("err", tr("failed_to_publish_post"), "Poll format invalid");
}
$note = NULL;
if(!is_null($this->postParam("note")) && $this->postParam("note") != "none") {
$note = (new Notes)->get((int)$this->postParam("note"));
if(!$note || $note->isDeleted() || $note->getOwner()->getId() != $this->user->id) {
$this->flashFail("err", tr("error"), tr("error_attaching_note"));
}
if($note->getOwner()->getPrivacySetting("notes.read") < 1) {
$this->flashFail("err", " ");
}
}
$videos = [];
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
}
}
if(empty($this->postParam("text")) && !$photos && !$video && !$poll)
if(empty($this->postParam("text")) && !$photos && sizeof($videos) < 1 && !$poll && !$note)
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
try {
@ -308,11 +344,15 @@ final class WallPresenter extends OpenVKPresenter
foreach($photos as $photo)
$post->attach($photo);
if(!is_null($video))
$post->attach($video);
if(sizeof($videos) > 0)
foreach($videos as $vid)
$post->attach($vid);
if(!is_null($poll))
$post->attach($poll);
if(!is_null($note))
$post->attach($note);
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
@ -346,6 +386,9 @@ final class WallPresenter extends OpenVKPresenter
} else {
$this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall()));
$this->template->isWallOfGroup = true;
if ($this->template->wallOwner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
}
$this->template->cCount = $post->getCommentsCount();
$this->template->cPage = (int) ($_GET["p"] ?? 1);
@ -360,7 +403,10 @@ final class WallPresenter extends OpenVKPresenter
$post = $this->posts->getPostById($wall, $post_id);
if(!$post || $post->isDeleted()) $this->notFound();
if ($post->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!is_null($this->user)) {
$post->toggleLike($this->user->identity);
}
@ -375,21 +421,55 @@ final class WallPresenter extends OpenVKPresenter
$this->assertNoCSRF();
$post = $this->posts->getPostById($wall, $post_id);
if(!$post || $post->isDeleted()) $this->notFound();
if(!$post || $post->isDeleted())
$this->notFound();
if ($post->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
$where = $this->postParam("type") ?? "wall";
$groupId = NULL;
$flags = 0;
if($where == "group")
$groupId = $this->postParam("groupId");
if(!is_null($this->user)) {
$nPost = new Post;
$nPost->setOwner($this->user->id);
$nPost->setWall($this->user->id);
if($where == "wall") {
$nPost->setOwner($this->user->id);
$nPost->setWall($this->user->id);
} elseif($where == "group") {
$nPost->setOwner($this->user->id);
$club = (new Clubs)->get((int)$groupId);
if(!$club || !$club->canBeModifiedBy($this->user->identity))
$this->notFound();
if($this->postParam("asGroup") == 1)
$flags |= 0b10000000;
if($this->postParam("signed") == 1)
$flags |= 0b01000000;
$nPost->setWall($groupId * -1);
}
$nPost->setContent($this->postParam("text"));
$nPost->setFlags($flags);
$nPost->save();
$nPost->attach($post);
if($post->getOwner(false)->getId() !== $this->user->identity->getId() && !($post->getOwner() instanceof Club))
(new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit();
};
$this->returnJson(["wall_owner" => $this->user->identity->getId()]);
$this->returnJson([
"wall_owner" => $where == "wall" ? $this->user->identity->getId() : $groupId * -1
]);
}
function renderDelete(int $wall, int $post_id): void
@ -405,6 +485,9 @@ final class WallPresenter extends OpenVKPresenter
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
if ($wallOwner->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if($wall < 0) $canBeDeletedByOtherUser = $wallOwner->canBeModifiedBy($this->user->identity);
else $canBeDeletedByOtherUser = false;
@ -428,6 +511,9 @@ final class WallPresenter extends OpenVKPresenter
$post = $this->posts->getPostById($wall, $post_id);
if(!$post)
$this->notFound();
if ($post->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!$post->canBePinnedBy($this->user->identity))
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
@ -441,4 +527,64 @@ final class WallPresenter extends OpenVKPresenter
# TODO localize message based on language and ?act=(un)pin
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
}
function renderEdit()
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if($_SERVER["REQUEST_METHOD"] !== "POST")
$this->redirect("/id0");
if($this->postParam("type") == "post")
$post = $this->posts->get((int)$this->postParam("postid"));
else
$post = (new Comments)->get((int)$this->postParam("postid"));
if(!$post || $post->isDeleted())
$this->returnJson(["error" => "Invalid post"]);
if(!$post->canBeEditedBy($this->user->identity))
$this->returnJson(["error" => "Access denied"]);
$attachmentsCount = sizeof(iterator_to_array($post->getChildren()));
if(empty($this->postParam("newContent")) && $attachmentsCount < 1)
$this->returnJson(["error" => "Empty post"]);
$post->setEdited(time());
try {
$post->setContent($this->postParam("newContent"));
} catch(\LengthException $e) {
$this->returnJson(["error" => $e->getMessage()]);
}
if($this->postParam("type") === "post") {
$post->setNsfw($this->postParam("nsfw") == "true");
$flags = 0;
if($post->getTargetWall() < 0 && $post->getWallOwner()->canBeModifiedBy($this->user->identity)) {
if($this->postParam("fromgroup") == "true") {
$flags |= 0b10000000;
$post->setFlags($flags);
} else
$post->setFlags($flags);
}
}
$post->save(true);
$this->returnJson(["error" => "no",
"new_content" => $post->getText(),
"new_edited" => (string)$post->getEditTime(),
"nsfw" => $this->postParam("type") === "post" ? (int)$post->isExplicit() : 0,
"from_group" => $this->postParam("type") === "post" && $post->getTargetWall() < 0 ?
((int)$post->isPostedOnBehalfOfGroup()) : "false",
"new_text" => $post->getText(false),
"author" => [
"name" => $post->getOwner()->getCanonicalName(),
"avatar" => $post->getOwner()->getAvatarUrl()
]]);
}
}

View file

@ -10,8 +10,19 @@
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_banned_alt}" style="width: 20%;" />
</center>
<p>
{tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/>
{tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape}
{var $ban = $thisUser->getBanReason("banned")}
{if is_string($ban)}
{tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/>
{tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape}
{else}
{tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}
<div>
Эта страница была заморожена {$ban[0]|noescape}
{if $ban[1] !== "app"}
{include "Report/ViewContent.xml", type => $ban[1], object => $ban[2]}
{/if}
</div>
{/if}
{if !$thisUser->getUnbanTime()}
{_banned_perm}

View file

@ -38,9 +38,8 @@
<body>
<div id="sudo-banner" n:if="isset($thisUser) && $userTainted">
<p>
Вы вошли как <b>{$thisUser->getCanonicalName()}</b>. Пожалуйста, уважайте
право на тайну переписки других людей и не злоупотребляйте подменой пользователя.
Нажмите <a href="/setSID/unset?hash={rawurlencode($csrfToken)}">здесь</a>, чтобы выйти.
{_you_entered_as} <b>{$thisUser->getCanonicalName()}</b>. {_please_rights}
{_click_on} <a href="/setSID/unset?hash={rawurlencode($csrfToken)}">{_there}</a>, {_to_leave}.
</p>
</div>
@ -49,6 +48,28 @@
<div class="notifications_global_wrap"></div>
<div class="dimmer"></div>
<div class="articleView">
<a id="articleCloseButton" class="button" href="javascript:void(u('body').removeClass('article'));">{_close}</a>
<div class="articleView_container">
<div class="articleView_info">
<div class="articleView_author">
<img id="articleAuthorAva" src="" />
<div>
<span><a id="articleAuthorName"></a></span>
<time id="articleTime"></time>
</div>
</div>
<div class="articleView_link">
<a id="articleLink" href="/" class="button">{_aw_legacy_ui}</a>
</div>
</div>
<div class="articleView_text" id="articleText">
</div>
</div>
</div>
{if isset($backdrops) && !is_null($backdrops)}
<div id="backdrop" style="background-image: url('{$backdrops[0]|noescape}'), url('{$backdrops[1]|noescape}');">
<div id="backdropDripper"></div>
@ -70,28 +91,66 @@
<a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a>
</div>
{else}
<div class="link">
<div class="link dec">
<a href="/">{_header_home}</a>
</div>
<div class="link">
<div class="link dec">
<a href="/search?type=groups">{_header_groups}</a>
</div>
<div class="link">
<div class="link dec">
<a href="/search">{_header_search}</a>
</div>
<div class="link">
<div class="link dec">
<a href="/invite">{_header_invite}</a>
</div>
<div class="link">
<div class="link dec">
<a href="/support">{_header_help} <b n:if="$ticketAnsweredCount > 0">({$ticketAnsweredCount})</b></a>
</div>
<div class="link">
<div class="link dec">
<a href="/logout?hash={urlencode($csrfToken)}">{_header_log_out}</a>
</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;" title="{_header_search} [Alt+Shift+F]" accesskey="f" />
</form>
{var $atSearch = str_contains($_SERVER['REQUEST_URI'], "/search")}
<div id="srch" class="{if $atSearch}nodivider{else}link{/if}">
{if !$atSearch}
<form action="/search" method="get" id="searcher" style="position:relative;">
<input autocomplete="off" id="searchInput" oninput="checkSearchTips()" onfocus="expandSearch()" onblur="decreaseSearch()" class="sr" 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" />
<select onchange="checkSearchTips()" id="typer" name="type" class="whatFind" style="display:none;top: 0px;">
<option value="users">{_s_by_people}</option>
<option value="groups">{_s_by_groups}</option>
<option value="posts">{_s_by_posts}</option>
<option value="comments">{_s_by_comments}</option>
<option value="videos">{_s_by_videos}</option>
<option value="apps">{_s_by_apps}</option>
</select>
</form>
<div class="searchTips" id="srcht" hidden>
<table style="border:none;border-spacing: 0;">
<tbody id="srchrr">
</tbody>
</table>
</div>
{else}
<form action="/search" method="get" id="searcher" style="margin-top: -1px;position:relative;">
<input id="searchInput" value="{$_GET['query'] ?? ''}" type="search" class="sr" name="query" placeholder="{_header_search}" style="height: 20px; background-color: #fff; padding-left: 6px;width: 555px;" title="{_header_search} [Alt+Shift+F]" accesskey="f" />
<select name="type" class="whatFind">
<option value="users" {if str_contains($_SERVER['REQUEST_URI'], "type=users")}selected{/if}>{_s_by_people}</option>
<option value="groups" {if str_contains($_SERVER['REQUEST_URI'], "type=groups")}selected{/if}>{_s_by_groups}</option>
<option value="posts" {if str_contains($_SERVER['REQUEST_URI'], "type=posts")}selected{/if}>{_s_by_posts}</option>
<option value="comments" {if str_contains($_SERVER['REQUEST_URI'], "type=comments")}selected{/if}>{_s_by_comments}</option>
<option value="videos" {if str_contains($_SERVER['REQUEST_URI'], "type=videos")}selected{/if}>{_s_by_videos}</option>
<option value="apps" {if str_contains($_SERVER['REQUEST_URI'], "type=apps")}selected{/if}>{_s_by_apps}</option>
</select>
<button class="searchBtn"><span style="color:white;font-weight: 600;font-size:12px;">{_header_search}</span></button>
</form>
<script>
let els = document.querySelectorAll("div.dec")
for(const element of els)
{
element.style.display = "none"
}
</script>
{/if}
</div>
{/if}
{else}
@ -116,7 +175,7 @@
<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">
<a href="/friends{$thisUser->getId()}?act=incoming" class="linkunderline">
(<b>{$thisUser->getFollowersCount()}</b>)
</a>
</object>
@ -136,7 +195,7 @@
(<b>{$thisUser->getNotificationsCount()}</b>)
{/if}
</a>
<a href="/apps?act=installed" class="link">{_my_apps}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('apps')" href="/apps?act=installed" class="link">{_my_apps}</a>
<a href="/settings" class="link">{_my_settings}</a>
{var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
@ -149,8 +208,23 @@
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
{/if}
</a>
<a n:if="$thisUser->getLeftMenuItemStatus('links')" n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem" href="{$menuItem['url']}" target="_blank" class="link">{strpos($menuItem["name"], "@") === 0 ? tr(substr($menuItem["name"], 1)) : $menuItem["name"]}</a>
<a n:if="$canAccessHelpdesk" href="/scumfeed" class="link">{tr("reports")}
{if $reportNotAnsweredCount > 0}
(<b>{$reportNotAnsweredCount}</b>)
{/if}
</a>
<a n:if="$canAccessHelpdesk" href="/noSpam" class="link">
noSpam
</a>
<a
n:if="$thisUser->getLeftMenuItemStatus('links')"
n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem"
href="{$menuItem['url']}"
target="_blank"
class="link">
{strpos($menuItem["name"], "@") === 0 ? tr(substr($menuItem["name"], 1)) : $menuItem["name"]}
</a>
<div id="_groupListPinnedGroups">
<div id="_groupListPinnedGroups">
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
@ -220,11 +294,16 @@
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_log_in}" class="button" style="display: inline-block; font-family: Tahoma" />
<a href="/reg"><input type="button" value="{_registration}" class="button" style="font-family: Tahoma" /></a><br><br>
<a href="/restore">{_forgot_password}</a>
{if !OPENVK_ROOT_CONF['openvk']['preferences']['security']['disablePasswordRestoring']}<a href="/restore">{_forgot_password}</a>{/if}
</form>
{/ifset}
</div>
</div>
</div>
{ifset $thisUser}
{if !$thisUser->isBanned() && !$thisUser->isDeleted()}
</div>
{/if}
{/ifset}
<div class="page_body">
<div id="wrapH">
@ -266,6 +345,7 @@
<div class="navigation_footer">
<a href="/about" class="link">{_footer_about_instance}</a>
<a href="/terms" class="link">{_footer_rules}</a>
<a href="/blog" class="link">{_footer_blog}</a>
<a href="/support" class="link">{_footer_help}</a>
<a href="/dev" target="_blank" class="link">{_footer_developers}</a>

View file

@ -12,16 +12,24 @@
{include size, x => $dat}
{/ifset}
{ifset before_content}
{include before_content, 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 top}
{include top, x => $dat}
{/ifset}
{if sizeof($data) > 0}
<div class="content" n:foreach="$data as $dat">
<table>
<tbody>
<tbody n:attr="id => is_null($table_body_id) ? NULL : $table_body_id">
<tr>
<td valign="top">
<a href="{include link, x => $dat}">

View file

@ -74,6 +74,6 @@
<h4>{_rules}</h4>
<div style="margin-top: 16px;">
{presenter "openvk!Support->knowledgeBaseArticle", "rules"}
{tr("about_watch_rules", "/terms")|noescape}
</div>
{/block}

View file

@ -1,12 +1,10 @@
{extends "../@layout.xml"}
{block title}Ваш браузер устарел{/block}
{block title}{_deprecated_browser}{/block}
{block header}
Устаревший браузер
{_deprecated_browser}
{/block}
{block content}
Для просмотра этого контента вам понадобится Firefox ESR 52+ или
эквивалентный по функционалу навигатор по всемирной сети интернет.<br/>
Сожалеем об этом.
{_deprecated_browser_description}
{/block}

View file

@ -9,5 +9,5 @@
<div id="faqhead">Для кого этот сайт?</div>
<div id="faqcontent">Сайт предназначен для поиска друзей и знакомых, а также просмотр данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. Также этот сайт подойдёт для ностальгираторов и тех, кто решил слезть с трубы "ВКонтакте", которого клон и является.<br></div>
Я попозже допишу ок ~~ veselcraft - 12.01.2020 - 22:05 GMT+3
Давай
{/block}

View file

@ -2,7 +2,7 @@
{block title}Sandbox{/block}
{block header}
Sandbox для разработчиков
{_sandbox_for_developers}
{/block}
{block content}

View file

@ -458,7 +458,7 @@
</tr>
<tr>
<td class="e">Initial hosting</td>
<td class="v">Ilya Prokopenko (dsrev) and Celestora</td>
<td class="v">Lumaeris and Celestora</td>
</tr>
<tr>
<td class="e">Initial bug-tracker hosting</td>
@ -492,7 +492,7 @@
<td>
kovaltim, Vladimir Lapskiy (0x7d5), Alexander Minkin (WerySkok), Polina Katunina (RousPhaul), veth,
Egor Shevchenko, Vadim Korovin (yuni), Ash Defenders,
Pavel Silaev, Dmitriy Daemon, Ilya Prokopenko (dsrev),
Pavel Silaev, Dmitriy Daemon, Lumaeris,
cmed404 and unknown tester, who disappeared shortly after trying to upload post with cat.
</td>
</tr>

View file

@ -6,6 +6,7 @@
<style>
{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}
{file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping-darkmode.css")|noescape}
</style>
<title>{include title} - {_admin} {$instance_name}</title>
</head>
@ -21,12 +22,49 @@
</a>
</h1>
</div>
<div n:if="$search ?? false" class="aui-header-secondary">
<div 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 />
</form>
<li n:if="$search ?? false">
<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 />
</form>
</li>
<li>
<aui-toggle id="switch-theme" label="Toggle dark mode"></aui-toggle>
<script>
const toggle = document.getElementById("switch-theme");
let currentTheme = localStorage.getItem("ovkadmin-theme");
if (currentTheme == null) {
const preferDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
let theme = "light";
if (preferDarkScheme.matches) {
theme = "dark";
document.body.classList.add("aui-theme-dark");
}
localStorage.setItem("ovkadmin-theme", theme);
}
if (currentTheme == "dark") {
document.body.classList.add("aui-theme-dark");
}
toggle.addEventListener("click", function() {
document.body.classList.toggle("aui-theme-dark");
let theme = "light";
if (document.body.classList.contains("aui-theme-dark")) {
theme = "dark";
}
localStorage.setItem("ovkadmin-theme", theme);
});
</script>
</li>
</ul>
</div>
</div>
@ -34,7 +72,7 @@
</header>
<div class="aui-page-panel">
<div class="aui-page-panel-inner">
<div class="aui-page-panel-nav" style="background-color: #fff;">
<div class="aui-page-panel-nav">
<nav class="aui-navgroup aui-navgroup-vertical">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
@ -86,6 +124,9 @@
<li>
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
</li>
<li>
<a href="/admin/logs">Логи</a>
</li>
<li>
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
</li>

View file

@ -0,0 +1,86 @@
{extends "./@layout.xml"}
{block title}
{_bans_history}
{/block}
{block heading}
{include title}
{/block}
{block content}
<table class="aui aui-table-list">
<thead>
<tr>
<th>ID</th>
<th>{_bans_history_blocked}</th>
<th>{_bans_history_initiator}</th>
<th>{_bans_history_start}</th>
<th>{_bans_history_end}</th>
<th>{_bans_history_time}</th>
<th>{_bans_history_reason}</th>
<th>{_bans_history_removed}</th>
</tr>
</thead>
<tbody>
<tr n:foreach="$bans as $ban">
<td>{$ban->getId()}</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$ban->getUser()->getAvatarUrl('miniscule')}"
alt="{$ban->getUser()->getCanonicalName()}" style="object-fit: cover;"
role="presentation"/>
</span>
</span>
<a href="{$ban->getUser()->getURL()}">{$ban->getUser()->getCanonicalName()}</a>
<span n:if="$ban->getUser()->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
{_admin_banned}
</span>
</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$ban->getInitiator()->getAvatarUrl('miniscule')}"
alt="{$ban->getInitiator()->getCanonicalName()}" style="object-fit: cover;"
role="presentation"/>
</span>
</span>
<a href="{$ban->getInitiator()->getURL()}">{$ban->getInitiator()->getCanonicalName()}</a>
<span n:if="$ban->getInitiator()->isBanned()"
class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}
</span>
</td>
<td>{date('d.m.Y в H:i:s', $ban->getStartTime())}</td>
<td>{date('d.m.Y в H:i:s', $ban->getEndTime())}</td>
<td>{$ban->getTime()}</td>
<td>
{$ban->getReason()}
</td>
<td>
{if $ban->isRemovedManually()}
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$ban->whoRemoved()->getAvatarUrl('miniscule')}"
alt="{$ban->whoRemoved()->getCanonicalName()}" style="object-fit: cover;"
role="presentation"/>
</span>
</span>
<a href="{$ban->whoRemoved()->getURL()}">{$ban->whoRemoved()->getCanonicalName()}</a>
<span n:if="$ban->whoRemoved()->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
{_admin_banned}
</span>
{else}
<b style="color: red;">{_bans_history_active}</b>
{/if}
</td>
</tr>
</tbody>
</table>
{/block}

View file

@ -0,0 +1,92 @@
{extends "@layout.xml"}
{block title}
{_logs}
{/block}
{block heading}
{_logs}
{/block}
{block content}
{var $amount = sizeof($logs)}
<style>
del, ins { text-decoration: none; color: #000; }
del { background: #fdd; }
ins { background: #dfd; }
</style>
<form class="aui">
<div>
<select class="select medium-field" type="number" id="type" name="type" placeholder="{_logs_change_type}">
<option value="any" n:attr="selected => !$type">{_logs_anything}</option>
<option value="0" n:attr="selected => $type === 0">{_logs_adding}</option>
<option value="1" n:attr="selected => $type === 1">{_logs_editing}</option>
<option value="2" n:attr="selected => $type === 2">{_logs_removing}</option>
<option value="3" n:attr="selected => $type === 3">{_logs_restoring}</option>
</select>
<input class="text medium-field" type="number" id="id" name="id" placeholder="{_logs_id_post}" n:attr="value => $id"/>
<input class="text medium-field" type="text" id="uid" name="uid" placeholder="{_logs_uuid_user}" n:attr="value => $user"/>
</div>
<div style="margin: 8px 0;" />
<div>
<select class="select medium-field" id="obj_type" name="obj_type" placeholder="{_logs_change_object}">
<option value="any" n:attr="selected => !$obj_type">{_logs_anything}</option>
<option n:foreach="$object_types as $type" n:attr="selected => $obj_type === $type">{$type}</option>
</select>
<input class="text medium-field" type="number" id="obj_id" name="obj_id" placeholder="{_logs_id_object}" n:attr="value => $obj_id"/>
<input type="submit" class="aui-button aui-button-primary medium-field" value="Поиск" style="width: 165px;"/>
</div>
</form>
<table class="aui aui-table-list">
<thead>
<tr>
<th>ID</th>
<th>{_logs_user}</th>
<th>{_logs_object}</th>
<th>{_logs_type}</th>
<th>{_logs_changes}</th>
<th>{_logs_time}</th>
</tr>
</thead>
<tbody>
<tr n:foreach="$logs as $log">
<td>{$log->getId()}</td>
<td>
<a href="/admin/chandler/user/{$log->getUser()}" target="_blank">{$log->getUser()}</a>
</td>
<td>
<span n:if="$log->getObjectAvatar()" class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$log->getObjectAvatar()}" alt="{$log->getObjectName()}" style="object-fit: cover;" role="presentation" />
</span>
</span>
<a href="{$log->getObjectURL()}">{$log->getObjectName()}</a>
</td>
<td>{_$log->getTypeNom()}</td>
<td>
{foreach $log->getChanges() as $change}
<div>
<b>{$change["field"]}</b>:
{if array_key_exists('diff', $change)}
{$change["diff"]|noescape}
{else}
<ins>{$change["old_value"]}</ins>
{/if}
</div>
{/foreach}
</td>
<td>
{=new openvk\Web\Util\DateTime($change["ts"])}
</td>
</tr>
</tbody>
</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}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -69,6 +69,16 @@
</select>
</div>
<hr/>
<div class="field-group">
<label for="email">{_password}</label>
<input class="text medium-field" type="password" id="password" name="password" value="" />
</div>
<div class="buttons-container">
<div class="buttons">
<a class="button" onclick="let pswd = Math.random().toString(27).slice(2,12); alert('Сгенерированный пароль: ' + pswd + '\n\nНе забудьте сообщить пользователю, чтобы он сменил пароль на свой!'); $('input#password').val(pswd);">Сгенерировать пароль</a>
</div>
</div>
<hr/>
<h2>{_c_groups}</h2>
<div>
<div class="field-group">

View file

@ -1,4 +1,5 @@
{extends "../@layout.xml"}
{var $canReport = $owner->getId() !== $thisUser->getId()}
{block title}
{$name}
@ -6,6 +7,7 @@
{block header}
{$name}
<a style="float: right;" onClick="reportApp()" n:if="$canReport ?? false">{_report}</a>
{/block}
{block content}
@ -33,5 +35,29 @@
window.appOrigin = {$origin};
</script>
<script n:if="$canReport ?? false">
function reportApp() {
uReportMsgTxt = {_going_to_report_app};
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$id} + "?reason=" + res + "&type=app", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
{script "js/al_games.js"}
{/block}

View file

@ -11,7 +11,7 @@
<tbody>
<tr>
<td>
{_two_factor_authentication_login}
{_two_factor_authentication_login}
</td>
</tr>
</tbody>
@ -25,7 +25,7 @@
<span class="nobold">{_code}: </span>
</td>
<td>
<input type="text" name="code" autocomplete="off" required />
<input type="text" name="code" autocomplete="off" required autofocus />
</td>
</tr>
<tr>

View file

@ -101,14 +101,16 @@
</select>
</td>
</tr>
<tr>
<td class="regform-left">
<span class="nobold">CAPTCHA: </span>
</td>
<td class="regform-right">
{captcha_template()|noescape}
</td>
</tr>
{if !(strpos(captcha_template(), 'verified'))}
<tr>
<td class="regform-left">
<span class="nobold">CAPTCHA: </span>
</td>
<td class="regform-right">
{captcha_template()|noescape}
</td>
</tr>
{/if}
</table>
<div style="margin-left: 100px; margin-right: 100px; text-align: center;">
<input type="hidden" name="hash" value="{$csrfToken}" />
@ -120,21 +122,18 @@
</form>
</div>
{else}
<h4>{_registration_closed}</h4>
<h4>{_registration_closed}</h4>
<table cellspacing="10" cellpadding="0" border="0" align="center" style="margin: 9px;">
<tbody>
<tr>
<td style="width: 20%;">
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_registration_closed}" style="width: 100%;"/>
<img src="/assets/packages/static/openvk/img/oof.apng" alt="{_registration_closed}" style="width: 100%;"/>
</td>
<td>
{_registration_disabled_info}
{if OPENVK_ROOT_CONF['openvk']['preferences']['registration']['reason']}
<br/>
<br/>
{_admin_banned_link_reason}:
<br>
<b>{php echo OPENVK_ROOT_CONF['openvk']['preferences']['registration']['reason']}</b>
{if OPENVK_ROOT_CONF['openvk']['preferences']['registration']['disablingReason']}
<br/><br/>{_admin_banned_link_reason}:<br>
<b>{php echo OPENVK_ROOT_CONF['openvk']['preferences']['registration']['disablingReason']}</b>
{/if}
</td>
</tr>

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