and must not be empty
* will look for an image file in $basedir/images/a.png and convert it to inline.
* If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
* Converts data-uri images into embedded attachments.
* If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
*
* @param string $message HTML message string
* @param string $basedir Absolute path to a base directory to prepend to relative paths to images
* @param bool|callable $advanced Whether to use the internal HTML to text converter
* or your own custom converter @see PHPMailer::html2text()
*
* @return string $message The transformed message Body
*/
public function msgHTML($message, $basedir = '', $advanced = false)
{
preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
if (array_key_exists(2, $images)) {
if (strlen($basedir) > 1 && '/' != substr($basedir, -1)) {
// Ensure $basedir has a trailing /
$basedir .= '/';
}
foreach ($images[2] as $imgindex => $url) {
// Convert data URIs into embedded images
//e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
if (count($match) == 4 and static::ENCODING_BASE64 == $match[2]) {
$data = base64_decode($match[3]);
} elseif ('' == $match[2]) {
$data = rawurldecode($match[3]);
} else {
//Not recognised so leave it alone
continue;
}
//Hash the decoded data, not the URL so that the same data-URI image used in multiple places
//will only be embedded once, even if it used a different encoding
$cid = hash('sha256', $data) . '@phpmailer.0'; // RFC2392 S 2
if (!$this->cidExists($cid)) {
$this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, static::ENCODING_BASE64, $match[1]);
}
$message = str_replace(
$images[0][$imgindex],
$images[1][$imgindex] . '="cid:' . $cid . '"',
$message
);
continue;
}
if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
!empty($basedir)
// Ignore URLs containing parent dir traversal (..)
and (strpos($url, '..') === false)
// Do not change urls that are already inline images
and 0 !== strpos($url, 'cid:')
// Do not change absolute URLs, including anonymous protocol
and !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
) {
$filename = basename($url);
$directory = dirname($url);
if ('.' == $directory) {
$directory = '';
}
$cid = hash('sha256', $url) . '@phpmailer.0'; // RFC2392 S 2
if (strlen($basedir) > 1 and '/' != substr($basedir, -1)) {
$basedir .= '/';
}
if (strlen($directory) > 1 and '/' != substr($directory, -1)) {
$directory .= '/';
}
if ($this->addEmbeddedImage(
$basedir . $directory . $filename,
$cid,
$filename,
static::ENCODING_BASE64,
static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))
)
) {
$message = preg_replace(
'/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
$images[1][$imgindex] . '="cid:' . $cid . '"',
$message
);
}
}
}
}
$this->isHTML(true);
// Convert all message body line breaks to LE, makes quoted-printable encoding work much better
$this->Body = static::normalizeBreaks($message);
$this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
if (!$this->alternativeExists()) {
$this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
. static::$LE;
}
return $this->Body;
}
/**
* Convert an HTML string into plain text.
* This is used by msgHTML().
* Note - older versions of this function used a bundled advanced converter
* which was removed for license reasons in #232.
* Example usage:
*
* ```php
* // Use default conversion
* $plain = $mail->html2text($html);
* // Use your own custom converter
* $plain = $mail->html2text($html, function($html) {
* $converter = new MyHtml2text($html);
* return $converter->get_text();
* });
* ```
*
* @param string $html The HTML text to convert
* @param bool|callable $advanced Any boolean value to use the internal converter,
* or provide your own callable for custom conversion
*
* @return string
*/
public function html2text($html, $advanced = false)
{
if (is_callable($advanced)) {
return call_user_func($advanced, $html);
}
return html_entity_decode(
trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
ENT_QUOTES,
$this->CharSet
);
}
/**
* Get the MIME type for a file extension.
*
* @param string $ext File extension
*
* @return string MIME type of file
*/
public static function _mime_types($ext = '')
{
$mimes = [
'xl' => 'application/excel',
'js' => 'application/javascript',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'bin' => 'application/macbinary',
'doc' => 'application/msword',
'word' => 'application/msword',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'class' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'dms' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'lha' => 'application/octet-stream',
'lzh' => 'application/octet-stream',
'psd' => 'application/octet-stream',
'sea' => 'application/octet-stream',
'so' => 'application/octet-stream',
'oda' => 'application/oda',
'pdf' => 'application/pdf',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'smi' => 'application/smil',
'smil' => 'application/smil',
'mif' => 'application/vnd.mif',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'gtar' => 'application/x-gtar',
'php3' => 'application/x-httpd-php',
'php4' => 'application/x-httpd-php',
'php' => 'application/x-httpd-php',
'phtml' => 'application/x-httpd-php',
'phps' => 'application/x-httpd-php-source',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'tar' => 'application/x-tar',
'tgz' => 'application/x-tar',
'xht' => 'application/xhtml+xml',
'xhtml' => 'application/xhtml+xml',
'zip' => 'application/zip',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'm4a' => 'audio/mp4',
'mpga' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'ram' => 'audio/x-pn-realaudio',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'ra' => 'audio/x-realaudio',
'wav' => 'audio/x-wav',
'mka' => 'audio/x-matroska',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'jpeg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'webp' => 'image/webp',
'heif' => 'image/heif',
'heifs' => 'image/heif-sequence',
'heic' => 'image/heic',
'heics' => 'image/heic-sequence',
'eml' => 'message/rfc822',
'css' => 'text/css',
'html' => 'text/html',
'htm' => 'text/html',
'shtml' => 'text/html',
'log' => 'text/plain',
'text' => 'text/plain',
'txt' => 'text/plain',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'vcf' => 'text/vcard',
'vcard' => 'text/vcard',
'ics' => 'text/calendar',
'xml' => 'text/xml',
'xsl' => 'text/xml',
'wmv' => 'video/x-ms-wmv',
'mpeg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mp4' => 'video/mp4',
'm4v' => 'video/mp4',
'mov' => 'video/quicktime',
'qt' => 'video/quicktime',
'rv' => 'video/vnd.rn-realvideo',
'avi' => 'video/x-msvideo',
'movie' => 'video/x-sgi-movie',
'webm' => 'video/webm',
'mkv' => 'video/x-matroska',
];
$ext = strtolower($ext);
if (array_key_exists($ext, $mimes)) {
return $mimes[$ext];
}
return 'application/octet-stream';
}
/**
* Map a file name to a MIME type.
* Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
*
* @param string $filename A file name or full path, does not need to exist as a file
*
* @return string
*/
public static function filenameToType($filename)
{
// In case the path is a URL, strip any query string before getting extension
$qpos = strpos($filename, '?');
if (false !== $qpos) {
$filename = substr($filename, 0, $qpos);
}
$ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
return static::_mime_types($ext);
}
/**
* Multi-byte-safe pathinfo replacement.
* Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
*
* @see http://www.php.net/manual/en/function.pathinfo.php#107461
*
* @param string $path A filename or path, does not need to exist as a file
* @param int|string $options Either a PATHINFO_* constant,
* or a string name to return only the specified piece
*
* @return string|array
*/
public static function mb_pathinfo($path, $options = null)
{
$ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
$pathinfo = [];
if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$#im', $path, $pathinfo)) {
if (array_key_exists(1, $pathinfo)) {
$ret['dirname'] = $pathinfo[1];
}
if (array_key_exists(2, $pathinfo)) {
$ret['basename'] = $pathinfo[2];
}
if (array_key_exists(5, $pathinfo)) {
$ret['extension'] = $pathinfo[5];
}
if (array_key_exists(3, $pathinfo)) {
$ret['filename'] = $pathinfo[3];
}
}
switch ($options) {
case PATHINFO_DIRNAME:
case 'dirname':
return $ret['dirname'];
case PATHINFO_BASENAME:
case 'basename':
return $ret['basename'];
case PATHINFO_EXTENSION:
case 'extension':
return $ret['extension'];
case PATHINFO_FILENAME:
case 'filename':
return $ret['filename'];
default:
return $ret;
}
}
/**
* Set or reset instance properties.
* You should avoid this function - it's more verbose, less efficient, more error-prone and
* harder to debug than setting properties directly.
* Usage Example:
* `$mail->set('SMTPSecure', 'tls');`
* is the same as:
* `$mail->SMTPSecure = 'tls';`.
*
* @param string $name The property name to set
* @param mixed $value The value to set the property to
*
* @return bool
*/
public function set($name, $value = '')
{
if (property_exists($this, $name)) {
$this->$name = $value;
return true;
}
$this->setError($this->lang('variable_set') . $name);
return false;
}
/**
* Strip newlines to prevent header injection.
*
* @param string $str
*
* @return string
*/
public function secureHeader($str)
{
return trim(str_replace(["\r", "\n"], '', $str));
}
/**
* Normalize line breaks in a string.
* Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
* Defaults to CRLF (for message bodies) and preserves consecutive breaks.
*
* @param string $text
* @param string $breaktype What kind of line break to use; defaults to static::$LE
*
* @return string
*/
public static function normalizeBreaks($text, $breaktype = null)
{
if (null === $breaktype) {
$breaktype = static::$LE;
}
// Normalise to \n
$text = str_replace(["\r\n", "\r"], "\n", $text);
// Now convert LE as needed
if ("\n" !== $breaktype) {
$text = str_replace("\n", $breaktype, $text);
}
return $text;
}
/**
* Return the current line break format string.
*
* @return string
*/
public static function getLE()
{
return static::$LE;
}
/**
* Set the line break format string, e.g. "\r\n".
*
* @param string $le
*/
protected static function setLE($le)
{
static::$LE = $le;
}
/**
* Set the public and private key files and password for S/MIME signing.
*
* @param string $cert_filename
* @param string $key_filename
* @param string $key_pass Password for private key
* @param string $extracerts_filename Optional path to chain certificate
*/
public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
{
$this->sign_cert_file = $cert_filename;
$this->sign_key_file = $key_filename;
$this->sign_key_pass = $key_pass;
$this->sign_extracerts_file = $extracerts_filename;
}
/**
* Quoted-Printable-encode a DKIM header.
*
* @param string $txt
*
* @return string
*/
public function DKIM_QP($txt)
{
$line = '';
$len = strlen($txt);
for ($i = 0; $i < $len; ++$i) {
$ord = ord($txt[$i]);
if (((0x21 <= $ord) and ($ord <= 0x3A)) or $ord == 0x3C or ((0x3E <= $ord) and ($ord <= 0x7E))) {
$line .= $txt[$i];
} else {
$line .= '=' . sprintf('%02X', $ord);
}
}
return $line;
}
/**
* Generate a DKIM signature.
*
* @param string $signHeader
*
* @throws Exception
*
* @return string The DKIM signature value
*/
public function DKIM_Sign($signHeader)
{
if (!defined('PKCS7_TEXT')) {
if ($this->exceptions) {
throw new Exception($this->lang('extension_missing') . 'openssl');
}
return '';
}
$privKeyStr = !empty($this->DKIM_private_string) ?
$this->DKIM_private_string :
file_get_contents($this->DKIM_private);
if ('' != $this->DKIM_passphrase) {
$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
} else {
$privKey = openssl_pkey_get_private($privKeyStr);
}
if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
openssl_pkey_free($privKey);
return base64_encode($signature);
}
openssl_pkey_free($privKey);
return '';
}
/**
* Generate a DKIM canonicalization header.
* Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
* Canonicalized headers should *always* use CRLF, regardless of mailer setting.
*
* @see https://tools.ietf.org/html/rfc6376#section-3.4.2
*
* @param string $signHeader Header
*
* @return string
*/
public function DKIM_HeaderC($signHeader)
{
//Unfold all header continuation lines
//Also collapses folded whitespace.
//Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
//@see https://tools.ietf.org/html/rfc5322#section-2.2
//That means this may break if you do something daft like put vertical tabs in your headers.
$signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
$lines = explode("\r\n", $signHeader);
foreach ($lines as $key => $line) {
//If the header is missing a :, skip it as it's invalid
//This is likely to happen because the explode() above will also split
//on the trailing LE, leaving an empty line
if (strpos($line, ':') === false) {
continue;
}
list($heading, $value) = explode(':', $line, 2);
//Lower-case header name
$heading = strtolower($heading);
//Collapse white space within the value
$value = preg_replace('/[ \t]{2,}/', ' ', $value);
//RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
//But then says to delete space before and after the colon.
//Net result is the same as trimming both ends of the value.
//by elimination, the same applies to the field name
$lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
}
return implode("\r\n", $lines);
}
/**
* Generate a DKIM canonicalization body.
* Uses the 'simple' algorithm from RFC6376 section 3.4.3.
* Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
*
* @see https://tools.ietf.org/html/rfc6376#section-3.4.3
*
* @param string $body Message Body
*
* @return string
*/
public function DKIM_BodyC($body)
{
if (empty($body)) {
return "\r\n";
}
// Normalize line endings to CRLF
$body = static::normalizeBreaks($body, "\r\n");
//Reduce multiple trailing line breaks to a single one
return rtrim($body, "\r\n") . "\r\n";
}
/**
* Create the DKIM header and body in a new message header.
*
* @param string $headers_line Header lines
* @param string $subject Subject
* @param string $body Body
*
* @return string
*/
public function DKIM_Add($headers_line, $subject, $body)
{
$DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
$DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
$DKIMquery = 'dns/txt'; // Query method
$DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
$subject_header = "Subject: $subject";
$headers = explode(static::$LE, $headers_line);
$from_header = '';
$to_header = '';
$date_header = '';
$current = '';
$copiedHeaderFields = '';
$foundExtraHeaders = [];
$extraHeaderKeys = '';
$extraHeaderValues = '';
$extraCopyHeaderFields = '';
foreach ($headers as $header) {
if (strpos($header, 'From:') === 0) {
$from_header = $header;
$current = 'from_header';
} elseif (strpos($header, 'To:') === 0) {
$to_header = $header;
$current = 'to_header';
} elseif (strpos($header, 'Date:') === 0) {
$date_header = $header;
$current = 'date_header';
} elseif (!empty($this->DKIM_extraHeaders)) {
foreach ($this->DKIM_extraHeaders as $extraHeader) {
if (strpos($header, $extraHeader . ':') === 0) {
$headerValue = $header;
foreach ($this->CustomHeader as $customHeader) {
if ($customHeader[0] === $extraHeader) {
$headerValue = trim($customHeader[0]) .
': ' .
$this->encodeHeader(trim($customHeader[1]));
break;
}
}
$foundExtraHeaders[$extraHeader] = $headerValue;
$current = '';
break;
}
}
} else {
if (!empty($$current) and strpos($header, ' =?') === 0) {
$$current .= $header;
} else {
$current = '';
}
}
}
foreach ($foundExtraHeaders as $key => $value) {
$extraHeaderKeys .= ':' . $key;
$extraHeaderValues .= $value . "\r\n";
if ($this->DKIM_copyHeaderFields) {
$extraCopyHeaderFields .= "\t|" . str_replace('|', '=7C', $this->DKIM_QP($value)) . ";\r\n";
}
}
if ($this->DKIM_copyHeaderFields) {
$from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
$to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
$date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
$subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header));
$copiedHeaderFields = "\tz=$from\r\n" .
"\t|$to\r\n" .
"\t|$date\r\n" .
"\t|$subject;\r\n" .
$extraCopyHeaderFields;
}
$body = $this->DKIM_BodyC($body);
$DKIMlen = strlen($body); // Length of body
$DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
if ('' == $this->DKIM_identity) {
$ident = '';
} else {
$ident = ' i=' . $this->DKIM_identity . ';';
}
$dkimhdrs = 'DKIM-Signature: v=1; a=' .
$DKIMsignatureType . '; q=' .
$DKIMquery . '; l=' .
$DKIMlen . '; s=' .
$this->DKIM_selector .
";\r\n" .
"\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
"\th=From:To:Date:Subject" . $extraHeaderKeys . ";\r\n" .
"\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
$copiedHeaderFields .
"\tbh=" . $DKIMb64 . ";\r\n" .
"\tb=";
$toSign = $this->DKIM_HeaderC(
$from_header . "\r\n" .
$to_header . "\r\n" .
$date_header . "\r\n" .
$subject_header . "\r\n" .
$extraHeaderValues .
$dkimhdrs
);
$signed = $this->DKIM_Sign($toSign);
return static::normalizeBreaks($dkimhdrs . $signed) . static::$LE;
}
/**
* Detect if a string contains a line longer than the maximum line length
* allowed by RFC 2822 section 2.1.1.
*
* @param string $str
*
* @return bool
*/
public static function hasLineLongerThanMax($str)
{
return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
}
/**
* Allows for public read access to 'to' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
*
* @return array
*/
public function getToAddresses()
{
return $this->to;
}
/**
* Allows for public read access to 'cc' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
*
* @return array
*/
public function getCcAddresses()
{
return $this->cc;
}
/**
* Allows for public read access to 'bcc' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
*
* @return array
*/
public function getBccAddresses()
{
return $this->bcc;
}
/**
* Allows for public read access to 'ReplyTo' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
*
* @return array
*/
public function getReplyToAddresses()
{
return $this->ReplyTo;
}
/**
* Allows for public read access to 'all_recipients' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.
*
* @return array
*/
public function getAllRecipientAddresses()
{
return $this->all_recipients;
}
/**
* Perform a callback.
*
* @param bool $isSent
* @param array $to
* @param array $cc
* @param array $bcc
* @param string $subject
* @param string $body
* @param string $from
* @param array $extra
*/
protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
{
if (!empty($this->action_function) and is_callable($this->action_function)) {
call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
}
}
/**
* Get the OAuth instance.
*
* @return OAuth
*/
public function getOAuth()
{
return $this->oauth;
}
/**
* Set an OAuth instance.
*
* @param OAuth $oauth
*/
public function setOAuth(OAuth $oauth)
{
$this->oauth = $oauth;
}
}
```
===== FILE: common/extensions/CDEK.php =====
```
Class CDEK {
public $token = NULL;
public $curl = NULL;
function __construct($type = NULL) {
$this->curl = new Curl();
$params = array(
"grant_type" => "client_credentials",
"client_id" => "uRpWyq46AogPuMT8114gbroKVABhqEUS",
"client_secret" => "ufnr9up2TQgNRVDvsVRLK5cJ99R45UOj"
);
$result = $this->curl->request("https://api.cdek.ru/v2/oauth/token", $params);
$json = json_decode($result);
$this->token = $json->access_token;
}
function __destruct() {
}
public function getTrackNumber($id){
$result = $this->curl->request("https://api.cdek.ru/v2/orders?im_number=".$id, false, array( "Authorization: Bearer ".$this->token ));
$json = json_decode($result);
$request = $json->requests[0];
if( isset($json->entity) ){
return $json->entity->cdek_number;
}else{
foreach ($request->errors as $key => $error) {
echo $error->message."".$key.": ".$value."
".$value."
";
// var_dump($user);
// }
return $user;
}
}
```
===== FILE: common/components/services/UpdateDeduplicator.php =====
```
updateId)) {
return true;
}
$file = Yii::app()->runtimePath . DIRECTORY_SEPARATOR . 'tg_update_ids.log';
// гарантируем, что runtime существует
if (!is_dir(Yii::app()->runtimePath)) {
@mkdir(Yii::app()->runtimePath, 0777, true);
}
$fp = @fopen($file, 'c+');
if (!$fp) {
// если не можем дедупить — лучше пропустить апдейт, чем убить бота
return true;
}
try {
if (!flock($fp, LOCK_EX)) {
return true;
}
// читаем текущие
$lines = [];
rewind($fp);
while (($line = fgets($fp)) !== false) {
$line = trim($line);
if ($line !== '') $lines[] = $line;
}
$idStr = (string)$dto->updateId;
if (in_array($idStr, $lines, true)) {
return false;
}
// добавляем
$lines[] = $idStr;
// трим
if (count($lines) > $this->maxLines) {
$lines = array_slice($lines, -$this->maxLines);
}
// перезаписываем файл
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, implode("\n", $lines) . "\n");
return true;
} finally {
@flock($fp, LOCK_UN);
@fclose($fp);
}
}
}
```
===== FILE: common/components/services/TryOnPendingRuntime.php =====
```
runtimePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'tryon_pending';
if (!is_dir($dir)) @mkdir($dir, 0777, true);
return $dir;
}
public static function save(int $telegramId, array $data, int $ttlSeconds = 3600): void
{
$path = self::dirPath() . DIRECTORY_SEPARATOR . 'pending_' . $telegramId . '.json';
@file_put_contents($path, json_encode([
'exp' => time() + $ttlSeconds,
'data' => $data,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
public static function load(int $telegramId): ?array
{
$path = self::dirPath() . DIRECTORY_SEPARATOR . 'pending_' . $telegramId . '.json';
if (!is_file($path)) return null;
$raw = @file_get_contents($path);
$j = $raw ? json_decode($raw, true) : null;
if (!is_array($j)) return null;
if (!empty($j['exp']) && time() > (int)$j['exp']) {
@unlink($path);
return null;
}
return is_array($j['data'] ?? null) ? $j['data'] : null;
}
public static function clear(int $telegramId): void
{
$path = self::dirPath() . DIRECTORY_SEPARATOR . 'pending_' . $telegramId . '.json';
@unlink($path);
}
}
```
===== FILE: common/components/services/SubscriptionsService.php =====
```
find(
'user_id=:u AND plan_version_id=:p AND (status IS NULL OR status=:st) AND cp_subscription_id IS NULL AND created_at >= DATE_SUB(NOW(), INTERVAL 30 MINUTE)',
[':u' => $userId, ':p' => $planVersionId, ':st' => 'Pending']
);
if ($draft) {
return $draft;
}
$sub = new UserSubscription();
$sub->user_id = $userId;
$sub->plan_version_id = $planVersionId;
$sub->status = 'Pending';
$sub->created_at = date('Y-m-d H:i:s');
$sub->updated_at = date('Y-m-d H:i:s');
if (!$sub->save(false)) {
throw new RuntimeException('Cannot create subscription draft');
}
return $sub;
}
public static function handlePay(array $payload, string $rawBody): void
{
$meta = self::decodeMeta($payload);
$subscription = self::resolveSubscription($payload, $meta);
if (!$subscription) {
Yii::log('SubscriptionsService: Pay: cannot resolve subscription. payload=' . $rawBody, CLogger::LEVEL_ERROR);
return;
}
$planVersion = PlanVersion::model()->with('plan')->findByPk((int)$subscription->plan_version_id);
if (!$planVersion) {
Yii::log('SubscriptionsService: Pay: planVersion not found for subscription_id=' . (int)$subscription->id, CLogger::LEVEL_ERROR);
return;
}
$txId = isset($payload['TransactionId']) ? (int)$payload['TransactionId'] : null;
$amount = isset($payload['Amount']) ? (float)$payload['Amount'] : (float)$planVersion->price;
$currency = isset($payload['Currency']) ? (string)$payload['Currency'] : (string)$planVersion->currency;
// Идемпотентность по транзакции
if ($txId) {
$exists = SubscriptionCharge::model()->find(
'transaction_id=:t AND subscription_id=:s',
[':t' => (string)$txId, ':s' => (int)$subscription->id]
);
if ($exists) {
return;
}
}
$now = date('Y-m-d H:i:s');
// Обновляем подписку
$cpSubId = isset($payload['SubscriptionId']) ? trim((string)$payload['SubscriptionId']) : null;
if ($cpSubId !== null && $cpSubId !== '') {
$subscription->cp_subscription_id = $cpSubId;
}
try {
$cp = new CloudPaymentsApi();
$next = $cp->getNextChargeAtDb($subscription->cp_subscription_id);
if ($next) {
$subscription->next_charge_at = $next;
}
} catch (Exception $e) {
Yii::log('Cannot fetch next_charge_at from CP: ' . $e->getMessage(), CLogger::LEVEL_WARNING);
}
$wasActiveBefore = ($subscription->status === 'Active');
$subscription->status = 'Active';
if (empty($subscription->started_at)) {
$subscription->started_at = self::payloadDateTime($payload) ?: $now;
}
$subscription->last_paid_at = self::payloadDateTime($payload) ?: $now;
if (!empty($payload['CardLastFour'])) $subscription->card_last4 = (string)$payload['CardLastFour'];
if (!empty($payload['CardType'])) $subscription->card_type = (string)$payload['CardType'];
$subscription->updated_at = $now;
$subscription->save(false);
// Записываем списание
$charge = new SubscriptionCharge();
$charge->subscription_id = (int)$subscription->id;
$charge->kind = $wasActiveBefore ? 'payment' : 'initial';
$charge->amount = $amount;
$charge->currency = $currency;
$charge->status = 'Paid';
$charge->transaction_id = $txId ? (string)$txId : null;
$charge->reason = null;
// !!! ВАЖНО: raw_json — колонка MySQL JSON. Сохраняем валидный JSON.
$charge->raw_json = self::packRawJson($payload, $rawBody);
$charge->created_at = $now;
$charge->save(false);
// Начисляем кредиты
$credits = (int)$planVersion->credits_per_month;
if ($credits > 0) {
CreditsService::add(
(int)$subscription->user_id,
$credits,
'subscriptionCredits',
(int)$subscription->id,
null,
$txId,
'Subscription: ' . (string)$planVersion->plan->title
);
}
if ($user = User::model()->findByPk($subscription->user_id)) {
$messageGenerator = new MessageGenerator($user);
Yii::app()->controller->sendMessageToTelegram(
$user,
(count($subscription->charges) > 1)?
$messageGenerator->subscriptionRenewalMessage($credits):
$messageGenerator->subscriptionSuccessMessage($credits)
);
}
}
public static function handleFail(array $payload, string $rawBody): void
{
$meta = self::decodeMeta($payload);
$subscription = self::resolveSubscription($payload, $meta);
if (!$subscription) {
Yii::log('SubscriptionsService: Fail: cannot resolve subscription. payload=' . $rawBody, CLogger::LEVEL_WARNING);
return;
}
$txId = isset($payload['TransactionId']) ? (int)$payload['TransactionId'] : null;
if ($txId) {
$exists = SubscriptionCharge::model()->find(
'transaction_id=:t AND subscription_id=:s',
[':t' => (string)$txId, ':s' => (int)$subscription->id]
);
if ($exists) return;
}
$now = date('Y-m-d H:i:s');
$charge = new SubscriptionCharge();
$charge->subscription_id = (int)$subscription->id;
$charge->kind = 'payment';
$charge->amount = isset($payload['Amount']) ? (float)$payload['Amount'] : null;
$charge->currency = isset($payload['Currency']) ? (string)$payload['Currency'] : null;
$charge->status = 'Fail';
$charge->transaction_id = $txId ? (string)$txId : null;
$charge->reason = self::failReason($payload);
// !!! ВАЖНО: raw_json — JSON
$charge->raw_json = self::packRawJson($payload, $rawBody);
$charge->created_at = $now;
$charge->save(false);
}
public static function handleRecurrent(array $payload, string $rawBody): void
{
$cpSubId = isset($payload['Id']) ? trim((string)$payload['Id']) : null;
if (!$cpSubId) {
Yii::log('SubscriptionsService: Recurrent: missing Id. payload=' . $rawBody, CLogger::LEVEL_WARNING);
return;
}
$subscription = UserSubscription::model()->find('cp_subscription_id=:id', [':id' => $cpSubId]);
if (!$subscription && !empty($payload['AccountId'])) {
$userId = (int)$payload['AccountId'];
$subscription = UserSubscription::model()->find(
'user_id=:u AND cp_subscription_id IS NULL AND (status IS NULL OR status=:st) ORDER BY id DESC',
[':u' => $userId, ':st' => 'Pending']
);
if ($subscription) {
$subscription->cp_subscription_id = $cpSubId;
}
}
if (!$subscription) {
Yii::log('SubscriptionsService: Recurrent: subscription not found for cpId=' . $cpSubId, CLogger::LEVEL_WARNING);
return;
}
$now = date('Y-m-d H:i:s');
$status = isset($payload['Status']) ? (string)$payload['Status'] : null;
if ($status) {
$subscription->status = $status;
}
if (!empty($payload['LastTransactionDate'])) {
$subscription->last_paid_at = self::payloadDateTime($payload, 'LastTransactionDate');
}
if (!empty($payload['NextTransactionDate'])) {
$subscription->next_charge_at = self::payloadDateTime($payload, 'NextTransactionDate');
}
if (in_array($status, ['Cancelled', 'Rejected', 'Expired'], true)) {
if (empty($subscription->canceled_at)) {
$subscription->canceled_at = $now;
}
if ($subscription->user) {
$messageGenerator = new MessageGenerator($subscription->user);
Yii::app()->controller->sendMessageToTelegram(
$subscription->user,
$messageGenerator->subscriptionCanceledMessage()
);
}
}
$subscription->updated_at = $now;
$subscription->save(false);
}
public static function cancelUserSubscription(int $userId, int $subscriptionId, string $reason = 'user'): bool
{
/** @var UserSubscription $sub */
$sub = UserSubscription::model()->findByPk($subscriptionId);
if (!$sub || (int)$sub->user_id !== $userId) {
throw new CHttpException(404, 'Subscription not found');
}
// Если уже отменена/закрыта — идемпотентно
if (in_array($sub->status, ['Cancelled', 'Expired', 'Rejected'], true)) {
return true;
}
if (empty($sub->cp_subscription_id)) {
// нет CP id — нечего отменять на стороне CP
// но в БД можем пометить как Cancelled, чтобы не считалась Active
$sub->status = 'Cancelled';
$sub->canceled_at = date('Y-m-d H:i:s');
$sub->updated_at = date('Y-m-d H:i:s');
$sub->save(false);
return true;
}
// 1) Отменяем в CloudPayments
$cp = new CloudPaymentsApi();
$cp->cancelSubscription($sub->cp_subscription_id);
// 2) В БД ставим Cancelled, но доступ "держим" до next_charge_at (если он есть и в будущем)
$now = date('Y-m-d H:i:s');
$sub->status = 'Cancelled';
if (empty($sub->canceled_at)) {
$sub->canceled_at = $now;
}
$sub->updated_at = $now;
$sub->save(false);
// 3) (опционально) логируем в subscription_charge как событие, если есть подходящая модель/тип
// Если у тебя raw_json в JSON-колонке — кладём валидный JSON
try {
$charge = new SubscriptionCharge();
$charge->subscription_id = (int)$sub->id;
$charge->kind = 'cancel';
$charge->amount = 0;
$charge->currency = null;
$charge->status = 'Ok';
$charge->transaction_id = null;
$charge->reason = $reason;
$charge->raw_json = json_encode([
'event' => 'cancel',
'reason' => $reason,
'cp_subscription_id' => $sub->cp_subscription_id,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}';
$charge->created_at = $now;
$charge->save(false);
} catch (Exception $e) {
// не валим отмену из-за логов
Yii::log('cancelUserSubscription: cannot write SubscriptionCharge: ' . $e->getMessage(), CLogger::LEVEL_WARNING);
}
return true;
}
// ---------------- helpers ----------------
private static function decodeMeta(array $payload): array
{
if (empty($payload['Data'])) return [];
if (is_array($payload['Data'])) return $payload['Data'];
if (is_string($payload['Data'])) {
$meta = @json_decode($payload['Data'], true);
return is_array($meta) ? $meta : [];
}
return [];
}
private static function resolveSubscription(array $payload, array $meta): ?UserSubscription
{
if (!empty($meta['subscription_id'])) {
return UserSubscription::model()->findByPk((int)$meta['subscription_id']);
}
if (!empty($payload['SubscriptionId'])) {
$cpId = trim((string)$payload['SubscriptionId']);
if ($cpId !== '') {
return UserSubscription::model()->find('cp_subscription_id=:id', [':id' => $cpId]);
}
}
// Иногда удобно (если invoiceId = user_subscription.id) — можно добавить fallback:
// if (!empty($payload['InvoiceId'])) return UserSubscription::model()->findByPk((int)$payload['InvoiceId']);
return null;
}
private static function payloadDateTime(array $payload, string $key = 'DateTime'): ?string
{
return empty($payload[$key]) ? null : (string)$payload[$key];
}
private static function failReason(array $payload): string
{
$reason = isset($payload['Reason']) ? (string)$payload['Reason'] : '';
$code = isset($payload['ReasonCode']) ? (string)$payload['ReasonCode'] : '';
$parts = array_filter([$reason, $code]);
return implode(' | ', $parts);
}
/**
* raw_json колонка = MySQL JSON => сюда НЕЛЬЗЯ писать querystring напрямую.
* Всегда возвращаем валидный JSON: объект {payload, rawBody}
*/
private static function packRawJson(array $payload, string $rawBody): string
{
$packed = [
'payload' => $payload,
'rawBody' => $rawBody,
];
$json = json_encode($packed, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// Если вдруг json_encode упал (редко, но бывает из-за бинарных символов) — кладём хотя бы payload
if ($json === false) {
$json = json_encode(['payload' => $payload], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
// Если и это не удалось — возвращаем пустой валидный JSON
return $json !== false ? $json : '{}';
}
}
```
===== FILE: common/controllers/PublicController.php =====
```
array("index", "privacyPolicy", "termsOfUse", "sessions"),
"users" => array("*"),
),
array("deny",
"users" => array("*"),
),
);
}
public function actionSessions($user_id = null){
if( $user_id ){
$user = User::model()->findByPk($user_id);
}
// Админы
if( isset($user) && $user && in_array($user->id, [1, 6]) ){
$rubrics = Rubric::model()->sorted()->with([
"prompts" => [
"order" => "prompts.counter DESC",
"condition" => "status_id = '".Prompt::STATUS_PUBLISHED."' OR status_id = '".Prompt::STATUS_TESTING."'"
]
])->findAll();
}else{
$rubrics = Rubric::model()->sorted()->with([
"prompts" => [
"order" => "t.id DESC",
"condition" => "status_id = '".Prompt::STATUS_PUBLISHED."'"
]
])->findAll();
}
// var_dump($sessions);
$params = [
"rubrics" => $rubrics
];
// $params = array(
// "data" => $dataProvider->getData(),
// "pages" => $pages,
// "filter" => $filter,
// "count" => $count,
// "labels" => $className::model()->attributeLabels(true),
// "modelName" => $this->adminMenu["items"][ $_GET["class"] ] ?? $this->adminMenu["items"][ mb_strtolower($_GET["class"], "UTF-8") ]
// );
$this->renderFile(Yii::getPathOfAlias('application.views.public.sessions') . '.php', $params);
}
public function actionIndex()
{
$this->render("index");
}
public function actionPrivacyPolicy()
{
$this->render("privacy-policy");
}
public function actionTermsOfUse()
{
$this->render("terms-of-use");
}
}
```
===== FILE: common/controllers/AdminController.php =====
```
array("index"),
"roles" => array("readAdmin"),
),
array("allow",
"actions" => array("create", "update", "delete"),
"roles" => array("updateAdmin"),
),
array("deny",
"users" => array("*"),
),
);
}
public function actionCreate()
{
$model=new Admin;
if(isset($_POST["Admin"]))
{
$model->attributes=$_POST["Admin"];
if($model->save()){
if( isset($_POST["Roles"]) ){
foreach ($_POST["Roles"] as $key => $roleId) {
$role = new AdminRole();
$role->admin_id = $model->id;
$role->role_id = $roleId;
$role->save();
}
}
$this->actionIndex(true);
return true;
}
}
$roleList = Role::model()->findAll();
$this->renderPartial("create",array(
"model" => $model,
"roleList" => $roleList
));
}
public function actionUpdate($id)
{
$model = $this->loadModel($id);
if(isset($_POST["Admin"]))
{
$model->prevPass = $model->password;
$model->attributes = $_POST["Admin"];
AdminRole::model()->deleteAll("admin_id=".$model->id);
if( isset($_POST["Roles"]) ){
foreach ($_POST["Roles"] as $key => $roleId) {
$role = new AdminRole();
$role->admin_id = $model->id;
$role->role_id = $roleId;
$role->save();
}
}
if($model->save()){
$this->actionIndex(true);
}
}else{
$roles = array();
foreach ($model->roles as $key => $role) {
array_push($roles, $role->role_id);
}
$roleList = Role::model()->findAll();
$this->renderPartial("update",array(
"model" => $model,
"roles" => $roles,
"roleList" => $roleList
));
}
}
public function actionDelete($id)
{
$this->loadModel($id)->delete();
$this->actionIndex(true);
}
public function actionIndex($partial = false)
{
if( !$partial ){
$this->layout="admin";
$this->pageTitle = $this->adminMenu["cur"]->name;
}
$filter = new Admin('filter');
if (isset($_GET['Admin'])){
$filter->attributes = $_GET['Admin'];
}
Controller::accessFilter($filter);
$dataProvider = $filter->search(50);
$count = $filter->search(50, true);
$params = array(
"data" => $dataProvider->getData(),
"pages" => $dataProvider->getPagination(),
"filter" => $filter,
"count" => $count,
"labels" => Admin::model()->attributeLabels(),
);
if( !$partial ){
$this->render("index".(($this->isMobile)?"Mobile":""), $params);
}else{
$this->renderPartial("index".(($this->isMobile)?"Mobile":""), $params);
}
}
/**
* Returns the data model based on the primary key given in the GET variable.
* If the data model is not found, an HTTP exception will be raised.
* @param integer $id the ID of the model to be loaded
* @return Admin the loaded model
* @throws CHttpException
*/
public function loadModel($id)
{
$model=Admin::model()->with("roles.role")->findByPk($id);
if($model===null)
throw new CHttpException(404, "The requested page does not exist.");
return $model;
}
/**
* Performs the AJAX validation.
* @param Admin $model the model to be validated
*/
protected function performAjaxValidation($model)
{
if(isset($_POST["ajax"]) && $_POST["ajax"] === "admin-form")
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
}
}
```
===== FILE: common/controllers/StatsController.php =====
```
array("index", "months", "month"),
"roles" => array("readAdmin"),
),
array("deny",
"users" => array("*"),
),
);
}
/**
* Главная: дневная статистика за текущий месяц + “плашка текущего месяца”
* Плашка ведёт в /stats/months
*/
public function actionIndex()
{
$service = new StatsService();
$ym = date('Y-m');
$from = $ym . '-01';
$to = date('Y-m-d'); // по сегодня
$kie = new Kie($this->getParam("KEYS", "KIE"));
$summary = $service->getMonthSummary((int)date('Y'), (int)date('m'));
$daily = $service->getDailyStats($from, $to);
$balanceKie = $kie->getBalance();
$this->render("index", array(
"ym" => $ym,
"summary" => $summary,
"daily" => $daily,
"balanceKie" => ($balanceKie["success"]) ? $this->number_format($balanceKie["credits"]) : "Не удалось получить",
"curDate" => strtotime(date('d.m.Y')),
));
}
/**
* Раздел “По месяцам” (список последних N месяцев)
*/
public function actionMonths()
{
$service = new StatsService();
$months = $service->getMonthlyStats(24);
$this->render("months", array(
"months" => $months,
));
}
/**
* Деталка по конкретному месяцу (дни + сводка)
* /stats/month?ym=2025-12
*/
public function actionMonth($ym = null)
{
$ym = $ym ?: date('Y-m');
if (!preg_match('~^\d{4}-\d{2}$~', $ym)) {
throw new CHttpException(400, "Invalid ym");
}
[$y, $m] = array_map('intval', explode('-', $ym));
$service = new StatsService();
$summary = $service->getMonthSummary($y, $m);
$from = $ym . '-01';
$to = date('Y-m-t', strtotime($from));
$daily = $service->getDailyStats($from, $to);
$this->render("month", array(
"ym" => $ym,
"summary" => $summary,
"daily" => $daily,
));
}
}
```
===== FILE: common/controllers/UploaderController.php =====
```
array('index','upload','getForm'),
'users' => array('*')
)
);
}
/*! Всплывающее окно с формой для загрузки изображений.\n
Плагин plupload. */
public function actionGetForm() {
$this->renderPartial('form');
}
/*! Страница, на которую плагин отправляет изображения. */
public function actionUpload() {
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
// 5 minutes execution time
@set_time_limit(5 * 60);
// Uncomment this one to fake upload time
// usleep(5000);
// Settings
$targetDir = Yii::app()->params['tempFolder'];
//$targetDir = 'uploads';
$cleanupTargetDir = true; // Remove old files
$maxFileAge = 5*3600; // Temp file age in seconds
// Create target dir
if (!file_exists($targetDir)) {
@mkdir($targetDir);
}
// Get a file name
if (isset($_REQUEST["name"])) {
$fileName = $_REQUEST["name"];
} elseif (!empty($_FILES)) {
$fileName = $_FILES["file"]["name"];
} else {
$fileName = uniqid("file_");
}
$filePath = $targetDir . "/" . $fileName;
// Chunking might be enabled
$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
// Remove old temp files
if ($cleanupTargetDir) {
if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
}
while (($file = readdir($dir)) !== false) {
$tmpfilePath = $targetDir . "/" . $file;
// If temp file is current file proceed to the next
if ($tmpfilePath == "{$filePath}.part") {
continue;
}
// Remove temp file if it is older than the max age and is not the current file
if (!preg_match('/\.part$/', $file) && (filemtime($tmpfilePath) < time() - $maxFileAge)) {
@unlink($tmpfilePath);
}
}
closedir($dir);
}
// Open temp file
if (!$out = @fopen("{$filePath}.part", $chunks ? "ab" : "wb")) {
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
}
if (!empty($_FILES)) {
if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) {
die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
}
// Read binary input stream and append it to temp file
if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
}
} else {
if (!$in = @fopen("php://input", "rb")) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
}
}
while ($buff = fread($in, 4096)) {
fwrite($out, $buff);
}
@fclose($out);
@fclose($in);
// Check if file has been uploaded
if (!$chunks || $chunk == $chunks - 1) {
// Strip the temp .part suffix off
rename("{$filePath}.part", $filePath);
}
// Return Success JSON-RPC response
die('{"jsonrpc" : "2.0", "result" : "success", "id" : "id"}');
}
}
```
===== FILE: common/controllers/CloudPaymentsController.php =====
```
array("*"),
),
);
}
// 1) Страница оплаты: /cloudpayments/pay?orderId=123
public function actionPay()
{
$this->layout = "public";
$userId = (int)Yii::app()->request->getParam('userId');
$packageId = (int)Yii::app()->request->getParam('packageId');
if( !$package = Package::model()->findByPk($packageId) ){
throw new CHttpException(404, 'Пакет не найден');
}
if( !$user = User::model()->findByPk($userId) ){
throw new CHttpException(404, 'Пакет не найден');
}
if( isset($user->orders) && is_array($user->orders) && count($user->orders) ){
$order = $user->orders[0];
if( (int) $order->status_id !== Order::STATUS_PENDING ){
$order = new Order();
}else{
$order->created_at = date('Y-m-d H:i:s');
}
}else{
$order = new Order();
}
if( $userId == 1 ){
$package->price = 1;
}
$order->user_id = $userId;
$order->package_id = $packageId;
$order->status_id = Order::STATUS_PENDING;
$order->sum = $package->price;
$order->save();
if (!$order || (int) $order->status_id !== Order::STATUS_PENDING) {
throw new CHttpException(404, 'Не удалось создать заказ');
}
// Data — любые дополнительные данные, попадут в webhook в поле Data
$data = [
'order_id' => $order->id
];
$this->render('pay', [
'publicId' => Yii::app()->params['cloudpayments']['publicId'],
'amount' => $order->sum,
'currency' => "RUB",
'description'=> $order->getTitle(),
'dataJson' => CJSON::encode($data),
'orderId' => $order->id
]);
}
public function actionSubscribe()
{
$this->layout = "public";
$userId = (int)Yii::app()->request->getParam('userId');
$planVersionId = (int)Yii::app()->request->getParam('planVersionId');
$paymentType = (string)Yii::app()->request->getParam('paymentType', 'card');
if (!$user = User::model()->findByPk($userId)) {
throw new CHttpException(404, 'Пользователь не найден');
}
$planVersion = PlanVersion::model()->with('plan')->findByPk($planVersionId);
if (!$planVersion) {
throw new CHttpException(404, 'Тариф не найден');
}
// Если уже есть активная подписка — можно не пускать (по желанию)
$active = UserSubscription::model()->find(
'user_id=:u AND status=:st AND canceled_at IS NULL',
[':u' => $userId, ':st' => 'Active']
);
if ($active) {
// минимально: просто покажем страницу/сообщение (можно сделать отдельный view)
header('Content-Type: text/plain; charset=utf-8');
echo "У вас уже есть активная подписка.";
Yii::app()->end();
}
// создаём draft подписки у нас
$sub = SubscriptionsService::createDraft($userId, $planVersionId);
// Важно: для подписок CloudPayments нужен accountId :contentReference[oaicite:5]{index=5}
// Важно: recurrent задается в data.cloudPayments.recurrent :contentReference[oaicite:6]{index=6}
$data = [
'kind' => 'subscription',
'subscription_id' => (int)$sub->id,
'plan_version_id' => (int)$planVersionId,
'user_id' => (int)$userId,
'cloudPayments' => [
'recurrent' => [
'interval' => 'Month',
'period' => 1,
],
],
];
$description = 'Подписка: ' . (string)$planVersion->plan->title;
if( $userId == 1 ){
$planVersion->price = 1;
}
$this->render('subscribe', [
'publicId' => Yii::app()->params['cloudpayments']['publicId'],
'amount' => (float)$planVersion->price,
'currency' => !empty($planVersion->currency) ? (string)$planVersion->currency : 'RUB',
'description' => $description,
'dataJson' => CJSON::encode($data),
'invoiceId' => (int)$sub->id,
'accountId' => (int)$userId,
]);
}
// 2) Webhook: /pay/cloudpayments/confirm
public function actionConfirm()
{
// 1. Сырое тело запроса (нужно и для логов, и для проверки подписи)
$requestBody = file_get_contents('php://input');
file_put_contents("CloudPayments.txt", $requestBody . "\n", FILE_APPEND);
// // 2. Проверяем подпись Content-HMAC по сырому body
// $headers = function_exists('getallheaders') ? getallheaders() : [];
// $signature = null;
// // на некоторых серверах ключ может приходить в другом регистре
// foreach ($headers as $key => $value) {
// if (strtolower($key) === 'content-hmac') {
// $signature = $value;
// break;
// }
// }
// $secret = Yii::app()->params['cloudpayments']['apiSecret'];
// $calculatedSign = base64_encode(hash_hmac('sha256', $requestBody, $secret, true));
// if (!$signature || $signature !== $calculatedSign) {
// // Неверная подпись
// $this->sendJson(['code' => 13]); // неверная подпись
// Yii::log('CloudPayments invalid signature: ' . $requestBody, CLogger::LEVEL_ERROR);
// Yii::app()->end();
// }
// 3. Разбираем тело в массив (формат application/x-www-form-urlencoded)
$data = [];
parse_str($requestBody, $data);
// Альтернатива: $data = $_POST; но для надёжности парсим именно то, по чему считали HMAC
// Если это подписка — у нас в Data лежит kind/subscription_id
$meta = [];
if (!empty($data['Data'])) {
$meta = is_string($data['Data']) ? @json_decode($data['Data'], true) : (is_array($data['Data']) ? $data['Data'] : []);
if (!is_array($meta)) $meta = [];
}
if (!empty($meta['kind']) && $meta['kind'] === 'subscription' || !empty($meta['subscription_id']) || !empty($data['SubscriptionId'])) {
SubscriptionsService::handlePay($data, $requestBody);
$this->sendJson(['code' => 0]);
Yii::app()->end();
}
// 4. Обработка только успешного платежа
if (empty($data['Status']) || $data['Status'] !== 'Completed') {
$this->sendJson(['code' => 0]); // просто принимаем
Yii::app()->end();
}
// 5. Достаём orderId — сначала из InvoiceId
$orderId = !empty($data['InvoiceId']) ? (int)$data['InvoiceId'] : null;
// Если не передали InvoiceId — пробуем вытащить из Data (внутри — JSON)
if (!$orderId && !empty($data['Data'])) {
$meta = @json_decode($data['Data'], true);
if (is_array($meta) && !empty($meta['order_id'])) {
$orderId = (int)$meta['order_id'];
}
}
if (!$orderId) {
Yii::log('CloudPayments: no order id in webhook: ' . $requestBody, CLogger::LEVEL_ERROR);
$this->sendJson(['code' => 0]);
Yii::app()->end();
}
// 6. Ищем заказ
$order = Order::model()->findByPk($orderId);
if (!$order) {
Yii::log('CloudPayments: order not found: ' . $orderId, CLogger::LEVEL_ERROR);
$this->sendJson(['code' => 0]);
Yii::app()->end();
}
if ((int)$order->status_id === Order::STATUS_PAID) {
// Уже отмечен как оплачен
$this->sendJson(['code' => 0]);
Yii::app()->end();
}
// 7. Проверяем сумму/валюту (при необходимости)
// В actionPay ты используешь $order->sum и жёстко "RUB",
// поэтому сверяем именно с ними.
if (isset($data['Amount']) && isset($data['Currency'])) {
if ((float)$order->sum != (float)$data['Amount'] || $data['Currency'] !== 'RUB') {
Yii::log('CloudPayments: amount/currency mismatch for order ' . $orderId, CLogger::LEVEL_ERROR);
// на твой вкус: либо просто логируем, либо можно не засчитывать оплату
}
}
// 8. Помечаем заказ как оплаченный
$order->status_id = Order::STATUS_PAID;
$order->paid_at = date('Y-m-d H:i:s');
$order->save(false);
$logText = "Оплата {$data['Amount']} руб.\n";
// 9. Начисляем генерации пользователю, если у заказа есть пакет
if (isset($order->package) && !empty($order->package)) {
$logText .= $order->package->getTitle()."\n";
// $user->increaseBalance($order->package->amount);
CreditsService::add((int)$order->user_id, $order->package->amount, 'buyCredits');
// $user->setPaid();
}
$user = User::model()->findByPk($order->user_id);
$logText .= "Пользователь {$user->name} ({$user->username})\n\nИтого за сегодня: ".$order->getTotalToday()." руб.";
$this->logToTelegram($logText, Yii::app()->params["paymentLogsChatId"]);
// 10. Уведомляем пользователя в Telegram
if ($user) {
$messageGenerator = new MessageGenerator($user);
$this->sendMessageToTelegram(
$user,
$messageGenerator->paymentSuccessMessage($order->package->amount)
);
}
// 11. Ответ CloudPayments
$this->sendJson(['code' => 0]); // CloudPayments ожидает {"code":0} при успехе
Yii::app()->end();
}
public function actionSbp()
{
$this->layout = "public";
$userId = (int)Yii::app()->request->getParam('userId');
$packageId = (int)Yii::app()->request->getParam('packageId');
if (!$package = Package::model()->findByPk($packageId)) {
throw new CHttpException(404, 'Пакет не найден');
}
if (!$user = User::model()->findByPk($userId)) {
throw new CHttpException(404, 'Пользователь не найден');
}
// 1) Создаем/переиспользуем pending-заказ (логика как в actionPay, но без "..." и магии)
$order = Order::model()->find(
'user_id = :uid AND package_id = :pid AND status_id = :st',
[':uid' => $userId, ':pid' => $packageId, ':st' => Order::STATUS_PENDING]
);
if (!$order) {
$order = new Order();
$order->user_id = $userId;
$order->package_id = $packageId;
$order->status_id = Order::STATUS_PENDING;
}
$order->sum = $package->price;
if (empty($order->created_at)) {
$order->created_at = date('Y-m-d H:i:s');
}
if (!$order->save()) {
throw new CHttpException(500, 'Не удалось создать заказ');
}
// 2) Дергаем CloudPayments SBP link API и редиректим на QrUrl
try {
$client = new CloudPaymentsApi();
$payload = [
'PublicId' => Yii::app()->params['cloudpayments']['publicId'],
'Amount' => (float)$order->sum,
'Currency' => 'RUB',
'Description' => $order->getTitle(),
'AccountId' => (string)$user->id,
'InvoiceId' => (string)$order->id,
'Scheme' => 'charge',
// чтобы в вебхуках можно было восстановить контекст
'JsonData' => [
'order_id' => (int)$order->id,
'user_id' => (int)$user->id,
'package_id' => (int)$package->id,
],
// возвращаем пользователя обратно после оплаты/ошибки
'SuccessRedirectUrl' => $this->createAbsoluteUrl('cloudPayments/success', ['orderId' => (int)$order->id]),
'FailRedirectUrl' => $this->createAbsoluteUrl('cloudPayments/fail', ['orderId' => (int)$order->id]),
];
$qrUrl = $client->createSbpLink($payload);
// ВАЖНО: редиректим пользователя прямо в SBP flow
$this->redirect($qrUrl);
Yii::app()->end();
} catch (Throwable $e) {
$order->status_id = Order::STATUS_FAILED;
$order->last_error = $e->getMessage();
$order->save(false);
// фоллбек: покажем простую страницу, чтобы не было "белого экрана"
header('Content-Type: text/plain; charset=utf-8');
echo "SBP payment init failed: " . $e->getMessage();
Yii::app()->end();
}
}
public function actionRecurrent()
{
$requestBody = file_get_contents('php://input');
// 1) Подпись (Content-HMAC) — аналогично actionConfirm
$headers = function_exists('getallheaders') ? getallheaders() : [];
$signature = null;
foreach ($headers as $k => $v) {
if (strtolower($k) === 'content-hmac') {
$signature = $v;
break;
}
}
$secret = Yii::app()->params['cloudpayments']['apiSecret'];
$computed = base64_encode(hash_hmac('sha256', $requestBody, $secret, true));
if (!hash_equals($computed, $signature)) {
Yii::log('CloudPayments Recurrent: invalid signature. body=' . $requestBody, CLogger::LEVEL_ERROR);
$this->sendJson(['code' => 13]);
Yii::app()->end();
}
// 2) Парсим payload (CP шлёт querystring)
$data = [];
parse_str($requestBody, $data);
// fallback: если CP прислал JSON (редко)
if (empty($data)) {
$json = @json_decode($requestBody, true);
if (is_array($json)) {
$data = $json;
}
}
if (empty($data)) {
Yii::log('CloudPayments Recurrent: empty payload. body=' . $requestBody, CLogger::LEVEL_WARNING);
$this->sendJson(['code' => 0]);
Yii::app()->end();
}
// 3) Обрабатываем (Status/LastTransactionDate/NextTransactionDate)
SubscriptionsService::handleRecurrent($data, $requestBody);
$this->sendJson(['code' => 0]);
Yii::app()->end();
}
public function actionFail()
{
$requestBody = file_get_contents('php://input');
$data = @json_decode($requestBody, true);
// fallback если пришло не JSON
if (!is_array($data)) {
$data = Yii::app()->request->getIsPostRequest() ? $_POST : $_GET;
}
// (опционально) проверка подписи как у actionConfirm
// Сейчас у вас подпись проверяется только в actionConfirm — можно вынести в helper,
// но минимально оставим как есть (не блокируем поток).
$meta = [];
if (!empty($data['Data'])) {
$meta = is_string($data['Data']) ? @json_decode($data['Data'], true) : (is_array($data['Data']) ? $data['Data'] : []);
if (!is_array($meta)) $meta = [];
}
if (!empty($meta['kind']) && $meta['kind'] === 'subscription' || !empty($meta['subscription_id']) || !empty($data['SubscriptionId'])) {
SubscriptionsService::handleFail($data, $requestBody ?: CJSON::encode($data));
$this->sendJson(['code' => 0]); // Fail ожидает code=0 :contentReference[oaicite:9]{index=9}
Yii::app()->end();
}
// старое поведение: просто лог
Yii::log('CloudPayments FAIL params: ' . var_export($data, true), CLogger::LEVEL_WARNING);
$this->sendJson(['code' => 0]);
Yii::app()->end();
}
private function sendJson($data)
{
header('Content-Type: application/json; charset=utf-8');
echo CJSON::encode($data);
}
}
```
===== FILE: common/controllers/DictionaryController.php =====
```
array("index", "list"),
"roles" => array("readDictionary"),
),
array("allow",
"actions" => array("update", "delete", "create"),
"roles" => array("updateDictionary"),
),
array("deny",
"users" => array("*"),
),
);
}
public function actionIndex($partial = false){
unset($_GET["partial"]);
$this->pageTitle = "Справочники";
$models = ModelNames::model()->findAll(array("order" => "t.sort ASC", "condition" => "parent_id = '23'"));
$params = array(
"data" => $models,
"count" => count( $models ),
);
if( !$partial ){
$this->render("index".(($this->isMobile)?"Mobile":""), $params);
}else{
$this->renderPartial("index".(($this->isMobile)?"Mobile":""), $params);
}
}
public function actionList($partial = false, $class = NULL){
$_GET["class"] = ucfirst($_GET["class"]);
unset($_GET["partial"]);
// $this->title = "asda";
if( $model = ModelNames::model()->find("code = '".$class."'") ){
$this->pageTitle = $model->name;
}
$className = trim($class);
if( !$className || !class_exists($className) ){
throw new CHttpException(404, "Class «".$className."» is not defined");
}
$this->checkAccess($class);
$filter = new $className('filter');
if( isset($filter->sort) ){
$filter->sort = NULL;
}
if( isset($filter->active) ){
$filter->active = NULL;
}
if( !$filter->isDictionary ){
throw new CHttpException(404, "Class «".$className."» is not dictionary");
}
if (isset($_GET[ $className ])){
$filter->attributes = $_GET[ $className ];
}
$count = 50;
if( isset($filter->countPerPage) ){
$count = $filter->countPerPage;
}
$dataProvider = $filter->sorted()->search($count);
$count = $filter->search($count, true);
$pages = $dataProvider->getPagination();
$pages->route = "dictionary/list";
$params = array(
"data" => $dataProvider->getData(),
"pages" => $pages,
"filter" => $filter,
"count" => $count,
"labels" => $className::model()->attributeLabels(true),
"modelName" => $this->adminMenu["items"][ $_GET["class"] ] ?? $this->adminMenu["items"][ mb_strtolower($_GET["class"], "UTF-8") ]
);
if( !$partial ){
$this->render("list", $params);
}else{
$this->renderPartial("list", $params);
}
}
public function actionCreate($class = NULL)
{
$className = ucfirst(trim($class));
if( !$className || !class_exists($className) ){
throw new CHttpException(404, "Class «".$className."» is not defined");
}
$this->checkAccess($class);
$model = new $className();
if( !$model->isDictionary ){
throw new CHttpException(404, "Class «".$className."» is not dictionary");
}
$fields = $className::model()->attributeLabels(true);
foreach ($fields as $key => $label) {
if( $key == "id" && !isset($label->showInput) ){
unset($fields[$key]);
}
}
if(isset($_POST[ $className ])) {
if( $photo = Controller::savePhoto(1200, 1200) ){
$_POST[ $className ]["photo"] = $photo;
}
if( $model->updateObj($_POST[ $className ]) ){
$this->actionList(true, $_GET["class"]);
return true;
}
} else {
if(isset($_GET[ $className ])) {
$model->attributes = $_GET[ $className ];
}
$this->renderPartial("create",array(
"model" => $model,
"fields" => $fields,
"modelName" => $this->adminMenu["items"][ $_GET["class"] ] ?? $this->adminMenu["items"][ mb_strtolower($_GET["class"], "UTF-8") ]
));
}
}
public function actionUpdate($id, $class = NULL)
{
$className = ucfirst(trim($class));
if( !$className || !class_exists($className) ){
throw new CHttpException(404, "Class «".$className."» is not defined");
}
$this->checkAccess($class);
$model = $this->loadModel($id, $className);
if( !$model->isDictionary ){
throw new CHttpException(404, "Class «".$className."» is not dictionary");
}
$fields = $className::model()->attributeLabels(true);
foreach ($fields as $key => $label) {
if( $key == "id" && !isset($label->showInput) ){
unset($fields[$key]);
}
}
if(isset($_POST[ $className ])) {
if( $photo = Controller::savePhoto(1200, 1200) ){
if( $photo != $model->photo && !empty($model->photo) ){
unlink($model->photo);
}
$_POST[ $className ]["photo"] = $photo;
}
if( $model->updateObj($_POST[ $className ]) ){
$this->actionList(true, $_GET["class"]);
return true;
}
} else {
$this->renderPartial("update",array(
"model" => $model,
"fields" => $fields,
"modelName" => $this->adminMenu["items"][ $_GET["class"] ] ?? $this->adminMenu["items"][ mb_strtolower($_GET["class"], "UTF-8") ]
));
}
}
public function actionDelete($id, $class = NULL)
{
$className = ucfirst(trim($class));
if( !$className || !class_exists($className) ){
throw new CHttpException(404, "Class «".$className."» is not defined");
}
$this->checkAccess($class);
$model = $this->loadModel($id, $className);
if( !$model->isDictionary ){
throw new CHttpException(404, "Class «".$className."» is not dictionary");
}
$model->delete();
$this->actionList(true, $_GET["class"]);
}
public function checkAccess($class){
$modelName = ModelNames::model()->find(array(
'condition' => "code='".$class."'",
));
if ( !Yii::app()->user->checkAccess($modelName->rule) ) {
throw new CHttpException(403, "Forbidden");
}
}
public function loadModel($id, $className)
{
$model = $className::model()->findByPk($id);
if($model===null)
throw new CHttpException(404, "The requested page does not exist.");
return $model;
}
}
```
===== FILE: common/controllers/User3Controller.php =====
```
array("index"),
"roles" => array("root"),
),
array("allow",
"actions" => array("create", "update", "delete"),
"roles" => array("root"),
),
array("deny",
"users" => array("*"),
),
);
}
public function actionCreate()
{
$model=new User;
if(isset($_POST["User"]))
{
$model->attributes=$_POST["User"];
if($model->save()){
if( isset($_POST["Roles"]) ){
foreach ($_POST["Roles"] as $key => $roleId) {
$role = new UserRole();
$role->user_id = $model->id;
$role->role_id = $roleId;
$role->save();
}
}
$this->actionIndex(true);
return true;
}
}
$roleList = Role::model()->findAll();
$this->renderPartial("create",array(
"model" => $model,
"roleList" => $roleList
));
}
public function actionUpdate($id)
{
$model = $this->loadModel($id);
if(isset($_POST["User"]))
{
$model->prevPass = $model->password;
$model->attributes = $_POST["User"];
UserRole::model()->deleteAll("user_id=".$model->id);
if( isset($_POST["Roles"]) ){
foreach ($_POST["Roles"] as $key => $roleId) {
$role = new UserRole();
$role->user_id = $model->id;
$role->role_id = $roleId;
$role->save();
}
}
if($model->save()){
$this->actionIndex(true);
}
}else{
$roles = array();
foreach ($model->roles as $key => $role) {
array_push($roles, $role->role_id);
}
$roleList = Role::model()->findAll();
$this->renderPartial("update",array(
"model" => $model,
"roles" => $roles,
"roleList" => $roleList
));
}
}
public function actionDelete($id)
{
$this->loadModel($id)->delete();
$this->actionIndex(true);
}
public function actionIndex($partial = false)
{
if( !$partial ){
$this->layout="admin";
$this->pageTitle = $this->adminMenu["cur"]->name;
}
$filter = new User('filter');
if (isset($_GET['User'])){
$filter->attributes = $_GET['User'];
}
Controller::accessFilter($filter);
$dataProvider = $filter->search(50);
$count = $filter->search(50, true);
$params = array(
"data" => $dataProvider->getData(),
"pages" => $dataProvider->getPagination(),
"filter" => $filter,
"count" => $count,
"labels" => User::attributeLabels(),
);
if( !$partial ){
$this->render("index".(($this->isMobile)?"Mobile":""), $params);
}else{
$this->renderPartial("index".(($this->isMobile)?"Mobile":""), $params);
}
}
/**
* Returns the data model based on the primary key given in the GET variable.
* If the data model is not found, an HTTP exception will be raised.
* @param integer $id the ID of the model to be loaded
* @return User the loaded model
* @throws CHttpException
*/
public function loadModel($id)
{
$model=User::model()->with("roles.role")->findByPk($id);
if($model===null)
throw new CHttpException(404, "The requested page does not exist.");
return $model;
}
/**
* Performs the AJAX validation.
* @param User $model the model to be validated
*/
protected function performAjaxValidation($model)
{
if(isset($_POST["ajax"]) && $_POST["ajax"] === "user-form")
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
}
}
```
===== FILE: common/controllers/SettingsController.php =====
```
array('index','create','update','delete','list',"categoryCreate","categoryUpdate","categoryDelete"),
'roles'=>array('updateSettings'),
),
array('deny',
'users'=>array('*'),
),
);
}
public function actionCategoryCreate()
{
$model=new Category;
if(isset($_POST['Category']))
{
$model->attributes=$_POST['Category'];
if($model->save()){
$this->actionIndex(true);
return true;
}
}
$this->renderPartial('categoryCreate',array(
'model'=>$model,
));
}
public function actionCategoryUpdate($id)
{
$model=Category::model()->findByPk($id);
if(isset($_POST['Category']))
{
$model->attributes=$_POST['Category'];
if($model->save())
$this->actionIndex(true);
}else{
$this->renderPartial('categoryUpdate',array(
'model'=>$model,
));
}
}
public function actionCategoryDelete($id)
{
$model=Category::model()->findByPk($id);
$model->delete();
$this->actionIndex(true);
}
public function actionCreate()
{
$model=new Settings;
if(isset($_POST['Settings']))
{
$model->attributes=$_POST['Settings'];
if($model->save()){
$this->actionList($model->parent_id,$model->category_id,true);
return true;
}
}
$this->renderPartial('create',array(
'model'=>$model,
));
}
public function actionUpdate($id)
{
$model=$this->loadModel($id);
if(isset($_POST['Settings']))
{
$model->attributes=$_POST['Settings'];
if($model->save())
$this->actionList($model->parent_id,$model->category_id,true);
}else{
$this->renderPartial('update',array(
'model'=>$model,
));
}
}
public function actionDelete($id)
{
$model = $this->loadModel($id);
$model->delete();
$this->actionList($model->parent_id, $model->category_id, true);
}
public function actionIndex($partial = false)
{
if( !$partial ){
$this->layout='admin';
}
$model = Category::model()->findAll(array("order"=>'sort ASC'));
$option = array(
'data'=>$model,
'labels'=>Category::model()->attributeLabels()
);
if( !$partial ){
$this->render('index',$option);
}else{
$this->renderPartial('index',$option);
}
}
public function actionList($parent_id = false,$id = false,$partial = false)
{
if( !$partial ){
$this->layout='admin';
}
if( $id ){
$category = Category::model()->findByPk($id);
}else if( $parent_id ){
$parent = Settings::model()->find("id=".$parent_id);
}
$filter = new Settings('filter');
$criteria = new CDbCriteria();
if (isset($_GET['Settings']))
{
$filter->attributes = $_GET['Settings'];
foreach ($_GET['Settings'] AS $key => $val)
{
if ($val != '')
{
$criteria->addSearchCondition($key, $val);
}
}
}
$criteria->order = 'sort ASC';
if( $id ){
$criteria->addSearchCondition('category_id', $id);
$back_link = $this->createUrl('/'.$this->adminMenu["cur"]->code.'/index');
}else if( $parent_id ){
$criteria->addSearchCondition('parent_id', $parent_id);
if( $parent->parent_id == 0 ){
$back_link = $this->createUrl('/'.$this->adminMenu["cur"]->code.'/list',array('id'=>$parent->category_id));
}else{
$back_link = $this->createUrl('/'.$this->adminMenu["cur"]->code.'/list',array('parent_id'=>$parent->parent_id));
}
}
$model = Settings::model()->findAll($criteria);
$option = array(
'data'=>$model,
'filter'=>$filter,
'back_link'=>$back_link,
'labels'=>Settings::model()->attributeLabels()
);
if( $id ){
$option['category'] = $category;
}else if( $parent_id ){
$option['parent'] = $parent;
}
if( !$partial ){
$this->render('list'.(($this->isMobile)?"Mobile":""),$option);
}else{
$this->renderPartial('list'.(($this->isMobile)?"Mobile":""),$option);
}
}
public function loadModel($id)
{
$model=Settings::model()->findByPk($id);
if($model===null)
throw new CHttpException(404,'The requested page does not exist.');
return $model;
}
}
```
===== FILE: common/controllers/SiteController.php =====
```
array("notifications"),
"users" => array("*"),
),
array("allow",
"actions" => array("download", "viewFile"),
"roles" => array("readAll"),
),
// array("allow",
// "actions" => array("upload", "install"),
// "roles" => array("root"),
// ),
array("allow",
"actions" => array("error", "index", "login", "logout", "install"),
"users" => array("*"),
),
array("deny",
"users" => array("*"),
),
);
}
/**
* Declares class-based actions.
*/
public function actions()
{
return array(
// captcha action renders the CAPTCHA image displayed on the contact page
"captcha" => array(
"class" => "CCaptchaAction",
"backColor" => 0xFFFFFF,
),
// page action renders "static" pages stored under "protected/views/site/pages"
// They can be accessed via: index.php?r=site/page&view=FileName
"page" => array(
"class" => "CViewAction",
),
);
}
public function actionDownload($file_id){
$file = File::model()->findByPk($file_id);
if($file===null)
throw new CHttpException(404, "The requested page does not exist.");
$this->downloadFile(Yii::app()->params['saveFolder']."/".$file->name, $file->original);
}
public function actionViewFile($file_id){
$file = File::model()->findByPk($file_id);
if($file===null)
throw new CHttpException(404, "The requested page does not exist.");
$filename = Yii::app()->params['saveFolder']."/".$file->name;
if (file_exists($filename)) {
$ext = array_pop(explode(".", $filename));
// header('Content-Description: File Transfer');
switch (strtolower($ext)) {
case "pdf":
header('Content-Type: application/pdf');
break;
case "jpg":
case "jpeg":
case "png":
case "gif":
case "gif":
header('Content-Type: image/'.$ext);
break;
default:
header('Content-Type: application/octet-stream');
break;
}
// header('Content-Disposition: attachment; filename="'.basename($filename).'"');
// header('Expires: 0');
// header('Cache-Control: must-revalidate');
// header('Pragma: public');
header('Content-Length: ' . filesize($filename));
readfile($filename);
exit;
}
// readfile(Yii::app()->params['saveFolder']."/".$file->name);
// $this->downloadFile(, $file->original);
}
/**
* This is the action to handle external exceptions.
*/
public function actionError()
{
$this->layout="public";
if($error=Yii::app()->errorHandler->error)
{
// if(Yii::app()->request->isAjaxRequest)
// echo $error["message"];
// else
$this->render("error", $error);
}
}
/**
* Displays the contact page
*/
public function actionContact()
{
$model=new ContactForm;
if(isset($_POST["ContactForm"]))
{
$model->attributes=$_POST["ContactForm"];
if($model->validate())
{
$headers="From: {$model->email}\r\nReply-To: {$model->email}";
mail(Yii::app()->params["adminEmail"], $model->subject, $model->body, $headers);
Yii::app()->user->setFlash("contact", "Thank you for contacting us. We will respond to you as soon as possible.");
$this->refresh();
}
}
$this->render("contact",array("model" => $model));
}
/**
* Displays the login page
*/
public function actionIndex(){
if( !Yii::app()->user->isGuest ){
$this->redirect($this->createUrl(Yii::app()->params["defaultAdminRedirect"]));
}else{
echo "Пусто :(";
}
}
public function actionLogin()
{
$this->layout="service";
if( !Yii::app()->user->isGuest ) $this->redirect($this->createUrl(Yii::app()->params["defaultAdminRedirect"]));
// $this->layout="admin";
if (!defined("CRYPT_BLOWFISH")||!CRYPT_BLOWFISH)
throw new CHttpException(500, "This application requires that PHP was compiled with Blowfish support for crypt().");
$model=new LoginForm;
// if it is ajax validation request
if(isset($_POST["ajax"]) && $_POST["ajax"]==="login-form")
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
// collect user input data
if(isset($_POST["LoginForm"]))
{
$model->attributes=$_POST["LoginForm"];
// validate user input and redirect to the previous page if valid
if($model->validate() && $model->login())
$this->redirect($this->createUrl(Yii::app()->params["defaultAdminRedirect"]));
}
// display the login form
$this->render("login",array("model" => $model));
}
/**
* Logs out the current user and redirect to homepage.
*/
public function actionLogout()
{
Yii::app()->user->logout();
$this->redirect(Yii::app()->homeUrl);
}
public function actionUpload(){
// Make sure file is not cached (as it happens for example on iOS devices)
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
/*
// Support CORS
header("Access-Control-Allow-Origin: *");
// other CORS headers if any...
if ($_SERVER["REQUEST_METHOD"] == "OPTIONS") {
exit; // finish preflight CORS requests here
}
*/
// 5 minutes execution time
@set_time_limit(5 * 60);
// Uncomment this one to fake upload time
// usleep(5000);
// Settings
$targetDir = "upload/images";
//$targetDir = "uploads";
$cleanupTargetDir = true; // Remove old files
$maxFileAge = 60 * 3600; // Temp file age in seconds
// Create target dir
if (!file_exists($targetDir)) {
@mkdir($targetDir);
}
// Get a file name
if (isset($_REQUEST["name"])) {
$fileName = $_REQUEST["name"];
} elseif (!empty($_FILES)) {
$fileName = $_FILES["file"]["name"];
} else {
$fileName = uniqid("file_");
}
$filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;
echo $filePath;
// Chunking might be enabled
$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
// Remove old temp files
if ($cleanupTargetDir) {
if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
}
while (($file = readdir($dir)) !== false) {
$tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file;
// If temp file is current file proceed to the next
if ($tmpfilePath == "{$filePath}.part") {
continue;
}
// Remove temp file if it is older than the max age and is not the current file
if (preg_match("/\.part$/", $file) && (filemtime($tmpfilePath) < time() - $maxFileAge)) {
@unlink($tmpfilePath);
}
}
closedir($dir);
}
// Open temp file
if (!$out = @fopen("{$filePath}.part", $chunks ? "ab" : "wb")) {
die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
}
if (!empty($_FILES)) {
if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) {
die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
}
// Read binary input stream and append it to temp file
if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
}
} else {
if (!$in = @fopen("php://input", "rb")) {
die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
}
}
while ($buff = fread($in, 4096)) {
fwrite($out, $buff);
}
@fclose($out);
@fclose($in);
// Check if file has been uploaded
if (!$chunks || $chunk == $chunks - 1) {
// Strip the temp .part suffix off
rename("{$filePath}.part", $filePath);
}
// Return Success JSON-RPC response
die('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}');
}
/*! Сброс всех правил. */
public function actionInstall() {
// die();
if ( Yii::app()->user->id != 1 ) {
throw new CHttpException(403, "Forbidden");
}
$auth=Yii::app()->authManager;
//сбрасываем все существующие правила
$auth->clearAll();
// Пользователи
$auth->createOperation("readAdmin", "Просмотр пользователей");
$auth->createOperation("updateAdmin", "Создание/изменение/удаление пользователей");
// Поставки
$auth->createOperation("readTracking", "Просмотр поставок");
$auth->createOperation("updateTracking", "Создание/изменение поставок");
// Партнерка
$auth->createOperation("readRefer", "Просмотр партнеров");
$auth->createOperation("updateRefer", "Создание/изменение партнеров");
// Статистика
$auth->createOperation("readStat", "Просмотр аналитики");
$auth->createOperation("updateStat", "Создание/изменение аналитики");
// Оптовые заказы
$auth->createOperation("readOpt", "Просмотр оптовых заказов");
$auth->createOperation("updateOpt", "Создание/изменение оптовых заказов");
// Справочники
$auth->createOperation("readDictionary", "Просмотр справочников");
$auth->createOperation("updateDictionary", "Создание/изменение справочников");
$auth->createOperation("accessBreed", "Доступ к породам");
$auth->createOperation("accessTk", "Доступ к ТК");
$auth->createOperation("accessSupplier", "Доступ к поставщикам");
$auth->createOperation("accessCity", "Доступ к городам");
// Заказы
$auth->createOperation("readOrder", "Просмотр заказов");
$auth->createOperation("readSumOrder", "Просмотр суммы заказов");
$auth->createOperation("updateOrder", "Создание/изменение заказов");
$auth->createOperation("deleteOrder", "Удаление заказов");
$auth->createOperation("changeStatusOrder", "Изменения статуса заказа");
$auth->createOperation("markPlatesOrder", "Массово помечать таблички готовностью");
// Зарплата
$auth->createOperation("readSalary", "Просмотр зарплаты");
$auth->createOperation("updateSalary", "Редактирование зарплаты");
//Настройки
$auth->createOperation("updateSettings", "Изменение настроек");
// Права --------------------------------------------------- Права
// Мастер
$role = $auth->createRole("master");
$role->addChild("readOrder");
$role->addChild("accessBreed");
$role->addChild("readDictionary");
// Швея
$role = $auth->createRole("sew");
$role->addChild("master");
// Фотограф/упаковщик
$role = $auth->createRole("packer");
$role->addChild("master");
$role->addChild("updateOrder");
$role->addChild("readTracking");
$role->addChild("updateTracking");
$role->addChild("readSumOrder");
$role->addChild("changeStatusOrder");
$role->addChild("markPlatesOrder");
// Продажник
$role = $auth->createRole("salesman");
$role->addChild("packer");
$role->addChild("updateOrder");
$role->addChild("deleteOrder");
$role->addChild("updateDictionary");
$role->addChild("accessCity");
// Руководитель
$role = $auth->createRole("supervisor");
$role->addChild("salesman");
$role->addChild("readAdmin");
$role->addChild("updateAdmin");
$role->addChild("readTracking");
$role->addChild("updateTracking");
$role->addChild("readStat");
$role->addChild("readDictionary");
$role->addChild("accessTk");
$role->addChild("accessSupplier");
$role->addChild("markPlatesOrder");
$role->addChild("readSalary");
$role->addChild("readRefer");
$role->addChild("updateRefer");
$role->addChild("readOpt");
$role->addChild("updateOpt");
// Root-доступ
$role = $auth->createRole("root");
$role->addChild("supervisor");
$role->addChild("updateStat");
$role->addChild("updateSalary");
$role->addChild("updateSettings");
// Роли --------------------------------------------------- Роли
// Связываем пользователей с ролями
$users = Admin::model()->with("roles.role")->findAll();
foreach ($users as $i => $user) {
foreach ($user->roles as $j => $role) {
$auth->assign($role->role->code, $user->id);
}
}
// Сохраняем роли и операции
$auth->save();
// die();
$this->render("install");
}
}
```
===== FILE: common/views/uploader/form.php =====
```
Ваш браузер не поддерживает Flash.
```
===== FILE: common/views/settings/categoryUpdate.php =====
```
Редактирование категории
renderPartial('_categoryForm', array('model'=>$model)); ?>
```
===== FILE: common/views/settings/update.php =====
```
Редактирование =$this->adminMenu["cur"]->rod_name?>
renderPartial('_form', array('model'=>$model)); ?>
```
===== FILE: common/views/settings/index.php =====
```
=$this->adminMenu["cur"]->name?>
code.'/categorycreate')?>" class="ajax-form ajax-create b-butt b-top-butt">Добавить
beginWidget('CActiveForm'); ?>
if( $this->isMobile ): ?> endif; ?>
echo $labels['name']; ?>
echo $labels['code']; ?>
Действия
if( count($data) ): ?>
foreach ($data as $i => $item): ?>
code.'/list',array('id'=>$item->id))?>" class="b-double-click">=$item->name?>
=$item->code?>
code.'/categoryupdate',array('id'=>$item->id))?>" class="ajax-form ajax-update b-tool b-tool-update" title="Редактировать категорию">
code.'/categorydelete',array('id'=>$item->id))?>" class="ajax-form ajax-delete b-tool b-tool-delete" data-name="категорию" title="Удалить категорию">
endforeach; ?>
else: ?>
Пусто
endif; ?>
if( $this->isMobile ): ?> endif; ?>
endWidget(); ?>
```
===== FILE: common/views/settings/_categoryForm.php =====
```
beginWidget('CActiveForm', array(
'id'=>'faculties-form',
'enableAjaxValidation'=>false,
)); ?>
errorSummary($model); ?>
labelEx($model,'name'); ?>
textField($model,'name',array('maxlength'=>255,'required'=>true)); ?>
error($model,'name'); ?>
labelEx($model,'code'); ?>
textField($model,'code',array('maxlength'=>255,'required'=>true)); ?>
error($model,'code'); ?>
labelEx($model,'sort'); ?>
textField($model,'sort',array('maxlength'=>255,'required'=>true)); ?>
error($model,'sort'); ?>
endWidget(); ?>
```
===== FILE: common/views/settings/categoryCreate.php =====
```
Добавление категории
renderPartial('_categoryForm', array('model'=>$model)); ?>
```
===== FILE: common/views/settings/create.php =====
```
Добавление =$this->adminMenu["cur"]->rod_name?>
renderPartial('_form', array('model'=>$model)); ?>
```
===== FILE: common/views/settings/list.php =====
```
Назад
=$this->adminMenu["cur"]->name?>: name;}else{echo $parent->name;}?>
code.'/create',array((($category)?'category_id':'parent_id')=>($category)?$category->id:$parent->id))?>" class="ajax-form ajax-create b-butt b-top-butt">Добавить
beginWidget('CActiveForm'); ?>
| echo $labels['name']; ?> | echo $labels['code']; ?> | echo $labels['value']; ?> | Действия | ||||||
|---|---|---|---|---|---|---|---|---|---|
| =$item->name?> | =$item->code?> | =$this->replaceToBr($this->cutText($item->value))?> | else: ?>code.'/list',array('parent_id'=>$item->id))?>">=$item->name?> | endif; ?>code.'/update',array('id'=>$item->id))?>" class="ajax-form ajax-update b-tool b-tool-update b-double-click-click" title="Редактировать =$this->adminMenu["cur"]->vin_name?>"> code.'/delete',array('id'=>$item->id))?>" class="ajax-form ajax-delete b-tool b-tool-delete" data-name="=$this->adminMenu["cur"]->vin_name?>" title="Удалить =$this->adminMenu["cur"]->vin_name?>"> | |||||
| Пусто | |||||||||
| echo $labels['name']; ?>, echo $labels['code']; ?> | echo $labels['value']; ?> | Действия | |||||||
|---|---|---|---|---|---|---|---|---|---|
=$item->name?> =$item->code?> |
=$this->replaceToBr($this->cutText($item->value))?> | else: ?>code.'/list',array('parent_id'=>$item->id))?>">=$item->name?> | endif; ?>code.'/update',array('id'=>$item->id))?>" class="ajax-form ajax-update b-tool b-tool-update b-double-click-click" title="Редактировать =$this->adminMenu["cur"]->vin_name?>"> code.'/delete',array('id'=>$item->id))?>" class="ajax-form ajax-delete b-tool b-tool-delete" data-name="=$this->adminMenu["cur"]->vin_name?>" title="Удалить =$this->adminMenu["cur"]->vin_name?>"> | ||||||
| Пусто | |||||||||
| № | =$labels["name"]?> | =$labels["login"]?> | =$labels["roles"]?> | Действия | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1, "placeholder" => "Поиск по логину")); ?> | Сбросить | ||||||||||||||||||
| =$item->id?> | =$item->name?> | =$item->login?> | =implode(", ", $item->getRoleNames())?> | $item->id))?>" class="ajax-form b-double-click-click ajax-update b-tool b-tool-update" title="Редактировать =$this->adminMenu["cur"]->vin_name?>">$item->id))?>" class="ajax-form ajax-delete b-tool b-tool-delete" title="Удалить =$this->adminMenu["cur"]->vin_name?>" data-name="=$this->adminMenu["cur"]->vin_name?>"> | |||||||||||||||
| Ничего не найдено, попробуйте изменить фильтр | |||||||||||||||||||
| № | =$labels["name"]?> | =$labels["login"]?> | =$labels["email"]?> | =$labels["roles"]?> | Действия | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1, "placeholder" => "Поиск по логину")); ?> | 2, "placeholder" => "Поиск по email")); ?> | Сбросить | |||||||||||||||||
| =$item->id?> | =$item->fio?> | =$item->login?> | =$item->email?> | =implode(", ", $item->getRoleNames())?> | $item->id))?>" class="ajax-form b-double-click-click ajax-update b-tool b-tool-update" title="Редактировать =$this->adminMenu["cur"]->vin_name?>">$item->id))?>" class="ajax-form ajax-delete b-tool b-tool-delete" title="Удалить =$this->adminMenu["cur"]->vin_name?>" data-name="=$this->adminMenu["cur"]->vin_name?>"> | ||||||||||||||
| Ничего не найдено, попробуйте изменить фильтр | |||||||||||||||||||
Выйти
=(($this->isMobile)?$menuItem->name:$menuItem->menu_name)?>
endif; ?> if(Yii::app()->params['debug']): ?>| Наименование | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| code.'/list',array('class'=>$item->code))?>" class="b-double-click">=$item->name?> | |||||||||||||||||||
| Ничего не найдено, попробуйте изменить фильтр | |||||||||||||||||||
| Наименование |
|---|
| code.'/list',array('class'=>$item->code))?>" class="b-double-click">=$item->name?> |
| width) ): ?>style="width: =$label->width?>; min-width: =$label->width?>;" endif; ?>>=$label->name?> | endforeach; ?>px;">Действия | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| continue; endif; ?> switch($label->type ?? "default"): case "bool": ?> | "Нет", 1 => "Да"), array("class" => "select2", "empty" => "Да/нет", "tabindex" => 1, "placeholder" => "Да/нет")); ?> | $arData = (isset($label->model))?CHtml::listData($label->model::model()->sorted()->findAll($label->condition ?? null), $label->key ?? 'id', $label->value ?? 'name'):$label->list ?>"select2", "id" => $key."_filter", "empty" => "Все", "tabindex" => 1, "placeholder" => "Не выбрано")); ?> | default: ?>if($filter){ echo CHtml::activeTextField($filter, $key, array('tabindex' => 1, "placeholder" => "Поиск")); } ?> | endswitch; ?> endforeach; ?>Сбросить"> | |||||||||||||||
| class) ): ?>class="=$label->class?>" endif; ?>>
switch($field->type ?? "default"):
case "bool": ?>
=( ($item->{$key})?"Да":"Нет" )?>
=number_format( $item->{$key}, 0, ',', ' ' )?>
=number_format( $item->{$key}, 0, ',', ' ' )?> руб.
|
endforeach; ?>
if( !isset($item->withoutActions) ): ?> if( Yii::app()->user->checkAccess('updateDictionary') && isset($item->id) ): ?> code.'/update',array('id'=>$item->id, 'class' => $_GET["class"]))?>" class="ajax-form b-double-click-click ajax-update b-tool b-tool-update" title="Редактировать =$modelName->vin_name?>"> if( Yii::app()->user->checkAccess('root') && isset($item->id) ): ?>code.'/delete',array('id'=>$item->id, 'class' => $_GET["class"]))?>" class="ajax-form ajax-delete b-tool b-tool-delete" title="Удалить =$modelName->vin_name?>" data-name="=$modelName->vin_name?>"> endif; ?> endif; ?> endif; ?> | ||||||||||||||||||
| Ничего не найдено | |||||||||||||||||||
| width) ): ?>style="width: =$label->width?>;" endif; ?>>=$label->name?> | endforeach; ?>Действия | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| continue; endif; ?> switch($label->type): case "bool": ?> | "Нет", 1 => "Да"), array("class" => "select2", "empty" => "Не выбрано", "tabindex" => 1, "placeholder" => "Не выбрано")); ?> | $arData = (isset($label->model))?CHtml::listData($label->model::model()->sorted()->findAll($label->condition), ($label->key)?$label->key:'id', 'name'):$label->list ?>"select2", "empty" => "Все", "tabindex" => 1, "placeholder" => "Не выбрано")); ?> | default: ?>if($filter){ echo CHtml::activeTextField($filter, $key, array('tabindex' => 1, "placeholder" => "Поиск")); } ?> | endswitch; ?> endforeach; ?>Сбросить"> | |||||||||||||||
| class) ): ?>class="=$label->class?>" endif; ?>>
switch($field->type):
case "bool": ?>
=( ($item->{$key})?"Да":"Нет" )?>
|
endforeach; ?>
if( Yii::app()->user->checkAccess('updateDictionary') ): ?>code.'/update',array('id'=>$item->id, 'class' => $_GET["class"]))?>" class="ajax-form b-double-click-click ajax-update b-tool b-tool-update" title="Редактировать =$this->adminMenu["items"][ $_GET["class"] ]->vin_name?>"> if( Yii::app()->user->checkAccess('updateDictionary') ): ?>code.'/delete',array('id'=>$item->id, 'class' => $_GET["class"]))?>" class="ajax-form ajax-delete b-tool b-tool-delete" title="Удалить =$this->adminMenu["items"][ $_GET["class"] ]->vin_name?>" data-name="=$this->adminMenu["items"][ $_GET["class"] ]->vin_name?>"> endif; ?> endif; ?> | ||||||||||||||||||
| Ничего не найдено | |||||||||||||||||||
If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
Fields with * are required.
errorSummary($model); ?>This is the "about" page for my blog site.
``` ===== FILE: common/views/stats/months.php ===== ```| Месяц | Новых пользователей | Сумма заказов | Кол-во заказов | Средний чек | Генераций |
|---|---|---|---|---|---|
| = CHtml::link( CHtml::encode($row["ym"]), array("stats/month", "ym" => $row["ym"]) ) ?> | = nf_int($row["new_users"]) ?> | = nf_money($row["orders_sum"]) ?> | = nf_int($row["orders_count"]) ?> | = nf_money($row["avg_check"]) ?> | = nf_int($row["generations"]) ?> |
| День | Сумма заказов | Кол-во заказов | Новых пользователей | Генераций | Средний чек |
|---|---|---|---|---|---|
| = CHtml::encode($row["day"]) ?> | = nf_money($row["orders_sum"]) ?> | = nf_int($row["orders_count"]) ?> | = nf_int($row["new_users"]) ?> | = nf_int($row["generations"]) ?> | = nf_money($row["avg_check"]) ?> |
Новых пользователей: = nf_int($summary["new_users"]) ?>
Генераций: = nf_int($summary["generations"]) ?>
/*&Order[date_create_to]==$monthSum["DATE_TO"]?>&Order[status_id]=all">=$monthSum["COUNT"]?> =$this->pluralForm($monthSum["COUNT"], array("заказ", "заказа", "заказов"))?> (чек: =$this->number_format($monthSum["AVERAGE_SUM"])?> руб.)code.'/monthStat')?>" class="b-double-click">
*/ ?>=$this->getRusDate(date("d.m.Y", $curDate), false, false)?>
Выручка по дням
| Дата | foreach ($daily as $row): ?>=$row["day"]?> | endforeach; ?>
|---|---|
| Сумма, руб | foreach ($daily as $row): ?>=nf_int($row["orders_sum"])?> | endforeach; ?>
| Стоимость генераций | foreach ($daily as $row): ?>=nf_int($row["gen_cost"])?> (=nf_int($row["gen_cost_percent"])?>%) | endforeach; ?>
| Чистыми | foreach ($daily as $row): ?>=nf_int($row["profit"])?> (=nf_int($row["profit_percent"])?>%) | endforeach; ?>
| Средний чек | foreach ($daily as $row): ?>=nf_int($row["avg_check"])?> | endforeach; ?>
| Кол-во заказов | foreach ($daily as $row): ?>=nf_int($row["orders_count"])?> | endforeach; ?>
| Новых пользователей | foreach ($daily as $row): ?>=nf_int($row["new_users"])?> | endforeach; ?>
| Кол-во генераций | foreach ($daily as $row): ?>=nf_int($row["generations"])?> | endforeach; ?>
Другие цифры
Важно: {$this->currency[3]} никогда не сгорают. Они переносятся на следующий месяц ❤️\n\nЖми на подходящий тариф и начинай творить! 🎨✨"; } public function prepaymentInfoMessage($package){ return "Оплата ".number_format( $package->price, 0, ',', ' ' )." ₽\n\nВы оплачиваете: «Пакет {$package->amount} ".Yii::app()->controller->pluralForm($package->amount, $this->currency)."». Мы не имеем доступа к вашим личным и платежным данным. Ознакомьтесь с пользовательским соглашением.\n\n".Yii::app()->controller->mb_ucfirst($this->currency[3])." - валюта нашего сервиса.\n1 сгенерированное фото = 1 {$this->currency[0]}.\n\nВ случае возникновения проблем обращайтесь в поддержку: mike@kitaev.pro"; } public function planInfoMessage($planVersion){ return "Оплата ".number_format( $planVersion->price, 0, ',', ' ' )." ".$planVersion->getRusCurrency()."\n\nВы оплачиваете ежемесячную подписку: «Тариф {$planVersion->plan->title}». Мы не имеем доступа к вашим личным и платежным данным. Ознакомьтесь с пользовательским соглашением.\n\n".Yii::app()->controller->mb_ucfirst($this->currency[3])." - валюта нашего сервиса.\n1 сгенерированное фото = 1 {$this->currency[0]}.\n\nВ случае возникновения проблем обращайтесь в поддержку: mike@kitaev.pro"; } public function subscriptionSuccessMessage($amount){ return "🎉 Подписка успешно активирована!\n\nНа баланс начислено {$amount} ".Yii::app()->controller->pluralForm($amount, $this->currency)." ✨\nТвой текущий баланс: {$this->user->balance} ".Yii::app()->controller->pluralForm($amount, $this->currency)."\n\nСпасибо за поддержку ❤️"; } public function subscriptionRenewalMessage($amount){ return "🔁 Подписка автоматически продлена \n\nНа баланс начислено {$amount} ".Yii::app()->controller->pluralForm($amount, $this->currency)." ✨\nТвой текущий баланс: {$this->user->balance} ".Yii::app()->controller->pluralForm($amount, $this->currency)."\n\nСпасибо, что ты с нами ❤️"; } public function cancelSubscriptionWarningMessage($until = null) { $msg = "⚠️ Отмена подписки\n\n"; $msg .= "Если ты отменишь подписку — авто-списания остановятся ✔️\n"; if ($until) { $msg .= "\nТы сможешь пользоваться ботом без ограничений до ".Yii::app()->controller->getRusDate($until)." ✨\n"; } else { $msg .= "\nДоступ сохранится до конца оплаченного периода.\n"; } $msg .= "\nПодтвердить отмену?"; return $msg; } public function cancelSubscriptionDoneMessage($until = null) { $msg = "✅ Подписка отменена.\n\n"; if ($until) { $msg .= "Доступ сохранится до: {$until}\n"; } else { $msg .= "Доступ сохранится до конца оплаченного периода.\n"; } return $msg; } public function subscriptionCancellingMessage(){ return "Отменяем подписку..."; } public function subscriptionCanceledMessage(){ return "☑️ Подписка остановлена \n\nНо бот никуда не уходит 🙂 Продолжай экспериментировать и вдохновляться — мы для этого и созданы ❤️"; } public function paymentSuccessMessage($amount){ return "Оплата прошла успешно! 🎉\n\nНа баланс начислено {$amount} ".Yii::app()->controller->pluralForm($amount, $this->currency)." ✨\nТвой текущий баланс: {$this->user->balance} ".Yii::app()->controller->pluralForm($amount, $this->currency)."\n\nМожно продолжать творить без ограничений! ❤️"; } public function customPromptMessage(){ return "Опиши, какую фотографию ты хочешь получить ✨\n\nНапример:\n— «Я на фоне Эйфелевой башни летом с бокалом вина»\n— «Я на море в белом платье на закате»\n\nЧем подробнее опишешь идею — тем точнее и красивее получится результат 📸"; } public function editPromptMessage(){ return "Опиши, что ты хочешь изменить на этом фото ✨\n\nНапример:\n— «Измени цвет одежды на бежевый»\n— «Убери человека на фоне»\n— «Добавь новогоднюю атмосферу»\n\nЧем точнее опишешь правки — тем лучше получится результат 📸"; } public function whatToDoWithPhotoMessage(){ return "Что желаешь сделать с этой фотографией?\n\nВыбери одно из предложенных действий и я сделаю это для тебя ❤️"; } public function politicsMessage(){ return "📘 Чтобы всё работало гладко и приятно, советуем ознакомиться с политикой использования. Вот ссылка — это быстро и полезно ❤️\n\n👉 ".Yii::app()->params["basePath"]; } public function supportMessage(){ return "🛠 Нужна помощь?\n\nМы всегда рядом и готовы подсказать ❤️\nНапишите в техническую поддержку по ссылке ниже:\n👉 ".Yii::app()->params["basePath"]; } public function generatingMessage(){ return "🔮 Нейросеть творит волшебство...\n\nПодожди чуть-чуть — кадры уже в пути 📸💫"; } public function errorGeneratingMessage(){ return "Не удалось сгенерировать изображение :( {$this->currency[0]} не был списан. Попробуйте повторить через пару минут."; } public function firstTryonMessage(){ return "Скинь фото образа, который тебе нравится 👗✨\n\nМожно взять из Pinterest или интернет-магазина — просто сделай скрин и отправь сюда 📸💛"; } public function firstTryonStartMessage(){ return "Стиль вижу, уже вдохновляет 👗🔥\n\nОсталось загрузить твоё фото — и можно примерять ✨"; } public function keyboardMessage(){ return "Отличный результат!\n\nЯ запомнил твоё фото — теперь можем создавать крутые и реалистичные AI-кадры в один клик! 📸\n\nОсновное меню — внизу, прямо на клавиатуре 👇"; } public function requestPhotoFullMessage(){ return "Отлично! 😍 А теперь пришли фото во весь рост 📸\n\n✨ Так я точнее подстрою образы и кадры под тебя 💛"; } public function chooseSessionMessage(){ return "Выберите фотосессию:"; } public function changePhotoMessage(){ return "Пришли, пожалуйста, свой портрет 📸\n\nЛицо крупно, но фото лучше сделать не селфи, а чуть издалека (с зумом) — так будет красивее 💛 ✨🌈"; } public function changePhotoSuccessMessage(){ return "Супер! 💛\n\nЯ запомнил твоё фото — Теперь можно примерять одежду и образы 👗 и запускать фотосеты 📸✨"; } public function helpMessage(){ return "Фотосессии 📷 – выбери тематику и получи готовый фотосет в выбранном стиле.\n\nСлучайное фото 🎲 – один кадр-сюрприз, стиль выбираем за тебя.\n\nФото по описанию 💬 – опиши идею и создай свой идеальный снимок.\n\nСменить фото 👤 — другое своё фото и попробуй новые образы.\n\n/invite - приглашай друзей и получай бесплатные {$this->currency[3]}.\n\n/balance - проверь, сколько {$this->currency[2]} осталось\n\n/support - напиши нам, если что-то не работает или заметил баг 🛠️\n\nА ещё, любое фото можно отредактировать! ✨\nПросто ответь на сообщение с нужной фотографией и напиши, что хочешь изменить, например: «Измени цвет рубашки на бежевый» или «Убери человека на фоне». Бот всё сделает для тебя 💫"; } public function defaultMessage(){ return "В Кадрум можно:\n– Примерять образы 👗👚\n– Запускать фотосессии 📸\n– Создавать фото по описанию 💬✨\n\nНажимай нужную кнопку внизу на клавиатуре, и продолжим творить 💛"; } public function errorFindSessionMessage(){ return "Извините, фотосессия не найдена. Воспользуйтесь меню снизу 👇"; } public function inviteMessage(){ $out = "Отправь друзьям ссылку:\n\n".$this->user->getInviteLink()."\n\nКогда твой друг зайдет в бот по этой ссылке и начнет пользоваться, ты получишь {$this->user->refBonus} ".Yii::app()->controller->pluralForm($this->user->refBonus, $this->currency)." на баланс!"; if( $this->user->startBalance > 1 ){ $out .= " А твои друзья получат по ".$this->user->startBalance." бесплатных {$this->currency[2]}."; } return $out; } public function refSuccessMessage(){ return "Отличные новости!\n\nТвой друг начал пользоваться нашим ботом, за это мы начислили тебе {$this->user->refBonus} ".Yii::app()->controller->pluralForm($this->user->refBonus, $this->currency)."\n\nСпасибо, что делишься нашим крутым сервисом! ♥️"; } public function userAlreadyExistRefMessage(){ return "Обратите внимание: реферальная ссылка не применилась.\nРеферальные ссылки действительны только для новых аккаунтов."; } public function refAlreadyExistRefMessage(){ return "Обратите внимание: реферальная ссылка не применилась.\nВы уже переходили по реферальной ссылке."; } public function notFoundRefMessage(){ return "Обратите внимание: реферальная ссылка не применилась.\nПользователь, пригласивший вас, не существует."; } public function notSelfRefMessage(){ return "Реферальную ссылку нельзя применить к своему же аккаунту 😅\n\nОтправь ссылку друзьям и получай бесплатные {$this->currency[3]}"; } } ?> ``` ===== FILE: apps/kadrum/protected/components/TryOnDeepLinkStorageDb.php ===== ``` $img, 'name' => $name, 'back' => $back, ]; } public static function payloadHash(array $payloadCanon): string { // hash от канонизированного набора, чтобы одинаковые ссылки давали один токен return sha1(json_encode($payloadCanon, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } public static function getOrCreate(array $payload, ?int $ttlSeconds = null): TryonDeeplink { $canon = self::canonicalize($payload); if ($canon['img'] === '' || !filter_var($canon['img'], FILTER_VALIDATE_URL)) { throw new InvalidArgumentException('img is required and must be URL'); } $hash = self::payloadHash($canon); // 1) сначала пробуем найти $m = TryonDeeplink::model()->findByAttributes(['payload_hash' => $hash]); if ($m) return $m; // 2) если нет — создаём (с защитой от гонки) for ($i = 0; $i < 2; $i++) { $m = new TryonDeeplink(); $m->token = self::generateToken(); $m->payload_hash = $hash; $m->img_url = $canon['img']; $m->product_name = $canon['name']; $m->back_url = $canon['back']; $m->created_at = date('Y-m-d H:i:s'); $m->updated_at = $m->created_at; if ($ttlSeconds && $ttlSeconds > 0) { $m->expire_at = date('Y-m-d H:i:s', time() + $ttlSeconds); } if ($m->save()) { return $m; } // если словили unique payload_hash из-за параллельного запроса — забираем существующий $existing = TryonDeeplink::model()->findByAttributes(['payload_hash' => $hash]); if ($existing) return $existing; } throw new RuntimeException('Failed to create deeplink'); } public static function loadByToken(string $token, bool $touchUseCount = true): ?TryonDeeplink { $token = preg_replace('~[^a-zA-Z0-9]~', '', $token); if ($token === '') return null; /** @var TryonDeeplink|null $m */ $m = TryonDeeplink::model()->findByAttributes(['token' => $token]); if (!$m) return null; if (!empty($m->expire_at) && strtotime($m->expire_at) < time()) { return null; } if ($touchUseCount) { Yii::app()->db->createCommand(" UPDATE tryon_deeplink SET use_count = use_count + 1, last_used_at = NOW(), updated_at = NOW() WHERE id = :id ")->execute([':id' => (int)$m->id]); } return $m; } public static function incrementGenCount(int $id): void { Yii::app()->db->createCommand(" UPDATE tryon_deeplink SET gen_count = gen_count + 1, updated_at = NOW() WHERE id = :id ")->execute([':id' => (int)$id]); } private static function generateToken(): string { try { return bin2hex(random_bytes(8)); // 16 символов } catch (Throwable $e) { return dechex(time()) . dechex(mt_rand(100000, 999999)); } } } ``` ===== FILE: apps/kadrum/protected/components/bot/BotUiFactory.php ===== ``` user = $user; $this->currency = $currency; $this->controller = $controller; $this->mg = new MessageGenerator($user, $currency); } public static function fromApp(User $user): self { $controller = Yii::app()->controller; $currency = null; if ($controller && property_exists($controller, 'currency')) { $currency = $controller->currency; } if (!is_array($currency)) { $currency = ["кадр", "кадра", "кадры", "кадры"]; } return new self($user, $currency, $controller); } // -------------------- BASIC SCREENS -------------------- public function start(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->startMessage(), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Примерить образ", "callback_data" => "/first_tryon", ], ], [ [ "text" => "Хочу фотосессию!", "callback_data" => "/first_gen", ], ], ], ], ]; } public function firstGen(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->changePhotoMessage(), ]; } public function firstTryon(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->firstTryonMessage(), ]; } public function firstTryonStart(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->firstTryonStartMessage(), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Скорее прикрепить фото 📸", "callback_data" => "/tryon", ], ] ], ] ]; } public function default(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->defaultMessage(), "reply_markup" => $this->mainKeyboard(), ]; } public function error(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->errorMessage(), ]; } public function generating(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->generatingMessage(), ]; } public function errorGenerating(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->errorGeneratingMessage(), ]; } public function emptyBalance(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->emptyBalanceMessage(), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Купить {$this->currency[3]}", "callback_data" => "/choose_package", ], ], ], ], ]; } public function subscriptionRequired(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->subscriptionRequiredMessage(), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Выбрать тариф", "callback_data" => "/choose_plan", ], ], ], ], ]; } public function changePhoto($withBackButton = false): array { $out = [ "parse_mode" => "HTML", "text" => $this->mg->changePhotoMessage() ]; if( $withBackButton ){ $out["reply_markup"] = $this->backKeyboard(); } return $out; } public function whatToDoWithPhoto(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->whatToDoWithPhotoMessage(), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Примерить этот образ", "callback_data" => "/tryon", ], ], [ [ "text" => "Редактировать фото", "callback_data" => "/edit", ], ], [ [ "text" => "Запомнить меня для генерации", "callback_data" => "/save_photo", ], ], ], ] ]; } public function customPrompt(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->customPromptMessage(), "reply_markup" => $this->backKeyboard(), ]; } public function editPrompt(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->editPromptMessage(), "reply_markup" => $this->backKeyboard(), ]; } public function sendKeyboard(bool $isNewUser = false): array { return [ "parse_mode" => "HTML", "text" => $isNewUser ? $this->mg->keyboardMessage() : $this->mg->changePhotoSuccessMessage(), "reply_markup" => $this->mainKeyboard(), ]; } public function requestPhotoFull(bool $isPhotoFullEmpty = true): array { $out = [ "parse_mode" => "HTML", "text" => $this->mg->requestPhotoFullMessage() ]; if( !$isPhotoFullEmpty ){ $out["reply_markup"] = $this->backKeyboard("Оставить фото как было"); } return $out; } // -------------------- REFERRAL / PAYMENT / SOURCE -------------------- public function invite(): array { return [ "parse_mode" => "HTML", "disable_web_page_preview" => true, "text" => $this->mg->inviteMessage() ]; } public function balance(): array { // Показываем "Отменить подписку" только если есть подписка, которая ещё автопродлевается // (status=Active и canceled_at IS NULL) $subToCancel = UserSubscription::model()->find( 'user_id=:u AND status=:st AND canceled_at IS NULL AND cp_subscription_id IS NOT NULL ORDER BY id DESC', [':u' => (int)$this->user->id, ':st' => 'Active'] ); return [ "parse_mode" => "HTML", "text" => $this->mg->balanceMessage($this->user), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Купить {$this->currency[3]}", "callback_data" => "/choose_package", ], ], ...(!$this->user->hasActiveSubscribtion() ? [ [ [ "text" => "Оплатить подписку", "callback_data" => "/choose_plan", ] ] ] : []), ...($subToCancel ? [ [ [ "text" => "Отменить подписку", "callback_data" => "/cancel_subscription_" . (int)$subToCancel->id, ], ] ] : []), ...(!$subToCancel && $this->user->hasActiveSubscribtion() ? [ [ [ "text" => "Выбрать другой тариф", "callback_data" => "/choose_plan", ], ] ] : []), ], ], ]; } public function cancelSubscriptionWarning(UserSubscription $sub): array { $until = $sub->next_charge_at ? $sub->next_charge_at : null; return [ "parse_mode" => "HTML", "text" => $this->mg->cancelSubscriptionWarningMessage($until), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Подтвердить отмену подписки", "callback_data" => "/confirm_cancel_subscription_" . (int)$sub->id, ], ], ], ], ]; } public function subscriptionCancelling(): array { return [ "parse_mode" => "HTML", "text" => $this->mg->subscriptionCancellingMessage(), ]; } public function choosePackage(): array { // если хочешь — можно раскомментить пакеты сразу $packagesKb = Package::model()->getKeyboard($this->currency); return [ "parse_mode" => "HTML", "text" => $this->mg->choosePackageMessage(), "reply_markup" => [ "inline_keyboard" => $packagesKb ?: [], ], ]; } public function choosePlan(): array { // если хочешь — можно раскомментить пакеты сразу $planKb = PlanVersion::model()->getKeyboard(); return [ "parse_mode" => "HTML", "text" => $this->mg->choosePlanMessage(), "reply_markup" => [ "inline_keyboard" => $planKb ?: [], ], ]; } public function planInfo(PlanVersion $planVersion, int $userId): array { $cardUrl = $this->controller->getSubscribeLink("card", $userId, (int)$planVersion->id); // $sbpUrl = $this->controller->getSubscribeLink("sbp", $userId, (int)$planVersion->id); return [ "parse_mode" => "HTML", "text" => $this->mg->planInfoMessage($planVersion), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Оплатить картой МИР", "web_app" => [ "url" => $cardUrl, ], ], ], // [ // [ // "text" => "Оплатить по СБП", // "web_app" => [ // "url" => $sbpUrl, // ], // ], // ], ], ], ]; } public function prepaymentInfo(Package $package, int $userId): array { $cardUrl = $this->controller->getPaymentLink("card", $userId, (int)$package->id); $sbpUrl = $this->controller->getPaymentLink("sbp", $userId, (int)$package->id); return [ "parse_mode" => "HTML", "text" => $this->mg->prepaymentInfoMessage($package), "reply_markup" => [ "inline_keyboard" => [ [ [ "text" => "Оплатить картой МИР", "web_app" => [ "url" => $cardUrl, ], ], ], // [ // [ // "text" => "Оплатить по СБП", // "web_app" => [ // "url" => $sbpUrl, // ], // ], // ], ], ], ]; } private function getPaymentLinkSafe(string $type, int $userId, int $packageId): string { // в проекте это было в контроллере (getPaymentLink) if ($this->controller && method_exists($this->controller, 'getPaymentLink')) { return (string)$this->controller->getPaymentLink($type, $userId, $packageId); } // fallback (чтобы не падать) $base = Yii::app()->params["basePath"] ?? "/"; return rtrim($base, "/") . "/payment?type=" . urlencode($type) . "&user_id=" . $userId . "&package_id=" . $packageId; } private function getSubscribeLinkSafe(string $type, int $userId, int $planVersionId): string { // в проекте это было в контроллере (getPaymentLink) if ($this->controller && method_exists($this->controller, 'getSubscribeLink')) { return (string)$this->controller->getSubscribeLink($type, $userId, $planVersionId); } // fallback (чтобы не падать) $base = Yii::app()->params["basePath"] ?? "/"; return rtrim($base, "/") . "/payment?type=" . urlencode($type) . "&user_id=" . $userId . "&planVersionId=" . $planVersionId; } // -------------------- STATIC PAGES -------------------- public function politics(): array { $mg = $this->makeMessageGenerator(); return [ "parse_mode" => "HTML", "text" => $mg->politicsMessage(), ]; } public function support(): array { $mg = $this->makeMessageGenerator(); return [ "parse_mode" => "HTML", "text" => $mg->supportMessage(), ]; } /** * MessageGenerator берём так же безопасно, как в твоих хендлерах (Help/CustomPrompt): * подтягиваем currency из controller, если он есть. */ private function makeMessageGenerator(): MessageGenerator { $controller = Yii::app()->controller; $currency = (is_object($controller) && isset($controller->currency) && is_array($controller->currency)) ? $controller->currency : []; return new MessageGenerator($this->user, $currency); } // -------------------- KEYBOARDS -------------------- public function backKeyboard($title = "⏪ Назад"): array { return [ "keyboard" => [ [ [ "text" => $title, "callback_data" => "/default", ], ], ], "resize_keyboard" => true, "one_time_keyboard" => false, "is_persistent" => true, ]; } public function mainKeyboard(): array { return [ "keyboard" => [ [ [ "text" => "Примерка образов 👚👗", "callback_data" => "/first_tryon", ], ], [ [ "text" => "Фотосессии 📷", "web_app" => [ "url" => Yii::app()->params["basePath"] . "public/sessions?user_id=" . $this->user->id, ], ], [ "text" => "Фото по описанию", "callback_data" => "/custom_prompt", ], ], [ [ "text" => "Помощь ℹ️", "callback_data" => "/help", ], [ "text" => "Сменить фото 👤", "callback_data" => "/change_photo", ], ], ], "resize_keyboard" => true, "one_time_keyboard" => false, "is_persistent" => true, ]; } } ``` ===== FILE: apps/kadrum/protected/controllers/ApiController.php ===== ``` ['*']], ['deny', 'users' => ['?']], ]; } /** * Telegram webhook * Только: read input -> parse -> dedup -> resolve user -> route -> respond */ public function actionHook() { $json = file_get_contents("php://input"); // Если хочешь сохранять сырые апдейты — лучше Yii::log, но можно и файл. file_put_contents("text.txt", $json."\n", FILE_APPEND); try { $parser = new UpdateParser(); $dto = $parser->parse($json); // var_dump($dto); // пустые/непонятные апдейты — просто 200 if (empty($dto->telegramUserId)) { return $this->ok(); } // защита от повторной доставки апдейта $dedup = new UpdateDeduplicator(); if (!$dedup->allow($dto)) { // можно логировать, но без спама // Yii::log("Duplicate update_id={$dto->updateId}", CLogger::LEVEL_INFO, 'bot.webhook'); return $this->ok(); } // загрузка/создание пользователя $userResolver = new UserResolver(); $user = $userResolver->resolve($dto); // Telegram API gateway $tg = new TelegramGateway($user->telegram_id); // Сюда прокинь currency/params при желании $router = new BotRouter([ new BackHandler(), new PhotoUploadHandler(), new BuyPlanHandler(), new ChangePhotoHandler(), new CustomPromptHandler(), new WaitingPromptHandler(), new PhotoActionsHandler(), new ReplyImageEditHandler(), new RandomSessionHandler(), new BalanceHandler(), new CancelSubscriptionHandler(), new ConfirmCancelSubscriptionHandler(), new ChoosePlanHandler(), new ChoosePackageHandler(), new BuyPackageHandler(), new InviteHandler(), new TryOnConfirmHandler(), new PoliticsHandler(), new OfertaHandler(), new SupportHandler(), new HelpHandler(), new StartHandler(), new SessionGenerateHandler(), ]); $response = $router->handle($dto, $user); if ($response) { $tg->send($response); } return $this->ok(); } catch (Throwable $e) { Yii::log( "Hook error: ".$e->getMessage()."\n".$e->getTraceAsString(), CLogger::LEVEL_ERROR, 'bot.webhook' ); // важно: телеге почти всегда отвечаем 200, чтобы не долбила ретраями return $this->ok(); } } /** * Callback от Kie (или другого провайдера) * Только: input -> handler */ public function actionKieHook() { $json = file_get_contents("php://input"); try { $handler = new AiCallbackHandler(); // ты сделаешь его в protected/components/services $handler->handleKie($json); return $this->ok(); } catch (Throwable $e) { Yii::log( "KieHook error: ".$e->getMessage()."\n".$e->getTraceAsString(), CLogger::LEVEL_ERROR, 'bot.kiehook' ); // провайдеры тоже любят ретраить — чаще лучше вернуть 200 return $this->ok(); } } /** * Cron: обработка очереди Task */ public function actionCron() { try { // параметры можно брать из GET или конфига $limit = (int)($_GET['limit'] ?? 10); $worker = new TaskWorker(Yii::app()->controller); $worker->run($limit, [ 'userIds' => [2], 'status' => Task::STATUS_NEW, ]); echo "ok\n"; Yii::app()->end(); } catch (Throwable $e) { Yii::log( "Cron error: ".$e->getMessage()."\n".$e->getTraceAsString(), CLogger::LEVEL_ERROR, 'bot.cron' ); http_response_code(500); echo "error\n"; Yii::app()->end(); } } public function actionTryon() { $img = trim((string)($_GET['img'] ?? '')); $name = trim((string)($_GET['name'] ?? '')); $back = trim((string)($_GET['back'] ?? '')); if ($img === '' || !filter_var($img, FILTER_VALIDATE_URL)) { header('Content-Type: text/plain; charset=utf-8'); echo "Bad request: img is required and must be a valid URL"; Yii::app()->end(); } if ($back !== '' && !filter_var($back, FILTER_VALIDATE_URL)) { $back = ''; } $row = TryOnDeepLinkStorageDb::getOrCreate( ['img' => $img, 'name' => $name, 'back' => $back], null // 30 * 24 * 3600 // TTL 30 дней (можешь сделать null, тогда без TTL) ); $botLink = rtrim((string)Yii::app()->params['botLink'], '/'); // есть в params.php :contentReference[oaicite:2]{index=2} $deeplink = $botLink . '?start=tryon_' . $row->token; $this->redirect($deeplink); Yii::app()->end(); } /** * Тесты / диагностика — лучше вынести в отдельный контроллер или закрыть по IP */ public function actionTest() { // echo "
";
// $user = User::model()->findByPk(1);
// $planKb = PlanVersion::model()->getKeyboard(Yii::app()->controller->currency);
// var_dump($planKb);
}
public function actionGeneratePromptsPhoto(){
if( $prompts = Prompt::model()->findAll("person_type_id IS NOT NULL AND person_type_id != 0") ){
// if( $prompts = Prompt::model()->findAll("person_type_id IS NOT NULL AND person_type_id != 0") ){
foreach ($prompts as $key => $prompt) {
// code...
$task = new Task();
$task->user_id = 2;
$task->input_photo = $prompt->personType->photo;
$task->prompt = $prompt->prompt;
$task->session_id = $prompt->id;
if( !$task->save(false) ){
var_dump($task->getErrors());
exit;
}
}
}
}
/**
* Единая точка для ответа 200
*/
private function ok()
{
http_response_code(200);
echo "OK";
Yii::app()->end();
}
}
```
===== FILE: apps/kadrum/protected/data/auth.php =====
```
array (
'type' => 0,
'description' => 'Просмотр пользователей',
'bizRule' => NULL,
'data' => NULL,
),
'updateAdmin' =>
array (
'type' => 0,
'description' => 'Создание/изменение/удаление пользователей',
'bizRule' => NULL,
'data' => NULL,
),
'readTracking' =>
array (
'type' => 0,
'description' => 'Просмотр поставок',
'bizRule' => NULL,
'data' => NULL,
),
'updateTracking' =>
array (
'type' => 0,
'description' => 'Создание/изменение поставок',
'bizRule' => NULL,
'data' => NULL,
),
'readRefer' =>
array (
'type' => 0,
'description' => 'Просмотр партнеров',
'bizRule' => NULL,
'data' => NULL,
),
'updateRefer' =>
array (
'type' => 0,
'description' => 'Создание/изменение партнеров',
'bizRule' => NULL,
'data' => NULL,
),
'readStat' =>
array (
'type' => 0,
'description' => 'Просмотр аналитики',
'bizRule' => NULL,
'data' => NULL,
),
'updateStat' =>
array (
'type' => 0,
'description' => 'Создание/изменение аналитики',
'bizRule' => NULL,
'data' => NULL,
),
'readOpt' =>
array (
'type' => 0,
'description' => 'Просмотр оптовых заказов',
'bizRule' => NULL,
'data' => NULL,
),
'updateOpt' =>
array (
'type' => 0,
'description' => 'Создание/изменение оптовых заказов',
'bizRule' => NULL,
'data' => NULL,
),
'readDictionary' =>
array (
'type' => 0,
'description' => 'Просмотр справочников',
'bizRule' => NULL,
'data' => NULL,
),
'updateDictionary' =>
array (
'type' => 0,
'description' => 'Создание/изменение справочников',
'bizRule' => NULL,
'data' => NULL,
),
'accessBreed' =>
array (
'type' => 0,
'description' => 'Доступ к породам',
'bizRule' => NULL,
'data' => NULL,
),
'accessTk' =>
array (
'type' => 0,
'description' => 'Доступ к ТК',
'bizRule' => NULL,
'data' => NULL,
),
'accessSupplier' =>
array (
'type' => 0,
'description' => 'Доступ к поставщикам',
'bizRule' => NULL,
'data' => NULL,
),
'accessCity' =>
array (
'type' => 0,
'description' => 'Доступ к городам',
'bizRule' => NULL,
'data' => NULL,
),
'readOrder' =>
array (
'type' => 0,
'description' => 'Просмотр заказов',
'bizRule' => NULL,
'data' => NULL,
),
'readSumOrder' =>
array (
'type' => 0,
'description' => 'Просмотр суммы заказов',
'bizRule' => NULL,
'data' => NULL,
),
'updateOrder' =>
array (
'type' => 0,
'description' => 'Создание/изменение заказов',
'bizRule' => NULL,
'data' => NULL,
),
'deleteOrder' =>
array (
'type' => 0,
'description' => 'Удаление заказов',
'bizRule' => NULL,
'data' => NULL,
),
'changeStatusOrder' =>
array (
'type' => 0,
'description' => 'Изменения статуса заказа',
'bizRule' => NULL,
'data' => NULL,
),
'markPlatesOrder' =>
array (
'type' => 0,
'description' => 'Массово помечать таблички готовностью',
'bizRule' => NULL,
'data' => NULL,
),
'readSalary' =>
array (
'type' => 0,
'description' => 'Просмотр зарплаты',
'bizRule' => NULL,
'data' => NULL,
),
'updateSalary' =>
array (
'type' => 0,
'description' => 'Редактирование зарплаты',
'bizRule' => NULL,
'data' => NULL,
),
'updateSettings' =>
array (
'type' => 0,
'description' => 'Изменение настроек',
'bizRule' => NULL,
'data' => NULL,
),
'master' =>
array (
'type' => 2,
'description' => '',
'bizRule' => NULL,
'data' => NULL,
'children' =>
array (
0 => 'readOrder',
1 => 'accessBreed',
2 => 'readDictionary',
),
),
'sew' =>
array (
'type' => 2,
'description' => '',
'bizRule' => NULL,
'data' => NULL,
'children' =>
array (
0 => 'master',
),
),
'packer' =>
array (
'type' => 2,
'description' => '',
'bizRule' => NULL,
'data' => NULL,
'children' =>
array (
0 => 'master',
1 => 'updateOrder',
2 => 'readTracking',
3 => 'updateTracking',
4 => 'readSumOrder',
5 => 'changeStatusOrder',
6 => 'markPlatesOrder',
),
),
'salesman' =>
array (
'type' => 2,
'description' => '',
'bizRule' => NULL,
'data' => NULL,
'children' =>
array (
0 => 'packer',
1 => 'updateOrder',
2 => 'deleteOrder',
3 => 'updateDictionary',
4 => 'accessCity',
),
),
'supervisor' =>
array (
'type' => 2,
'description' => '',
'bizRule' => NULL,
'data' => NULL,
'children' =>
array (
0 => 'salesman',
1 => 'readAdmin',
2 => 'updateAdmin',
3 => 'readTracking',
4 => 'updateTracking',
5 => 'readStat',
6 => 'readDictionary',
7 => 'accessTk',
8 => 'accessSupplier',
9 => 'markPlatesOrder',
10 => 'readSalary',
11 => 'readRefer',
12 => 'updateRefer',
13 => 'readOpt',
14 => 'updateOpt',
),
),
'root' =>
array (
'type' => 2,
'description' => '',
'bizRule' => NULL,
'data' => NULL,
'children' =>
array (
0 => 'supervisor',
1 => 'updateStat',
2 => 'updateSalary',
3 => 'updateSettings',
),
'assignments' =>
array (
1 =>
array (
'bizRule' => NULL,
'data' => NULL,
),
3 =>
array (
'bizRule' => NULL,
'data' => NULL,
),
),
),
);
```
===== FILE: apps/kadrum/protected/data/dbgen.php =====
```
exec($sql);
}
```
===== FILE: apps/kadrum/protected/views/public/terms-of-use.php =====
```
$version = 1;
?>
One Button VPN - ваш ключ к защищенному интернету!
Условия использования приложения One Button VPN
Настоящие Условия регулируют использование данного Приложения и любого другого связанного с ним Соглашения или юридических отношений с Владельцем юридически обязательным образом. Слова, написанные с заглавной буквы, определены в соответствующем специальном разделе этого документа.
Пользователь обязан внимательно прочитать настоящий документ.
Хотя все договорные отношения, относящиеся к этим Продуктам, заключаются исключительно Владельцем и Пользователями, Пользователи признают и соглашаются, что, если это Приложение было предоставлено им через Apple App Store, Apple может обеспечить соблюдение настоящих Условий в качестве стороннего бенефициара. .
Это приложение предоставлено:
ИП Китаев Михаил Андреевич. ИНН/ОГРНИП: 421189830262/315422300007697
Контактный адрес электронной почты владельца: hi@onebttn.com
Что должен знать пользователь с первого взгляда
Обратите внимание, что некоторые положения настоящих Условий могут применяться только к определенным категориям Пользователей. В частности, некоторые положения могут применяться только к Потребителям или к тем Пользователям, которые не квалифицируются как Потребители. Такие ограничения всегда прямо упоминаются в каждом затронутом пункте. При отсутствии такого упоминания положения применяются ко всем Пользователям.
Право на отказ распространяется только на европейских потребителей.
Это Приложение использует автоматическое продление подписок на Продукты. Информацию о а) периоде продления, б) деталях прекращения действия и в) уведомлении о прекращении действия можно найти в соответствующем разделе настоящих Условий.
Важно: к потребителям, проживающим в Германии, применяются другие правила, описанные в соответствующем разделе настоящих Условий.
УСЛОВИЯ ЭКСПЛУАТАЦИИ
Если не указано иное, условия использования, подробно описанные в этом разделе, применяются в целом при использовании данного Приложения.
В определенных сценариях могут применяться единые или дополнительные условия использования или доступа, которые в таких случаях дополнительно указываются в настоящем документе.
Используя данное Приложение, Пользователи подтверждают соответствие следующим требованиям:
- Для Пользователей не существует ограничений в отношении статуса Потребителей или Бизнес-пользователей;
- Пользователи не находятся в стране, на которую наложено эмбарго правительства США или которая была определена правительством США как страна, «поддерживающая терроризм»;
- Пользователи не включены ни в один список запрещенных или ограниченных лиц правительства США;
Содержание этого приложения
Если иное не указано или явно не указано, весь контент, доступный в этом Приложении, принадлежит или предоставляется Владельцу или его лицензиарам.
Владелец прилагает все усилия, чтобы гарантировать, что контент, представленный в этом Приложении, не нарушает никакие применимые законодательные положения или права третьих лиц. Однако не всегда возможно добиться такого результата.
В таких случаях, без ущерба для каких-либо юридических прерогатив Пользователей по обеспечению соблюдения своих прав, Пользователей просят сообщать о соответствующих жалобах, используя контактную информацию, указанную в этом документе.
Права на содержимое этого приложения. Все права защищены.
Владелец владеет и сохраняет за собой все права интеллектуальной собственности на любой такой контент.
Таким образом, пользователи не могут использовать такой контент любым способом, который не является необходимым или не подразумевается для надлежащего использования Сервиса. В частности, помимо прочего, Пользователи не имеют права копировать, загружать, делиться (за пределами ограничений, указанных ниже), изменять, переводить, трансформировать, публиковать, передавать, продавать, сублицензировать, редактировать, передавать/переуступать третьим лицам или создавать производные работы. из контента, доступного в этом Приложении, и не позволять третьим лицам делать это через Пользователя или его устройство, даже без ведома Пользователя.
Если это явно указано в этом Приложении, Пользователь может загружать, копировать и/или делиться некоторым контентом, доступным через это Приложение, для своего исключительно личного и некоммерческого использования и при условии, что авторские права и все другие атрибуты, запрошенные Владельцем, реализованы правильно. .
Любые применимые законодательные ограничения или исключения из авторских прав остаются в силе.
Удаление контента из частей этого Приложения, доступных через App Store.
Если сообщенный контент будет сочтен нежелательным, он будет удален в течение 24 часов, а Пользователю, предоставившему такой контент, будет запрещено использовать Сервис.
Доступ к внешним ресурсам
Посредством этого Приложения Пользователи могут иметь доступ к внешним ресурсам, предоставленным третьими лицами. Пользователи признают и соглашаются с тем, что Владелец не контролирует такие ресурсы и, следовательно, не несет ответственности за их содержание и доступность.
Условия, применимые к любым ресурсам, предоставляемым третьими лицами, включая условия, применимые к любому возможному предоставлению прав на контент, вытекают из условий каждой такой третьей стороны или, при отсутствии таковых, из применимого статутного права.
Допустимое использование
Данное Приложение и Сервис можно использовать только в пределах того, для чего они предусмотрены, в соответствии с настоящими Условиями и применимым законодательством.
Пользователи несут единоличную ответственность за то, чтобы использование ими данного Приложения и/или Сервиса не нарушало применимые законы, правила или права третьих лиц.
Таким образом, Владелец оставляет за собой право принять любые соответствующие меры для защиты своих законных интересов, в том числе путем отказа Пользователям в доступе к этому Приложению или Сервису, расторжения контрактов, сообщения о любых неправомерных действиях, совершенных через это Приложение или Сервис, компетентным органам, например, судебным органам. или административные органы - всякий раз, когда Пользователи участвуют или подозреваются в участии в любой из следующих действий:
- Нарушать законы, правила и/или настоящие Условия;
- Нарушать любые права третьих лиц;
- Значительно ущемлять законные интересы Собственника;
- Оскорбить Владельца или любую третью сторону.
УСЛОВИЯ ПРОДАЖИ
Предоставление персональных данных
Для доступа или получения некоторых Продуктов, предоставляемых через это Приложение в рамках Сервиса, Пользователям может потребоваться предоставить свои личные данные, указанные в этом Приложении.
Платные продукты
Некоторые Продукты, представленные в настоящем Приложении, в рамках Сервиса предоставляются на платной основе.
Платы, продолжительность и условия, применимые к покупке таких Продуктов, описаны ниже и в соответствующих разделах настоящего Приложения.
Описание продукта
Цены, описания или наличие Продуктов указаны в соответствующих разделах настоящего Приложения и могут быть изменены без предварительного уведомления.
Хотя Продукты в этом Приложении представлены с максимально возможной с технической точки зрения точностью, представление в этом Приложении любыми средствами (включая, в зависимости от обстоятельств, графические материалы, изображения, цвета, звуки) предназначено только для справки и не подразумевает никаких гарантий в отношении характеристики приобретаемого Товара.
Характеристики выбранного Товара будут указаны в процессе покупки.
Процесс покупки
Любые шаги, предпринятые от выбора Продукта до подачи заказа, являются частью процесса покупки.
Процесс покупки включает в себя следующие этапы:
- Пользователи должны выбрать желаемый Продукт и подтвердить выбор покупки.
- После просмотра информации, отображаемой при выборе покупки, Пользователи могут разместить заказ, отправив его.
Подача заказа
Когда Пользователь отправляет заказ, применяется следующее:
- Отправка заказа определяет заключение договора и, следовательно, создает для Пользователя обязательство оплатить цену, налоги и возможные дополнительные сборы и расходы, как указано на странице заказа.
- В случае, если приобретенный Продукт требует от Пользователя действия, такого как предоставление личной информации или данных, спецификаций или особых пожеланий, подача заказа создает для Пользователя обязательство сотрудничать соответствующим образом.
- После отправки заказа Пользователи получат квитанцию, подтверждающую получение заказа.
- Все уведомления, связанные с описанным процессом покупки, направляются на адрес электронной почты, указанный Пользователем для этих целей.
Цены
Во время процесса покупки и перед отправкой заказа пользователи информируются о любых сборах, налогах и расходах (включая, если таковые имеются, расходы на доставку), которые с них будут взиматься.
Цены в этом приложении отображаются:
- Либо без учета, либо с учетом любых применимых сборов, налогов и расходов, в зависимости от раздела, который просматривает Пользователь.
Способы оплаты
Информация о принятых способах оплаты предоставляется в процессе покупки.
Некоторые способы оплаты могут быть доступны только при условии соблюдения дополнительных условий или комиссий. В таких случаях соответствующую информацию можно найти в специальном разделе настоящего Приложения.
Все платежи обрабатываются самостоятельно через сторонние сервисы. Таким образом, это приложение не собирает никакой платежной информации, например данных кредитной карты, а получает уведомление только после успешного завершения платежа. Пользователь может ознакомиться с политикой конфиденциальности данного Приложения, чтобы узнать больше об обработке данных и правах Пользователей в отношении их данных.
Если оплата с использованием доступных методов не удалась или была отклонена поставщиком платежных услуг, Владелец не обязан выполнять заказ на покупку. Если платеж не выполнен или отклонен, Владелец оставляет за собой право требовать от Пользователя любых связанных с этим расходов или убытков.
Сохранение права собственности на продукт
До тех пор, пока Владелец не получит оплату общей стоимости покупки, любые заказанные Продукты не станут собственностью Пользователя.
Сохранение прав использования
Пользователи не приобретают никаких прав на использование приобретенного Продукта до тех пор, пока Владелец не получит полную стоимость покупки.
Доставка
Доставка цифрового контента
Если не указано иное, цифровой контент, приобретенный в этом Приложении, доставляется путем загрузки на устройство(а), выбранное Пользователями.
Пользователи признают и принимают, что для загрузки и/или использования Продукта предполагаемое устройство(а) и соответствующее программное обеспечение (включая операционные системы) должны быть легальными, широко используемыми, актуальными и соответствовать текущим рыночным требованиям. стандарты.
Пользователи признают и принимают, что возможность загрузки приобретенного Продукта может быть ограничена во времени и пространстве.
Срок действия контракта
Подписки
Подписки позволяют Пользователям получать Продукт непрерывно или регулярно с течением времени. Подробная информация о типе подписки и ее прекращении приведена ниже.
Подписки с фиксированным сроком
Платные подписки с фиксированным сроком начинаются в день получения оплаты Владельцем и действуют в течение периода подписки, выбранного Пользователем или иным образом указанного в процессе покупки.
По истечении срока подписки Продукт больше не будет доступен.
Подписки обрабатываются через Apple ID
Пользователи могут подписаться на Продукт, используя Apple ID, связанный с их учетной записью Apple App Store, выполнив соответствующий процесс в этом Приложении. При этом Пользователи признают и принимают, что:
- любой причитающийся платеж будет снят с их учетной записи Apple ID;
- подписки автоматически продлеваются на тот же срок, если только Пользователь не отменит их как минимум за 24 часа до истечения текущего периода;
- любые сборы или платежи, причитающиеся за продление, будут сняты в течение 24 часов до окончания текущего периода;
- подписками можно управлять или отменять их в настройках учетной записи пользователя в Apple App Store.
Вышеизложенное имеет преимущественную силу перед любыми противоречащими или отличающимися положениями настоящих Условий.
Автоматическое продление подписок на определенный срок
Подписки автоматически продлеваются с помощью способа оплаты, который Пользователь выбрал при покупке.
Продленная подписка будет действовать в течение периода, равного первоначальному сроку.
Пользователь заблаговременно получит напоминание о предстоящем продлении с описанием процедуры, которую необходимо выполнить для отмены автоматического продления.
Прекращение действия
Подписку можно прекратить, отправив Владельцу четкое и недвусмысленное уведомление о прекращении с использованием контактной информации, указанной в этом документе, или — если применимо — с помощью соответствующих элементов управления в этом Приложении.
Если уведомление о прекращении получено Владельцем до продления подписки, прекращение вступит в силу, как только текущий период будет завершен.
Исключение для потребителей, находящихся в Германии
Однако, независимо от вышеизложенного, если Пользователь проживает в Германии и квалифицируется как Потребитель, применяется следующее:
- По окончании первоначального срока подписки автоматически продлеваются на неограниченный период, если только Пользователь не прекращает действие подписки до окончания такого срока.
- Плата, причитающаяся при продлении, будет взиматься с помощью метода оплаты, который Пользователь выбрал при покупке.
- После продления подписка будет действовать на неопределенный срок и может быть прекращена ежемесячно.
- Пользователь заблаговременно получит напоминание о предстоящем неограниченном продлении с описанием процедуры, которой необходимо следовать, чтобы предотвратить продление или прекратить подписку в дальнейшем.
Прекращение действия
Расширенная подписка может быть прекращена в любое время путем направления Владельцу четкого и недвусмысленного уведомления о прекращении с использованием контактной информации, указанной в этом документе, или — если применимо — с помощью соответствующих элементов управления в этом Приложении.
Если уведомление о прекращении получено Владельцем до конца текущего месяца, срок действия подписки истекает в конце такого месяца.
Права пользователя
Право на отзыв
Если не применяются исключения, Пользователь может иметь право отказаться от договора в течение периода, указанного ниже (обычно 14 дней), по любой причине и без обоснования. Пользователи могут узнать больше об условиях вывода средств в этом разделе.
На кого распространяется право отказа
Если ниже не указано какое-либо применимое исключение, Пользователям, являющимся европейскими потребителями, предоставляется законное право выхода в соответствии с правилами ЕС, позволяющее отказаться от договоров, заключенных онлайн (дистанционные договоры), в течение указанного периода, применимого к их случаю, по любой причине и без обоснования.
Пользователи, не соответствующие этому требованию, не могут воспользоваться правами, описанными в этом разделе. Потребитель несет ответственность перед Продавцом только за любое уменьшение стоимости товара, возникшее в результате обращения с товаром способом, отличным от того, который необходим для ознакомления его с характером, характеристиками и функциональностью товара.
Осуществление права отказа
Чтобы воспользоваться своим правом на выход, Пользователи должны направить Владельцу недвусмысленное заявление о своем намерении выйти из договора.
С этой целью Пользователи могут использовать форму отзыва модели, доступную в разделе «Определения» настоящего документа. Однако пользователи могут свободно выразить свое намерение выйти из договора, сделав недвусмысленное заявление любым другим подходящим способом. Чтобы уложиться в срок, в течение которого они могут воспользоваться таким правом, Пользователи должны отправить уведомление о выходе до истечения периода вывода.
Когда истекает срок вывода средств?
В случае покупки цифрового контента, не представленного на материальном носителе, период вывода средств истекает через 14 дней со дня заключения договора, если только Пользователь не отказался от права на вывод средств.
Последствия отмены
Пользователям, которые правильно отказываются от договора, Владелец возмещает все платежи, произведенные Владельцу, включая, если таковые имеются, расходы на доставку.
Однако любые дополнительные расходы, возникшие в результате выбора конкретного способа доставки, кроме самого дешевого типа стандартной доставки, предлагаемого Владельцем, не возмещаются.
Такое возмещение должно быть произведено без неоправданной задержки и в любом случае не позднее 14 дней со дня уведомления Владельца о решении Пользователя выйти из договора. Если иное не согласовано с Пользователем, возмещение будет производиться с использованием тех же средств платежа, которые использовались для обработки первоначальной транзакции. В любом случае Пользователь не несет никаких расходов или сборов в результате такого возмещения.
Права пользователя в Великобритании
Право на отмену
Если не применяются исключения, Пользователи, являющиеся Потребителями в Соединенном Королевстве, имеют законное право на аннулирование в соответствии с законодательством Великобритании и могут иметь право отказаться от контрактов, заключенных онлайн (дистанционные контракты), в течение периода, указанного ниже (обычно 14 дней), по любой причине и без обоснования.
Пользователи, не являющиеся Потребителями, не могут воспользоваться правами, описанными в этом разделе. Пользователи могут узнать больше об условиях отмены в этом разделе.
Осуществление права на отмену
Чтобы воспользоваться своим правом на расторжение, Пользователи должны направить Владельцу недвусмысленное заявление о своем намерении выйти из договора. С этой целью Пользователи могут использовать форму отзыва модели, доступную в разделе «Определения» настоящего документа. Однако пользователи могут свободно выразить свое намерение выйти из договора, сделав недвусмысленное заявление любым другим подходящим способом. Чтобы уложиться в срок, в течение которого они могут воспользоваться таким правом, Пользователи должны отправить уведомление о выходе до истечения периода отмены.
Когда истекает срок отмены?
В случае приобретения цифрового контента, не представленного на материальном носителе, срок отмены истекает через 14 дней со дня заключения договора, если только Пользователь не отказался от права на расторжение.
Последствия отмены
Пользователям, которые правильно отказываются от договора, Владелец возмещает все платежи, произведенные Владельцу, включая, если таковые имеются, расходы на доставку.
Однако любые дополнительные расходы, возникшие в результате выбора конкретного способа доставки, кроме самого дешевого типа стандартной доставки, предлагаемого Владельцем, не возмещаются.
Такое возмещение должно быть произведено без неоправданной задержки и в любом случае не позднее 14 дней со дня уведомления Владельца о решении Пользователя выйти из договора. Если иное не согласовано с Пользователем, возмещение будет производиться с использованием тех же средств платежа, которые использовались для обработки первоначальной транзакции. В любом случае Пользователь не несет никаких расходов или сборов в результате такого возмещения.
Права пользователя в Бразилии
Право сожаления
Если ниже не указано применимое исключение, Пользователи, являющиеся Потребителями в Бразилии, имеют законное право на сожаление в соответствии с законодательством Бразилии. Это означает, что Потребитель имеет право отказаться от контрактов, заключенных онлайн (дистанционных контрактов или любого контракта, подписанного вне офиса) в течение семи (7) дней с даты заключения контракта или получения продукта или услуги, в течение семи (7) дней с даты заключения контракта или получения продукта или услуги, по любой причине и без обоснования. Пользователи, не являющиеся Потребителями, не могут воспользоваться правами, описанными в этом разделе. Право на сожаление может быть реализовано Потребителем через каналы связи, указанные в начале настоящего документа, и в соответствии с рекомендациями этого раздела.
Осуществление права сожаления
Чтобы воспользоваться своим правом сожаления, Пользователи должны направить Владельцу недвусмысленное заявление о своем намерении выйти из договора. С этой целью Пользователи могут использовать форму отзыва модели, доступную в разделе «Определения» настоящего документа. Однако пользователи могут свободно выразить свое намерение выйти из договора, сделав недвусмысленное заявление любым другим подходящим способом. Чтобы уложиться в срок, в течение которого они могут воспользоваться таким правом, Пользователи должны отправить уведомление о сожалении до истечения периода сожаления.
Когда истекает период сожаления?
В случае покупки цифрового контента период сожаления истекает через семь (7) дней со дня заключения договора и только в том случае, если цифровой контент еще не был предоставлен и интегрирован в устройство Потребителя.
Эффекты сожаления
Пользователям, которые правильно отказываются от договора, Владелец возмещает все платежи, произведенные Владельцу, включая, если таковые имеются, расходы на доставку.
Однако любые дополнительные расходы, возникшие в результате выбора конкретного способа доставки, кроме самого дешевого типа стандартной доставки, предлагаемого Владельцем, не возмещаются.
Такое возмещение должно быть произведено без неоправданной задержки и в любом случае не позднее 14 дней со дня, когда Владелец будет проинформирован о решении Пользователя отказаться от договора или фактическом возврате продукта, в зависимости от того, что произойдет позже. Если иное не согласовано с Пользователем, возмещение будет производиться с использованием тех же средств платежа, которые использовались для обработки первоначальной транзакции. В любом случае Пользователь не несет никаких расходов или сборов в результате такого возмещения.
Гарантии
Юридическая гарантия соответствия цифровых продуктов законодательству ЕС.
В соответствии с законодательством ЕС в течение как минимум 2 лет с момента поставки или, в случае непрерывной поставки Цифровых продуктов в течение более 2 лет, в течение всего периода поставки, торговцы гарантируют соответствие Цифровых продуктов, которые они предоставляют Потребителям.
Если Пользователи квалифицируются как Европейские Потребители, юридическая гарантия соответствия распространяется на Цифровые продукты, доступные в этом Приложении, в соответствии с законодательством страны их обычного проживания. Национальное законодательство такой страны может предоставлять Пользователям более широкие права.
Соответствие контракту для потребителей в Великобритании
Пользователи в Соединенном Королевстве, квалифицируемые как Потребители, имеют право на получение товаров, соответствующих договору.
Юридическая гарантия соответствия товаров для потребителей в Бразилии
Юридическая гарантия, применимая к товарам, продаваемым с помощью этого Приложения (как физическим, так и цифровым), соответствует следующим условиям в соответствии с Кодексом защиты прав потребителей:
- На товары недлительного пользования предоставляется тридцатидневная (30-дневная) гарантия; и
- На товары длительного пользования предоставляется гарантия сроком девяносто (90 дней).
Гарантийный срок начинается с момента поставки товара.
Гарантия не распространяется на случаи неправильного использования, стихийных бедствий или если устройство подвергалось любому обслуживанию, кроме предусмотренного настоящим Приложением.
Гарантию можно запросить через каналы связи, предусмотренные настоящим Приложением. Владелец несет расходы по доставке товара на техническую оценку, если это необходимо.
Владелец по своему усмотрению может также предложить договорную гарантию в дополнение к юридической гарантии. Правила, применимые к договорным гарантиям, можно найти в спецификациях настоящего Приложения. Если такая информация не предоставлена, применяются только законодательные положения.
Ответственность и возмещение ущерба
Если иное прямо не указано или не согласовано с Пользователями, ответственность Владельца за ущерб в связи с исполнением Соглашения исключается, ограничивается и/или снижается в максимальной степени, разрешенной применимым законодательством.
Возмещение ущерба
Пользователь соглашается возместить ущерб и оградить Владельца и его дочерние компании, аффилированные лица, должностных лиц, директоров, агентов, партнеров, партнеров и сотрудников от ответственности за любые претензии или требования ⁠ — включая, помимо прочего, гонорары и издержки адвоката ⁠ — предъявленные любой третьей стороной в связи или в связи с любым виновным нарушением настоящих Условий, прав третьих лиц или законодательных положений, связанных с использованием Сервиса Пользователем или его аффилированными лицами, должностными лицами, директорами, агентами, кобрендерами, партнерами и работников в пределах, разрешенных действующим законодательством.
Вышеупомянутое также применимо к любым претензиям третьих лиц (включая, помимо прочего, клиентов или заказчиков Владельца) против Владельца, связанных с Цифровыми продуктами, предоставленными Пользователем, например, к претензиям о соответствии.
Ограничение ответственности
Если иное прямо не указано и без ущерба для действующего законодательства, Пользователи не имеют права требовать возмещения убытков от Владельца (или любого физического или юридического лица, действующего от его имени).
Это не относится к ущербу жизни, здоровью или физической неприкосновенности, ущербу, возникшему в результате нарушения существенных договорных обязательств, например, любого обязательства, строго необходимого для достижения цели договора, и/или ущерба, возникшего в результате умысла или грубой небрежности, при условии, что поскольку данное Приложение использовалось Пользователем надлежащим и правильным образом.
Если ущерб не причинен умыслом или грубой неосторожностью или не затрагивает жизнь, здоровье или физическую неприкосновенность, Владелец несет ответственность только в размере типичного и предсказуемого ущерба на момент заключения договора.
Австралийские пользователи
Ограничение ответственности
Ничто в настоящих Условиях не исключает, не ограничивает и не изменяет какие-либо гарантии, условия, гарантии, права или средства правовой защиты, которые Пользователь может иметь в соответствии с Законом о конкуренции и защите прав потребителей 2010 г. (Cth) или любым аналогичным законодательством штата и территории и которые не могут быть исключены, ограничены или изменены. (неисключаемое право). В максимальной степени, разрешенной законом, наша ответственность перед Пользователем, включая ответственность за нарушение неисключаемых прав и ответственности, которая иным образом не исключена в соответствии с настоящими Условиями использования, ограничивается, по собственному усмотрению Владельца, повторным -оказание услуг или оплата стоимости повторного оказания услуг.
Пользователи из США
Отказ от гарантий
Данное Приложение предоставляется строго на условиях «как есть» и «по мере доступности». Использование Сервиса осуществляется на собственный риск Пользователя. В максимальной степени, разрешенной применимым законодательством, Владелец прямо отказывается от всех условий, заявлений и гарантий — явных, подразумеваемых, установленных законом или иных, включая, помимо прочего, любые подразумеваемые гарантии коммерческой ценности, пригодности для определенной цели или ненарушение прав третьих лиц. Никакие советы или информация, устные или письменные, полученные Пользователем от Владельца или через Сервис, не создают каких-либо гарантий, прямо не указанных в настоящем документе.
Не ограничивая вышесказанное, Владелец, его дочерние компании, аффилированные лица, лицензиары, должностные лица, директора, агенты, совместные бренды, партнеры, поставщики и сотрудники не гарантируют, что контент является точным, надежным или правильным; что Сервис будет соответствовать требованиям Пользователей; что Сервис будет доступен в любое конкретное время и в любом месте, бесперебойно и безопасно; что любые дефекты или ошибки будут исправлены; или что Сервис не содержит вирусов или других вредоносных компонентов. Любой контент, загруженный или иным образом полученный с помощью Сервиса, загружается на собственный риск Пользователей, и Пользователи несут единоличную ответственность за любой ущерб компьютерной системе или мобильному устройству Пользователей или потерю данных, возникшую в результате такой загрузки или использования Пользователями обслуживание.
Владелец не гарантирует, не одобряет, не гарантирует и не принимает на себя ответственность за какой-либо продукт или услугу, рекламируемую или предлагаемую третьей стороной через Сервис или любой веб-сайт или сервис, на который имеется гиперссылка, и Владелец не должен быть стороной или каким-либо образом контролировать любые транзакции между Пользователями и сторонними поставщиками продуктов или услуг.
Сервис может стать недоступным или может не работать должным образом в веб-браузере, мобильном устройстве и/или операционной системе Пользователя. Владелец не может нести ответственность за любой предполагаемый или фактический ущерб, возникший в результате содержания, работы или использования Сервиса.
Федеральное законодательство, законы некоторых штатов и других юрисдикций не допускают исключения и ограничения некоторых подразумеваемых гарантий. Вышеуказанные исключения могут не распространяться на Пользователей. Настоящее Соглашение предоставляет Пользователям определенные законные права, и Пользователи также могут иметь другие права, которые варьируются от штата к штату. Отказ от ответственности и исключения по настоящему соглашению не применяются в степени, запрещенной применимым законодательством.
Ограничения ответственности
В максимальной степени, разрешенной применимым законодательством, ни при каких обстоятельствах Владелец, а также его дочерние компании, филиалы, должностные лица, директора, агенты, со-брендеры, партнеры, поставщики и сотрудники не несут ответственности за:
- Любые косвенные, штрафные, случайные, особые, косвенные или штрафные убытки, включая, помимо прочего, ущерб от упущенной выгоды, деловой репутации, использования, данных или других нематериальных убытков, возникающих в результате или в связи с использованием или невозможностью использования Сервиса. ; и
- Любой ущерб, потери или травмы, возникшие в результате взлома, фальсификации или другого несанкционированного доступа или использования Сервиса или учетной записи Пользователя или содержащейся в них информации;
- Любые ошибки, ошибки или неточности содержания;
- Травмы или материальный ущерб любого характера, возникшие в результате доступа Пользователя к Сервису или его использования;
- Любой несанкционированный доступ или использование защищенных серверов Владельца и/или любой личной информации, хранящейся на них;
- Любое прерывание или прекращение передачи данных в Службу или из нее;
- Любые ошибки, вирусы, трояны и т.п., которые могут передаваться на Сервис или через него;
- Любые ошибки или упущения в любом контенте, а также любые убытки или ущерб, понесенные в результате использования любого контента, опубликованного, отправленного по электронной почте, переданного или иным образом предоставленного через Сервис; и/или
- Клеветническое, оскорбительное или незаконное поведение любого Пользователя или третьей стороны.
Ни при каких обстоятельствах Владелец, а также его дочерние компании, филиалы, должностные лица, директора, агенты, кобрендинговые компании, партнеры, поставщики и сотрудники не несут ответственности за любые претензии, судебные разбирательства, обязательства, обязательства, ущерб, убытки или расходы в сумме, превышающей сумма, выплаченная Пользователем Владельцу по настоящему Соглашению за предыдущие 12 месяцев или в течение срока действия настоящего соглашения между Владельцем и Пользователем, в зависимости от того, что короче.
Этот раздел ограничения ответственности применяется в максимальной степени, разрешенной законодательством применимой юрисдикции, независимо от того, основана ли предполагаемая ответственность на договоре, правонарушении, небрежности, строгой ответственности или любом другом основании, даже если Пользователь был уведомлен о возможности такой ущерб.
В некоторых юрисдикциях не допускается исключение или ограничение ответственности за случайный или косвенный ущерб, поэтому вышеуказанные ограничения или исключения могут не распространяться на Пользователя. Условия предоставляют Пользователю определенные законные права, и Пользователь также может иметь другие права, которые варьируются от юрисдикции к юрисдикции. Отказ от ответственности, исключения и ограничения ответственности в соответствии с условиями не применяются в степени, запрещенной применимым законодательством.
Возмещение ущерба
Пользователь соглашается защищать, возмещать убытки и ограждать Владельца и его дочерние компании, аффилированные лица, должностных лиц, директоров, агентов, со-брендеров, партнеров, поставщиков и сотрудников от любых и всех претензий или требований, убытков, обязательств, убытков, ответственности. , издержки или долги, а также расходы, включая, помимо прочего, судебные издержки и расходы, возникающие в результате:
- Использование Пользователем Сервиса и доступ к нему, включая любые данные или контент, передаваемые или получаемые Пользователем;
- Нарушение Пользователем настоящих условий, включая, помимо прочего, нарушение Пользователем любых заявлений и гарантий, изложенных в настоящих условиях;
- Нарушение Пользователем любых прав третьих лиц, включая, помимо прочего, любое право на неприкосновенность частной жизни или права интеллектуальной собственности;
- Нарушение Пользователем любого статутного закона, правила или положения;
- Любой контент, отправленный из учетной записи Пользователя, включая доступ третьих лиц с использованием уникального имени пользователя, пароля или других мер безопасности Пользователя, если применимо, включая, помимо прочего, вводящую в заблуждение, ложную или неточную информацию;
- Умышленные неправомерные действия Пользователя; или
- Законодательные положения Пользователя или его аффилированных лиц, должностных лиц, директоров, агентов, совместных брендов, партнеров, поставщиков и сотрудников в степени, разрешенной применимым законодательством.
Общие положения
Без отказа
Неспособность Владельца отстоять какое-либо право или положение в соответствии с настоящими Условиями не является отказом от любого такого права или положения. Никакой отказ не может считаться дальнейшим или продолжающимся отказом от такого условия или любого другого условия.
Прерывание обслуживания
Чтобы обеспечить наилучший уровень обслуживания, Владелец оставляет за собой право прервать предоставление Сервиса для проведения технического обслуживания, обновления системы или любых других изменений, сообщив об этом Пользователям соответствующим образом.
В рамках закона Владелец также может принять решение о приостановке или полном прекращении предоставления Услуги. Если предоставление Услуги будет прекращено, Владелец будет сотрудничать с Пользователями, чтобы дать им возможность отозвать Персональные данные или информацию, и будет уважать права Пользователей, связанные с продолжением использования продукта и/или компенсацией, как это предусмотрено применимым законодательством.
Кроме того, Услуга может быть недоступна по причинам, находящимся вне разумного контроля Владельца, например, в случае «форс-мажорных» обстоятельств (например, сбоев в инфраструктуре или отключений электроэнергии).
Переуступка контракта
Владелец оставляет за собой право передавать, переуступать, распоряжаться путем новации или передавать в субподряд любые или все права или обязательства по настоящим Условиям, принимая во внимание законные интересы Пользователя. Положения, касающиеся изменений настоящих Условий, будут применяться соответствующим образом.
Пользователи не могут каким-либо образом переуступать или передавать свои права или обязанности по настоящим Условиям без письменного разрешения Владельца.
Контакты
Все сообщения, касающиеся использования данного Приложения, должны отправляться с использованием контактной информации, указанной в этом документе.
Делимость
Если какое-либо положение настоящих Условий будет признано или станет недействительным или не имеющим исковой силы в соответствии с применимым законодательством, недействительность или невозможность исковой силы такого положения не повлияет на действительность остальных положений, которые останутся в полной силе и действии.
Пользователи из США: Любое такое недействительное или не имеющее исковой силы положение будет интерпретироваться, истолковываться и изменяться в той степени, в которой это разумно необходимо, чтобы сделать его действительным, исполнимым и соответствующим его первоначальному замыслу. Настоящие Условия представляют собой полное Соглашение между Пользователями и Владельцем в отношении предмета настоящего Соглашения и заменяют все другие сообщения, включая, помимо прочего, все предыдущие соглашения между сторонами в отношении такого предмета. Настоящие Условия будут соблюдаться в полной мере, разрешенной законом.
Пользователи из ЕС: Если какое-либо положение настоящих Условий будет или будет считаться недействительным, недействительным или не имеющим исковой силы, стороны должны сделать все возможное, чтобы мирным путем прийти к соглашению о действительных и исполнимых положениях, тем самым заменив недействительные, недействительные или неисполнимые части. В случае невыполнения этого требования недействительные, недействительные или неисполнимые положения заменяются применимыми законодательными положениями, если это разрешено или указано в соответствии с применимым законодательством. Без ущерба для вышеизложенного недействительность, недействительность или невозможность обеспечить соблюдение конкретного положения настоящих Условий не аннулирует все Соглашение, за исключением случаев, когда отделенные положения являются существенными для Соглашения или настолько важными, что стороны не заключили бы соглашение. контракта, если бы они знали, что это положение не будет действительным, или в случаях, когда остальные положения приведут к неприемлемым трудностям для любой из сторон.
Применимое право
Настоящие Условия регулируются законодательством места нахождения Владельца, как указано в соответствующем разделе настоящего документа, без учета принципов коллизионного права.
Преимущественная сила национального законодательства: однако, независимо от вышеизложенного, если законодательство страны, в которой находится Пользователь, предусматривает более высокие применимые стандарты защиты потребителей, такие более высокие стандарты имеют преимущественную силу.
Исключение для потребителей в Швейцарии: если Пользователь квалифицируется как Потребитель в Швейцарии, будет применяться швейцарское законодательство.
Исключение для потребителей в Бразилии: если Пользователь квалифицируется как Потребитель в Бразилии, а продукт и/или услуга продаются в Бразилии, применяется законодательство Бразилии.
Место юрисдикции
Исключительная компетенция принимать решения по любым разногласиям, возникающим из настоящих Условий или связанным с ними, принадлежит судам по месту нахождения Владельца, как показано в соответствующем разделе настоящего документа.
Исключение для потребителей в Европе: вышеизложенное не применяется ни к Пользователям, которые квалифицируются как Европейские Потребители, ни к Потребителям, проживающим в Великобритании, Швейцарии, Норвегии или Исландии.
Исключение для потребителей в Бразилии
Вышеупомянутое не относится к Пользователям в Бразилии, которые квалифицируются как Потребители.
Разрешение спора
Дружеское разрешение споров
Пользователи могут обращаться с любыми спорами к Владельцу, который постарается разрешить их мирным путем.
Хотя право Пользователей подавать судебные иски всегда остается неизменным, в случае возникновения каких-либо разногласий относительно использования этого Приложения или Сервиса Пользователям предлагается связаться с Владельцем по контактным данным, указанным в этом документе.
Пользователь может подать жалобу, включая краткое описание и, если применимо, подробную информацию о соответствующем заказе, покупке или учетной записи, на адрес электронной почты Владельца, указанный в этом документе.
Владелец обработает жалобу без неоправданной задержки и в течение 3 дней с момента ее получения.
Разрешение споров онлайн для потребителей
Европейская комиссия создала онлайн-платформу для альтернативного разрешения споров, которая облегчает внесудебный метод разрешения споров, связанных и вытекающих из онлайн-контрактов купли-продажи и оказания услуг.
В результате любой европейский потребитель или потребитель, базирующийся в Норвегии, Исландии или Лихтенштейне, может использовать такую ​​платформу для разрешения споров, вытекающих из договоров, заключенных онлайн. Платформа доступна по следующей ссылке.
Определения и юридические ссылки
Данное Приложение (или данное Приложение) — свойство, позволяющее предоставлять Сервис.
Соглашение — любые юридически обязательные или договорные отношения между Владельцем и Пользователем, регулируемые настоящими Условиями.
Бразилия (или Бразилия) — применяется, если Пользователь, независимо от гражданства, находится в Бразилии.
Бизнес-пользователь — любой Пользователь, не являющийся Потребителем.
Цифровой продукт – Продукт, который состоит из: контента, произведенного и поставляемого в цифровой форме; и/или услугу, которая позволяет создавать, обрабатывать, хранить данные или получать к ним доступ в цифровой форме, а также совместно использовать или любую другую форму взаимодействия с цифровыми данными, загруженными или созданными Пользователем или любым другим пользователем данного Приложения.
Европейский (или Европа) — применяется, если Пользователь, независимо от гражданства, находится в ЕС.
Владелец (или Мы) — указывает физическое лицо(а) или юридическое лицо, которое предоставляет это Приложение и/или Сервис Пользователям.
Продукт — товар или услуга, доступные через это Приложение, например, физические товары, цифровые файлы, программное обеспечение, услуги бронирования и т. д., а также любые другие типы продуктов, отдельно определенные в настоящем документе, такие как Цифровые продукты.
Сервис — сервис, предоставляемый данным Приложением, как описано в настоящих Условиях и в настоящем Приложении.
Условия — все положения, применимые к использованию данного Приложения и/или Сервиса, как описано в этом документе, включая любые другие соответствующие документы или соглашения, и которые время от времени обновляются.
Великобритания (или Великобритания) — применяется, если Пользователь, независимо от гражданства, находится в Соединенном Королевстве.
Пользователь (или Вы) — обозначает любое физическое или юридическое лицо, использующее данное Приложение.
Потребитель . Потребителем является любой Пользователь, квалифицируемый как таковой в соответствии с применимым законодательством.
Последнее обновление: 20 ноября 2024 г.
```
===== FILE: apps/kadrum/protected/views/public/index.php =====
```
$version = 1;
?>
One Button VPN - ваш ключ к защищенному интернету!
Ваш ключ к защищенному интернету
VPN защищает ваши данные, скрывает ваше местоположение и помогает безопасно пользоваться интернетом, даже в общественных сетях Wi-Fi
Высокая скорость соединения
Наслаждайтесь стабильным интернетом без задержек и сбоев с постоянной скоростью до 1 Gb/сек
Управляйте защитой по своему
Выбирайте, какие приложения или сайты использовать через наше приложение, остальное будет работать без VPN
Глобальный доступ в один клик
Наша сеть охватывает десятки стран, чтобы вы всегда оставались на связи с нужным регионом
Мы не собираем ваши данные — конфиденциальность гарантирована
Наш VPN создан для вашей безопасности и анонимности — мы не храним и не передаем персональную информацию пользователей
```
===== FILE: apps/kadrum/protected/views/public/sessions.php =====
```
Галерея
/css/reset.css" type="text/css">
Фотосессии
foreach ($rubrics as $rubric): ?>
if(count($rubric->prompts)): ?>
=$rubric->name?>
foreach ($rubric->prompts as $session): ?>
" alt="Фото 1">
=$session->name?>
endforeach; ?>
endif; ?>
endforeach; ?>
```
===== FILE: apps/kadrum/protected/views/public/privacy-policy.php =====
```
$version = 1;
?>
One Button VPN - ваш ключ к защищенному интернету!
Политика конфиденциальности приложения One Button VPN
Это Приложение собирает некоторые Персональные данные от своих Пользователей.
Права потребителей на конфиденциальность
Этот документ содержит раздел, посвященный потребителям из Калифорнии и их правам на конфиденциальность.
Этот документ содержит раздел, посвященный потребителям штата Вирджиния и их правам на конфиденциальность.
Этот документ содержит раздел, посвященный потребителям из Колорадо и их правам на конфиденциальность.
Этот документ содержит раздел, посвященный потребителям Коннектикута и их правам на конфиденциальность.
Этот документ содержит раздел, посвященный потребителям штата Юта и их правам на конфиденциальность.
Этот документ содержит раздел, посвященный Пользователям в Швейцарии и их правам на конфиденциальность.
Этот документ содержит раздел, посвященный бразильским пользователям и их правам на конфиденциальность.
Этот документ можно распечатать для справки, воспользовавшись командой печати в настройках любого браузера.
Владелец и Контроллер данных
ИП Китаев Михаил Андреевич. ИНН/ОГРНИП: 421189830262/315422300007697
Контактный адрес электронной почты владельца: hi@onebttn.com
Типы собираемых данных
Полная информация о каждом типе собираемых Персональных данных представлена ​​в специальных разделах настоящей политики конфиденциальности или в конкретных поясняющих текстах, отображаемых перед сбором Данных.
Персональные данные могут быть свободно предоставлены Пользователем или, в случае Данных об использовании, собраны автоматически при использовании данного Приложения.
Если не указано иное, все Данные, запрашиваемые этим Приложением, являются обязательными, и непредоставление этих Данных может сделать невозможным предоставление этим Приложением своих услуг. В тех случаях, когда в этом Приложении прямо указано, что некоторые Данные не являются обязательными, Пользователи могут не передавать эти Данные без последствий для доступности или функционирования Сервиса.
Пользователи, которые не уверены в том, какие Персональные данные являются обязательными, могут связаться с Владельцем.
Режим и место обработки Данных
Методы обработки
Владелец принимает соответствующие меры безопасности для предотвращения несанкционированного доступа, раскрытия, изменения или несанкционированного уничтожения Данных.
Обработка Данных осуществляется с использованием компьютеров и/или ИТ-инструментов в соответствии с организационными процедурами и режимами, строго связанными с указанными целями. Помимо Владельца, в некоторых случаях Данные могут быть доступны определенным типам ответственных лиц, участвующих в работе данного Приложения (администрирование, продажи, маркетинг, юриспруденция, системное администрирование) или внешним сторонам (например, третьим лицам). поставщики технических услуг сторон, почтовые перевозчики, хостинг-провайдеры, ИТ-компании, агентства связи), назначенные, при необходимости, Владельцем обработчиками данных. Обновленный список этих сторон можно запросить у Владельца в любое время.
Место
Данные обрабатываются в операционных офисах Владельца и в любых других местах, где находятся лица, участвующие в обработке.
В зависимости от местоположения Пользователя передача данных может включать передачу Данных Пользователя в другую страну, кроме своей страны. Чтобы узнать больше о месте обработки таких передаваемых Данных, Пользователи могут просмотреть раздел, содержащий подробную информацию об обработке Персональных данных.
Время удержания
Если иное не указано в настоящем документе, Персональные данные будут обрабатываться и храниться до тех пор, пока этого требует цель, для которой они были собраны, и могут храниться дольше в соответствии с применимыми юридическими обязательствами или на основании согласия Пользователей.
Цели обработки
Данные, касающиеся Пользователя, собираются для того, чтобы Владелец мог предоставлять свои Услуги, выполнять свои юридические обязательства, реагировать на запросы правоохранительных органов, защищать свои права и интересы (или права и интересы своих Пользователей или третьих лиц), обнаруживать любую вредоносную или мошенническую деятельность, а также следующее: Оптимизация и распределение трафика.
Для получения конкретной информации о Персональных данных, используемых для каждой цели, Пользователь может обратиться к разделу «Подробная информация об обработке Персональных данных».
Подробная информация об обработке Персональных данных
Приложение One Button VPN собирает Персональные данные, которые вы предоставляете при создании или обновлении Учетной записи. Нам требуются Персональные данные, такие как адрес электронной почты и идентификатор устройства, чтобы мы могли предоставлять вам наши Услуги, отправлять вам электронные письма, получать платежи, отвечать на запросы в службу поддержки и делиться соответствующей информацией о вашей Учетной записи и/или Услугах.
Дополнительная информация для пользователей
Правовая основа обработки
Владелец может обрабатывать Персональные данные, относящиеся к Пользователям, если применимо одно из следующих условий:
- Пользователи дали свое согласие для одной или нескольких конкретных целей.
- Предоставление Данных необходимо для исполнения соглашения с Пользователем и/или любых его преддоговорных обязательств;
- Обработка необходима для соблюдения юридического обязательства, которому подчиняется Владелец;
- Обработка связана с задачей, которая выполняется в общественных интересах или при осуществлении официальных полномочий, предоставленных Владельцу;
- Обработка необходима для целей законных интересов, преследуемых Владельцем или третьим лицом.
В любом случае Владелец с радостью поможет разъяснить конкретную правовую основу, применимую к обработке, и, в частности, является ли предоставление Персональных данных законодательным или договорным требованием или требованием, необходимым для заключения договора.
Дополнительная информация о времени хранения
Если иное не указано в настоящем документе, Персональные данные будут обрабатываться и храниться до тех пор, пока этого требует цель, для которой они были собраны, и могут храниться дольше в соответствии с применимыми юридическими обязательствами или на основании согласия Пользователей.
Права Пользователей на основании Общего регламента защиты данных (GDPR)
Пользователи могут осуществлять определенные права в отношении своих Данных, обрабатываемых Владельцем. В частности, Пользователи имеют право совершать следующие действия в пределах, разрешенных законодательством:
- Отозвать свое согласие в любое время. Пользователи имеют право отозвать согласие, если они ранее дали согласие на обработку своих Персональных данных.
- Возражать против обработки своих Данных. Пользователи имеют право возражать против обработки своих Данных, если обработка осуществляется на правовом основании, отличном от согласия.
- Доступ к их данным. Пользователи имеют право узнать, обрабатываются ли Данные Владельцем, получить раскрытие информации относительно определенных аспектов обработки и получить копию Данных, подвергающихся обработке.
- Проверьте и запросите исправление. Пользователи имеют право проверять точность своих Данных и требовать их обновления или исправления.
- Ограничить обработку своих Данных. Пользователи имеют право ограничить обработку своих Данных. В этом случае Владелец не будет обрабатывать свои Данные ни для каких иных целей, кроме их хранения.
- Удалить или иным образом удалить свои Персональные данные. Пользователи имеют право потребовать от Владельца удаления своих Данных.
- Получить свои данные и передать их другому контролеру. Пользователи имеют право получать свои Данные в структурированном, широко используемом и машиночитаемом формате и, если это технически возможно, беспрепятственно передавать их другому контролеру.
- Подавать жалобу. Пользователи имеют право подать иск в свой компетентный орган по защите данных.
Пользователи также имеют право узнать о правовой основе передачи Данных за границу, в том числе в любую международную организацию, регулируемую международным публичным правом или созданную двумя или более странами, например ООН, а также о мерах безопасности, принимаемых Владельцем для защиты их данных. Данные.
Подробности о праве на возражение против обработки
Если Персональные данные обрабатываются в общественных интересах, при осуществлении официальных полномочий, предоставленных Владельцу, или в целях законных интересов, преследуемых Владельцем, Пользователи могут возразить против такой обработки, предоставив основание, связанное с их конкретной ситуацией. обоснуйте возражение.
Пользователи должны знать, что, однако, если их Персональные данные обрабатываются в целях прямого маркетинга, они могут в любое время возразить против такой обработки, бесплатно и без предоставления каких-либо обоснований. Если Пользователь возражает против обработки в целях прямого маркетинга, Персональные данные больше не будут обрабатываться для таких целей. Чтобы узнать, обрабатывает ли Владелец Персональные данные в целях прямого маркетинга, Пользователи могут обратиться к соответствующим разделам настоящего документа.
Как реализовать эти права
Любые запросы на реализацию прав Пользователя могут быть направлены Владельцу через контактную информацию, указанную в этом документе. Такие запросы бесплатны, и Владелец ответит на них как можно раньше и всегда в течение одного месяца, предоставив Пользователям информацию, необходимую по закону.
Дополнительная информация для пользователей в Бразилии
Этот раздел документа интегрируется и дополняет информацию, содержащуюся в остальной части политики конфиденциальности, и предоставляется организацией, использующей это Приложение, и, если возможно, его материнской компанией, дочерними и аффилированными компаниями (для целей настоящего раздела именуемых в совокупности как «мы», «нас», «наш»).
Этот раздел применим ко всем Пользователям в Бразилии (Пользователи именуются ниже просто «вы», «ваш», «ваш») в соответствии с «Lei Geral de Proteção de Dados» («LGPD»), и для таких Пользователей, она заменяет любую другую, возможно, расходящуюся или противоречивую информацию, содержащуюся в политике конфиденциальности.
В этой части документа используется термин «личная информация», как он определен в LGPD.
Основания, на которых мы обрабатываем вашу личную информацию
Мы можем обрабатывать вашу личную информацию только в том случае, если у нас есть правовое основание для такой обработки. Правовые основы следующие:
- Ваше согласие на соответствующую обработку;
- Соблюдение юридических или нормативных обязательств, лежащих на нас;
- Проведение государственной политики, предусмотренной законами или постановлениями или основанной на контрактах, соглашениях и аналогичных правовых документах;
- Исследования, проводимые исследовательскими организациями, желательно на анонимной личной информации;
- Исполнение договора и его предварительные процедуры, если вы являетесь стороной указанного договора;
- Осуществление наших прав в судебных, административных или арбитражных процедурах;
- Защита или физическая безопасность себя или третьего лица;
- Охрана здоровья – в процедурах, выполняемых организациями здравоохранения или специалистами;
- Наши законные интересы при условии, что ваши основные права и свободы не преобладают над такими интересами; и
- Кредитная защита.
Чтобы узнать больше о правовых основах, вы можете связаться с нами в любое время, используя контактную информацию, указанную в этом документе.
Категории обрабатываемой личной информации
Чтобы узнать, какие категории вашей личной информации обрабатываются, вы можете прочитать раздел «Подробная информация об обработке персональных данных» данного документа.
Почему мы обрабатываем вашу личную информацию
Чтобы узнать, почему мы обрабатываем вашу личную информацию, вы можете прочитать разделы «Подробная информация об обработке Персональных данных» и «Цели обработки» настоящего документа.
Ваши права на конфиденциальность в Бразилии, как подать запрос и наш ответ на ваши запросы
Ваши права на конфиденциальность в Бразилии:
- Получить подтверждение факта обработки вашей личной информации;
- Доступ к вашей личной информации;
- Исправлять неполную, неточную или устаревшую личную информацию;
- Добиться анонимности, блокировки или удаления вашей ненужной или чрезмерной личной информации или информации, которая не обрабатывается в соответствии с LGPD;
- Получить информацию о возможности предоставления или отказа в согласии и последствиях этого;
- Получать информацию о третьих лицах, которым мы передаем вашу личную информацию;
- Обеспечить по вашему прямому запросу возможность переноса вашей личной информации (за исключением анонимной информации) другому поставщику услуг или продуктов при условии, что наши коммерческие и промышленные тайны будут защищены;
- Добиться удаления вашей обрабатываемой личной информации, если обработка была основана на вашем согласии, за исключением одного или нескольких исключений, предусмотренных в ст. применяются статьи 16 LGPD;
- Отозвать свое согласие в любое время;
- Подать жалобу, связанную с вашей личной информацией, в ANPD (Национальный орган по защите данных) или в органы по защите прав потребителей;
- Возражать против обработки данных в случаях, когда обработка не осуществляется в соответствии с положениями закона;
- Запросить четкую и адекватную информацию о критериях и процедурах, используемых для автоматического принятия решения; и
- Запрашивать пересмотр решений, принятых исключительно на основе автоматизированной обработки вашей личной информации, затрагивающих ваши интересы. К ним относятся решения по определению вашего личного, профессионального, потребительского и кредитного профиля или аспектов вашей личности.
Вы никогда не подвергнетесь дискриминации или иным образом не пострадаете, если воспользуетесь своими правами.
Как подать заявку
Вы можете подать прямой запрос на бесплатное осуществление своих прав в любое время, используя контактную информацию, указанную в этом документе, или через своего законного представителя.
Как и когда мы ответим на ваш запрос
Мы постараемся оперативно реагировать на ваши запросы. В любом случае, если мы не сможем это сделать, мы обязательно сообщим вам фактические или юридические причины, которые не позволяют нам немедленно или иным образом выполнить ваши запросы. В тех случаях, когда мы не обрабатываем вашу личную информацию, мы укажем вам физическое или юридическое лицо, которому вы должны направить свои запросы, если мы в состоянии это сделать.
Если вы подаете запрос на подтверждение доступа или обработки личной информации, обязательно укажите, хотите ли вы, чтобы ваша личная информация была доставлена ​​в электронной или печатной форме. Вам также необходимо будет сообщить нам, хотите ли вы, чтобы мы ответили на ваш запрос немедленно (в этом случае мы ответим в упрощенном виде) или вместо этого вам потребуется полное раскрытие информации. В последнем случае мы ответим в течение 15 дней с момента вашего запроса, предоставив вам всю информацию о происхождении вашей личной информации, подтверждение того, существуют ли записи, любые критерии, используемые для обработки, и цели. обработки, сохраняя при этом наши коммерческие и промышленные тайны.
В случае, если вы подаете запрос на исправление, удаление, анонимизацию или блокировку личной информации, мы обязательно немедленно передадим ваш запрос другим лицам, которым мы передали вашу личную информацию, чтобы дать возможность таким третьим лицам также соблюдать ваши запрос — за исключением случаев, когда такое общение оказывается невозможным или требует непропорциональных усилий с нашей стороны.
Передача личной информации за пределы Бразилии разрешена законом
Нам разрешено передавать вашу личную информацию за пределы территории Бразилии в следующих случаях:
- когда передача необходима для международно-правового сотрудничества органов государственной разведки, следствия и прокуратуры в соответствии с правовыми средствами, предусмотренными международным правом;
- когда передача необходима для защиты вашей жизни или физической безопасности или безопасности третьих лиц;
- когда передача разрешена ANPD;
- когда передача является результатом обязательства, взятого на себя в соглашении о международном сотрудничестве;
- когда перевод необходим для исполнения государственной политики или законного присвоения государственной службы;
- когда передача необходима для соблюдения юридического или нормативного обязательства, выполнения контракта или предварительных процедур, связанных с контрактом, или регулярного осуществления прав в судебных, административных или арбитражных процедурах.
Дополнительная информация для потребителей Калифорнии
Этот раздел документа интегрируется и дополняет информацию, содержащуюся в остальной части политики конфиденциальности, и предоставляется компанией, использующей это Приложение, и, если возможно, ее материнской компанией, дочерними и аффилированными компаниями (для целей этого раздела в совокупности как «мы», «нас», «наш»).
Этот раздел применяется ко всем Пользователям (далее пользователи именуются просто «вы», «ваш», «ваш»), которые являются потребителями, проживающими в штате Калифорния, Соединенные Штаты Америки, в соответствии с «Законом о конфиденциальности потребителей штата Калифорния». Закон 2018 года» («CCPA») с изменениями, внесенными «Законом Калифорнии о правах на конфиденциальность» («CPRA») и последующими нормативными актами. Для таких потребителей этот раздел заменяет любую другую, возможно, расходящуюся или противоречивую информацию, содержащуюся в политике конфиденциальности.
В этой части документа используется термин «личная информация», как он определен в Законе Калифорнии о конфиденциальности потребителей (CCPA/CPRA).
Уведомление при сборе
Категории личной информации, собираемой, используемой, продаваемой или передаваемой
В этом разделе мы суммируем категории личной информации, которую мы собираем, используем, продаем или передаем, а также ее цели. Подробно об этой деятельности можно прочитать в разделе «Подробная информация об обработке Персональных данных» настоящего документа.
Информация, которую мы собираем: категории личной информации, которую мы собираем.
Мы собрали следующие категории личной информации о вас: информация о деятельности в Интернете или других электронных сетях.
Мы не собираем конфиденциальную личную информацию.
Мы не будем собирать дополнительные категории личной информации без вашего предварительного уведомления.
Для каких целей мы используем вашу личную информацию?
Мы можем использовать вашу личную информацию для обеспечения оперативного функционирования данного Приложения и его функций («деловые цели»). В таких случаях ваша личная информация будет обрабатываться способом, необходимым и соразмерным деловой цели, для которой она была собрана, и строго в пределах совместимых операционных целей.
Мы также можем использовать вашу личную информацию для других целей, например, в коммерческих целях, как указано в разделе «Подробная информация об обработке персональных данных» в этом документе, а также для соблюдения закона и защиты наших прав перед компетентными органами. когда наши права и интересы находятся под угрозой или нам причинен реальный ущерб.
Мы не будем обрабатывать вашу информацию в непредвиденных целях или в целях, несовместимых с первоначально раскрытыми целями, без вашего согласия.
Как долго мы храним вашу личную информацию?
Если иное не указано в разделе «Подробная информация об обработке Персональных данных», мы не будем хранить вашу личную информацию дольше, чем это разумно необходимо для целей, для которых она была собрана.
Как мы собираем информацию: каковы источники собираемой нами личной информации?
Мы собираем вышеупомянутые категории личной информации прямо или косвенно от вас, когда вы используете это Приложение.
Например, вы напрямую предоставляете свою личную информацию при отправке запросов через любые формы в этом Приложении. Вы также предоставляете личную информацию косвенно при навигации по этому Приложению, поскольку личная информация о вас автоматически отслеживается и собирается.
Наконец, мы можем собирать вашу личную информацию от третьих лиц, которые работают с нами в связи с Сервисом или с функционированием данного Приложения и его функций.
Как мы используем информацию, которую собираем: раскрытие вашей личной информации третьим лицам в деловых целях.
Для наших целей слово «третья сторона» означает лицо, которое не является ни одним из следующих: поставщиком услуг или подрядчиком, как это определено CCPA.
Мы раскрываем вашу личную информацию третьим лицам, подробно перечисленным в разделе «Подробная информация об обработке Персональных данных» настоящего документа. Эти третьи стороны сгруппированы и классифицированы в соответствии с различными целями обработки.
Никакой продажи вашей личной информации
Мы не продаем и не передаем вашу личную информацию. В случае, если мы примем такое решение, мы сообщим вам заранее и предоставим вам право отказаться от такой продажи.
Ваши права на конфиденциальность в соответствии с Законом Калифорнии о конфиденциальности потребителей и способы их реализации.
Право на доступ к личной информации: право на знание и на переносимость.
- Вы имеете право потребовать, чтобы мы раскрыли категории личной информации, которую мы собираем о вас, источники, цели, которым мы раскрываем такую ​​информацию, а также конкретные фрагменты личной информации, которую мы собрали.
- Вы также имеете право знать, какая личная информация продается или передается и кому, запросив два отдельных списка, в которых мы раскрываем категории личной информации, продаваемой или передаваемой, и категории третьих лиц, которым информация была продана или передана.
Право запросить удаление вашей личной информации с учетом законодательных исключений, а также право исправлять неточную личную информацию.
Право отказаться от продажи или передачи личной информации и ограничить использование вашей конфиденциальной личной информации.
Право на невозмездие после отказа или осуществления других прав (право на недискриминацию).
Как реализовать свои права
Чтобы воспользоваться своими правами, отправьте нам поддающийся проверке запрос, используя предоставленные контактные данные.
- Предоставьте нам достаточную информацию, чтобы убедиться, что вы являетесь лицом, о котором мы собрали личную информацию, или уполномоченным представителем.
- Опишите свой запрос достаточно подробно, чтобы мы могли его понять, оценить и ответить на него.
Мы не будем отвечать ни на один запрос, если не сможем подтвердить вашу личность и подтвердить, что личная информация относится к вам.
Для подачи поддающегося проверке запроса потребителя не требуется создавать у нас учетную запись. Если вы не можете лично подать поддающийся проверке запрос, вы можете уполномочить кого-либо действовать от вашего имени.
Вы можете подать максимум 2 запроса в течение 12 месяцев.
Как и когда мы должны обработать ваш запрос
Мы подтвердим получение вашего поддающегося проверке запроса в течение 10 дней и предоставим информацию о том, как мы его обработаем.
Мы ответим в течение 45 дней с момента его получения. Если потребуется больше времени, мы сообщим вам причину и период продления.
Наше раскрытие будет охватывать предыдущий 12-месячный период. В отношении личной информации, собранной 1 января 2022 года или после этой даты, вы можете потребовать, чтобы мы раскрыли информацию по истечении 12-месячного периода.
Если мы отклоним ваш запрос, мы объясним причины нашего отказа. Мы не взимаем плату, за исключением случаев, когда ваш запрос является явно необоснованным или чрезмерным. В таких случаях мы можем взимать разумную плату или отказать в выполнении запроса. В любом случае мы сообщим о своем выборе и объясним его причины.
Дополнительная информация для потребителей Вирджинии
Этот раздел интегрируется и дополняет информацию, содержащуюся в остальной части политики конфиденциальности. Он предоставляется контролером, запускающим это Приложение, в число которого могут входить его материнские, дочерние и аффилированные компании.
Этот раздел относится конкретно к Пользователям, которые являются потребителями, проживающими в Содружестве Вирджиния, в соответствии с Законом штата Вирджиния о защите данных потребителей (VCDPA). Для этих потребителей этот раздел имеет приоритет над любой другой, возможно, расходящейся или противоречивой информацией в политике конфиденциальности.
Здесь используется термин « персональные данные» в соответствии с его определением VCDPA.
Категории обрабатываемых персональных данных
Мы суммируем категории обрабатываемых персональных данных и их цели, как подробно описано в разделе «Подробная информация об обработке персональных данных» настоящего документа.
Категории персональных данных, которые мы собираем
Мы собрали такие категории персональных данных, как информация из Интернета. Мы не собираем конфиденциальные данные и не будем собирать дополнительные категории без предварительного уведомления.
Почему мы обрабатываем ваши персональные данные
Информацию о том, почему мы обрабатываем ваши персональные данные, можно найти в разделах «Подробная информация об обработке Персональных данных» и «Цели обработки» настоящего документа. Мы не будем обрабатывать ваши данные для непредвиденных или несовместимых целей без вашего согласия, которым вы можете управлять, используя предоставленные контактные данные.
Как мы используем данные, которые собираем: передача ваших личных данных третьим лицам
Мы передаем ваши персональные данные третьим лицам, указанным в разделе «Подробная информация об обработке Персональных данных» . Термин «третья сторона» определен в VCDPA.
Продажа ваших личных данных
Мы не продаем ваши персональные данные. Если ситуация изменится, вы будете проинформированы и сможете отказаться от продажи.
Обработка ваших персональных данных для целевой рекламы
Мы не используем ваши персональные данные для целевой рекламы, но обновим нашу политику и предоставим возможность отказа, если она изменится.
Ваши права на конфиденциальность в соответствии с Законом штата Вирджиния о защите потребительских данных и способы их реализации.
Вы можете осуществлять определенные права в отношении ваших данных, обрабатываемых нами, включая право на доступ, исправление, удаление или получение копии ваших личных данных, а также отказаться от определенных действий по обработке данных.
- Доступ к личным данным: право знать, обрабатываем ли мы ваши личные данные, и иметь к ним доступ.
- Исправить неточные персональные данные: право на исправление любых неточностей в ваших личных данных.
- Запрос на удаление: право запросить удаление ваших личных данных.
- Получить копию: право получить копию ваших данных в портативном формате.
- Отказ: право отказаться от целевой рекламы, продажи личных данных или профилирования.
- Недискриминация: мы не будем дискриминировать вас за реализацию ваших прав на конфиденциальность.
Чтобы воспользоваться этими правами, свяжитесь с нами, используя информацию, представленную в этом документе.
Как и когда мы должны обработать ваш запрос
Мы ответим на ваш запрос без неоправданной задержки, в течение 45 дней. Если потребуется больше времени, мы сообщим вам причины и ожидаемые сроки.
Если мы отклоним ваш запрос, мы укажем причины нашего отказа, и вы имеете право подать апелляцию.
Мы не взимаем плату за ответ на ваши запросы, если они не являются необоснованными, чрезмерными или повторяющимися. В таких случаях мы можем взимать плату или отказать в выполнении запроса.
Дополнительная информация для потребителей Колорадо
Этот раздел дополняет политику конфиденциальности конкретными соображениями для пользователей в Колорадо в соответствии с Законом о конфиденциальности штата Колорадо (CPA).
Персональные данные. Термин «персональные данные» определен в CPA.
Категории обрабатываемых персональных данных
Мы суммируем типы персональных данных, которые мы обрабатываем, и их цели. Более подробные пояснения можно найти в разделе «Подробная информация об обработке Персональных данных» настоящего документа.
- Информация в Интернете: основная категория персональных данных, которые мы собираем.
- Конфиденциальные данные: мы не собираем конфиденциальные персональные данные.
- Дополнительные данные: Мы не будем собирать дополнительные категории персональных данных без вашего ведома.
Почему мы обрабатываем ваши персональные данные
Подробную информацию о том, почему мы обрабатываем ваши персональные данные, можно найти в разделе «Цели обработки».
Мы обязуемся не обрабатывать вашу информацию в целях, которые являются неожиданными или несовместимыми с раскрытыми целями, без вашего согласия, которое вы можете дать, отклонить или отозвать в любое время.
Как мы используем данные, которые собираем
Мы передаем ваши персональные данные третьим лицам способом, который соответствует целям обработки, как указано в нашей политике конфиденциальности. Мы определяем термин «третья сторона» в соответствии с CPA.
Продажа ваших личных данных
Мы не продаем персональные данные. Если ситуация изменится, вы будете проинформированы и сможете отказаться от продажи.
Определение «продажи»: для целей CPA «продажа» предполагает обмен персональных данных за денежное или иное ценное вознаграждение.
Исключения: Определенное раскрытие персональных данных, например, обработчику, действующему от нашего имени, не считается продажей в соответствии с CPA. Могут также применяться и другие конкретные исключения.
Обработка ваших персональных данных для целевой рекламы
Мы не используем персональные данные для целевой рекламы согласно определению CPA. Если что-то изменится, вы получите уведомление и сможете отказаться.
Определение «целевой рекламы»: это относится к рекламе, основанной на личных данных, собранных с течением времени в результате вашей деятельности на несвязанных веб-сайтах или онлайн-сервисах.
Исключения: Определенные виды рекламы не считаются целевой рекламой в соответствии с CPA, например, те, которые непосредственно связаны с запросами потребителей, на собственных веб-сайтах или в приложениях контролера или для измерения рекламы без профилирования потребителей.
Ваши права на конфиденциальность в соответствии с Законом штата Колорадо о конфиденциальности и способы их реализации.
Вы можете осуществлять определенные права в отношении ваших данных, обрабатываемых нами. В частности, вы имеете право сделать следующее:
- Отказаться: от обработки ваших персональных данных в целях целевой рекламы, продажи персональных данных или профилирования для принятия решений, которые приводят к юридическим или аналогичным значимым последствиям в отношении вас.
- Доступ к личным данным: вы имеете право запросить у нас подтверждение того, обрабатываем ли мы ваши личные данные. Вы также имеете право на доступ к таким личным данным.
- Исправить неточные персональные данные: вы имеете право потребовать, чтобы мы исправили любые неточные персональные данные, которые мы храним о вас, принимая во внимание характер персональных данных и цели обработки персональных данных.
- Запросить удаление ваших личных данных. Вы имеете право потребовать, чтобы мы удалили любые ваши личные данные.
- Получите копию ваших личных данных: мы предоставим ваши личные данные в портативном и удобном формате, который позволит вам легко передавать данные другому лицу – при условии, что это технически осуществимо.
В любом случае мы не будем увеличивать стоимость или уменьшать доступность продукта или услуги исключительно на основании осуществления каких-либо ваших прав и не связанных с осуществимостью или ценностью услуги. Однако в той степени, в которой это разрешено законом, мы можем предложить вам другую цену, тариф, уровень, качество или выбор товаров или услуг, в том числе предлагать товары или услуги бесплатно, если наше предложение связано с вашим добровольным участием. в добросовестной программе лояльности, вознаграждениях, премиальных функциях, скидках или клубных картах.
Как реализовать свои права
Чтобы воспользоваться описанными выше правами, вам необходимо отправить нам запрос, связавшись с нами через контактную информацию, указанную в этом документе.
Чтобы мы могли ответить на ваш запрос, нам необходимо знать, кто вы и какое право вы хотите реализовать.
Мы не будем отвечать на любой запрос, если мы не сможем подтвердить вашу личность, используя коммерчески разумные усилия, и, следовательно, подтвердить, что личные данные, которыми мы располагаем, действительно относятся к вам. В таких случаях мы можем запросить у вас дополнительную информацию, которая разумно необходима для аутентификации вас и вашего запроса.
Для подачи потребительского запроса вам не требуется создавать у нас учетную запись. Однако мы можем потребовать от вас использовать существующую учетную запись. Мы будем использовать любые персональные данные, полученные от вас в связи с вашим запросом, исключительно в целях аутентификации, без дальнейшего раскрытия персональных данных, хранения их дольше, чем необходимо для целей аутентификации, или использования их для несвязанных целей.
Если вы взрослый человек, вы можете подать запрос от имени ребенка, находящегося под вашей родительской опекой.
Как и когда мы должны обработать ваш запрос
Мы ответим на ваш запрос без неоправданной задержки, но во всех случаях и не позднее, чем в течение 45 дней с момента его получения. Если нам понадобится больше времени, мы объясним вам причины и сколько еще времени нам нужно. В связи с этим обратите внимание, что выполнение вашего запроса может занять до 90 дней.
Если мы отклоним ваш запрос, мы объясним вам причины нашего отказа без неоправданной задержки, но во всех случаях и не позднее, чем в течение 45 дней с момента получения запроса. Вы имеете право обжаловать такое решение, отправив нам запрос по реквизитам, указанным в этом документе. В течение 45 дней с момента получения апелляции мы сообщим вам в письменной форме о любых действиях, предпринятых или не предпринятых в ответ на апелляцию, включая письменное объяснение причин принятых решений. Если апелляция отклонена, вы можете обратиться к Генеральному прокурору и подать жалобу.
Мы не взимаем плату за ответ на ваш запрос (до двух запросов в год).
Дополнительная информация для потребителей Коннектикута
Этот раздел документа интегрируется и дополняет информацию, содержащуюся в остальной части политики конфиденциальности, и предоставляется контролером, использующим это Приложение, и, если возможно, его материнской компанией, дочерними и аффилированными компаниями (для целей этого раздела). совместно именуемые «мы», «нас», «наш»).
Этот раздел применяется ко всем Пользователям (далее Пользователи именуются просто «вы», «ваш», «ваш»), которые являются потребителями, проживающими в штате Коннектикут, в соответствии с «Законом о конфиденциальности персональных данных и онлайн-мониторинге». (также известный как «Закон Коннектикута о конфиденциальности данных» или «CTDPA»), и для таких потребителей он заменяет любую другую, возможно, расходящуюся или противоречивую информацию, содержащуюся в политике конфиденциальности.
В этой части документа используется термин «персональные данные», как он определен в CTDPA.
Категории обрабатываемых персональных данных
В этом разделе мы суммируем категории персональных данных, которые мы обрабатываем, и их цели. Подробно об этой деятельности можно прочитать в разделе «Подробная информация об обработке Персональных данных» настоящего документа.
- Категории персональных данных, которые мы собираем: Мы собрали следующие категории персональных данных: информация в Интернете.
- Мы не собираем конфиденциальные данные.
- Мы не будем собирать дополнительные категории персональных данных без вашего уведомления.
Почему мы обрабатываем ваши персональные данные
Чтобы узнать, почему мы обрабатываем ваши персональные данные, вы можете прочитать разделы «Подробная информация об обработке Персональных данных» и «Цели обработки» настоящего документа.
Мы не будем обрабатывать вашу информацию в непредвиденных целях или в целях, несовместимых с первоначально раскрытыми целями, без вашего согласия. Вы можете свободно дать, отклонить или отозвать такое согласие в любое время, используя контактную информацию, указанную в этом документе.
Как мы используем данные, которые собираем: передача ваших личных данных третьим лицам
Мы передаем ваши персональные данные третьим лицам, подробно перечисленным в разделе «Подробная информация об обработке персональных данных» настоящего документа. Эти третьи стороны сгруппированы и классифицированы в соответствии с различными целями обработки.
Для наших целей слово «третья сторона» означает «лицо, государственный орган, агентство или орган, не являющийся потребителем, контролером, обработчиком или аффилированным лицом обработчика или контролера», как это определено CTDPA.
Продажа ваших личных данных
Мы не продаем ваши персональные данные. В случае, если мы примем такое решение, мы сообщим вам заранее и предоставим вам право отказаться от такой продажи.
Для наших целей слова «продажа», «продать» или «проданный» означают «обмен персональных данных за денежное или иное ценное вознаграждение контролером третьей стороне», как это определено CTDPA.
Обратите внимание, что согласно CTDPA раскрытие персональных данных обработчику, который обрабатывает персональные данные от имени контролера, не является продажей. Кроме того, могут применяться другие конкретные исключения, изложенные в CTDPA, такие как, помимо прочего, раскрытие персональных данных третьей стороне для предоставления запрошенного вами продукта или услуги.
Обработка ваших персональных данных для таргетированной рекламы
Мы не обрабатываем ваши персональные данные для таргетированной рекламы. Если мы примем такое решение, мы заранее сообщим вам об этом и предоставим вам право отказаться от обработки ваших личных данных для целевой рекламы.
Для наших целей слово «таргетированная реклама» означает «показ потребителю рекламы, которая выбрана на основе личных данных, полученных или выведенных с течением времени из действий потребителя на неаффилированных веб-сайтах, в приложениях или онлайн-сервисах для прогнозирования потребительских предпочтений или интересов». согласно определению CTDPA.
Обратите внимание, что согласно CTDPA, целевая реклама не включает: «рекламу, основанную на действиях на собственных веб-сайтах или онлайн-приложениях контролера; рекламные объявления, основанные на контексте текущего поискового запроса потребителя, посещения интернет-сайта или онлайн-приложения; рекламные объявления, направленные потребителю в ответ на запрос потребителя о предоставлении информации или отзыве; или обработку персональных данных исключительно для измерения или сообщения о частоте, эффективности или охвате рекламы».
Ваши права на конфиденциальность в соответствии с Законом штата Коннектикут о конфиденциальности данных и способы их реализации
Вы можете осуществлять определенные права в отношении ваших данных, обрабатываемых нами. В частности, вы имеете право сделать следующее:
- Доступ к персональным данным. Вы имеете право запросить подтверждение того, обрабатываем ли мы ваши персональные данные. Вы также имеете право на доступ к таким личным данным.
- Исправить неточные персональные данные: вы имеете право потребовать, чтобы мы исправили любые неточные персональные данные, которые мы храним о вас, принимая во внимание характер персональных данных и цели обработки персональных данных.
- Запросить удаление ваших личных данных. Вы имеете право потребовать, чтобы мы удалили любые ваши личные данные.
- Получите копию ваших личных данных: мы предоставим ваши личные данные в переносимом и удобном формате, который позволит вам легко передавать данные другому лицу — при условии, что это технически осуществимо.
- Отказ от участия: вы можете отказаться от обработки ваших персональных данных в целях целевой рекламы, продажи персональных данных или профилирования для принятия решений, которые имеют в отношении вас юридические или аналогичные значимые последствия.
В любом случае мы не будем увеличивать стоимость или уменьшать доступность продукта или услуги исключительно на основании осуществления каких-либо ваших прав и не связанных с осуществимостью или ценностью услуги. Однако в той степени, в которой это разрешено законом, мы можем предложить вам другую цену, тариф, уровень, качество или выбор товаров или услуг, в том числе предлагать товары или услуги бесплатно, если наше предложение связано с вашим добровольным участием. в добросовестной программе лояльности, вознаграждениях, премиальных функциях, скидках или клубных картах.
Как реализовать свои права
Чтобы воспользоваться описанными выше правами, вам необходимо отправить нам запрос, связавшись с нами через контактную информацию, указанную в этом документе.
Чтобы мы могли ответить на ваш запрос, нам необходимо знать, кто вы и какое право вы хотите реализовать. Мы не будем отвечать на любой запрос, если мы не сможем подтвердить вашу личность, используя коммерчески разумные усилия, и, следовательно, подтвердить, что личные данные, которыми мы располагаем, действительно относятся к вам. В таких случаях мы можем запросить у вас дополнительную информацию, которая разумно необходима для аутентификации вас и вашего запроса.
Для подачи потребительского запроса вам не требуется создавать у нас учетную запись. Однако мы можем потребовать от вас использовать существующую учетную запись. Мы будем использовать любые персональные данные, полученные от вас в связи с вашим запросом, исключительно в целях аутентификации, без дальнейшего раскрытия персональных данных, хранения их дольше, чем необходимо для целей аутентификации, или использования их для несвязанных целей.
Если вы взрослый человек, вы можете подать запрос от имени ребенка, находящегося под вашей родительской опекой.
Как и когда мы должны обработать ваш запрос
Мы ответим на ваш запрос без неоправданной задержки, но во всех случаях и не позднее, чем в течение 45 дней с момента его получения. Если нам понадобится больше времени, мы объясним вам причины и сколько еще времени нам нужно. В связи с этим обратите внимание, что выполнение вашего запроса может занять до 90 дней.
Если мы отклоним ваш запрос, мы объясним вам причины нашего отказа без неоправданной задержки, но во всех случаях и не позднее, чем в течение 45 дней с момента получения запроса. Вы имеете право обжаловать такое решение, отправив нам запрос по реквизитам, указанным в этом документе. В течение 45 дней с момента получения апелляции мы сообщим вам в письменной форме о любых действиях, предпринятых или не предпринятых в ответ на апелляцию, включая письменное объяснение причин принятых решений. Если апелляция отклонена, вы можете обратиться к Генеральному прокурору и подать жалобу.
Мы не взимаем плату за ответ на ваш запрос, но не более одного запроса в год.
Дополнительная информация для потребителей штата Юта
Этот раздел документа интегрируется и дополняет информацию, содержащуюся в остальной части политики конфиденциальности, и предоставляется контролером, использующим это Приложение, и, если возможно, его материнской компанией, дочерними и аффилированными компаниями (для целей этого раздела). вместе именуемые «мы», «нас», «наш»).
Этот раздел применяется ко всем Пользователям (далее пользователи именуются просто «вы», «ваш», «ваш»), которые являются потребителями, проживающими в штате Юта, в соответствии с «Законом о конфиденциальности потребителей» («UCPA '), и для таких потребителей она заменяет любую другую, возможно, расходящуюся или противоречивую информацию, содержащуюся в политике конфиденциальности.
В этой части документа используется термин «персональные данные», как он определен в UCPA.
Категории обрабатываемых персональных данных
В этом разделе мы суммируем категории персональных данных, которые мы обрабатываем, и их цели. Подробно об этих действиях можно прочитать в разделе «Подробная информация об обработке Персональных данных» настоящего документа.
Категории персональных данных, которые мы собираем
- Мы собрали следующие категории персональных данных: информация в Интернете.
- Мы не собираем конфиденциальные данные.
- Мы не будем собирать дополнительные категории персональных данных без вашего уведомления.
Почему мы обрабатываем ваши персональные данные
Чтобы узнать, почему мы обрабатываем ваши персональные данные, вы можете прочитать разделы «Подробная информация об обработке Персональных данных» и «Цели обработки» настоящего документа.
Как мы используем данные, которые собираем: передача ваших личных данных третьим лицам
Мы передаем ваши персональные данные третьим лицам, подробно перечисленным в разделе «Подробная информация об обработке персональных данных» настоящего документа. Эти третьи стороны сгруппированы и классифицированы в соответствии с различными целями обработки.
Для наших целей слово «третья сторона» означает «лицо, кроме: потребителя, контролера или процессора; или аффилированное лицо или подрядчик контролера или обработчика данных, как это определено UCPA.
Продажа ваших личных данных
Мы не продаем ваши персональные данные. В случае, если мы примем такое решение, мы сообщим вам заранее и предоставим вам право отказаться от такой продажи.
Для наших целей слова «продажа», «продать» или «проданный» означают «обмен персональных данных за денежное или иное ценное вознаграждение контролером третьей стороне», как это определено UCPA.
Обратите внимание, что согласно UCPA раскрытие персональных данных обработчику, который обрабатывает персональные данные от имени контролера, не является продажей. Кроме того, могут применяться другие конкретные исключения, изложенные в UCPA, такие как, помимо прочего, раскрытие персональных данных третьей стороне для предоставления запрошенного вами продукта или услуги.
Обработка ваших персональных данных для таргетированной рекламы
Мы не обрабатываем ваши персональные данные для таргетированной рекламы. Если мы примем такое решение, мы заранее сообщим вам об этом и предоставим вам право отказаться от обработки ваших личных данных для целевой рекламы.
Для наших целей слово «таргетированная реклама» означает «показ потребителю рекламы, которая выбрана на основе личных данных, полученных или выведенных с течением времени из действий потребителя на неаффилированных веб-сайтах, в приложениях или онлайн-сервисах для прогнозирования потребительских предпочтений или интересов». согласно определению UCPA.
Обратите внимание, что согласно UCPA, целевая реклама не включает: «рекламу, основанную на действиях на собственных веб-сайтах или онлайн-приложениях контролера, а также на любом дочернем веб-сайте или онлайн-приложении; рекламные объявления, основанные на контексте текущего поискового запроса потребителя, посещения веб-сайта или онлайн-приложения; рекламные объявления, направленные потребителю в ответ на запрос потребителя о информации, продукте, услуге или отзыве; или обработку персональных данных исключительно для измерения или составления отчета об эффективности, охвате или частоте рекламы».
Ваши права на конфиденциальность в соответствии с Законом штата Юта о конфиденциальности потребителей и способы их реализации
Вы можете осуществлять определенные права в отношении ваших данных, обрабатываемых нами. В частности, вы имеете право сделать следующее:
- Доступ к личным данным: вы имеете право запросить у нас подтверждение того, обрабатываем ли мы ваши личные данные. Вы также имеете право на доступ к таким личным данным.
- Запросить удаление ваших личных данных. Вы имеете право потребовать, чтобы мы удалили любые ваши личные данные.
- Получите копию ваших личных данных: мы предоставим ваши личные данные в портативном и удобном формате, который позволит вам легко передавать данные другому лицу – при условии, что это технически осуществимо.
- Отказаться от обработки ваших персональных данных: В целях целевой рекламы или продажи персональных данных.
В любом случае мы не будем увеличивать стоимость или уменьшать доступность продукта или услуги исключительно на основании осуществления каких-либо ваших прав и не связанных с осуществимостью или ценностью услуги. Однако в той степени, в которой это разрешено законом, мы можем предложить вам другую цену, тариф, уровень, качество или выбор товаров или услуг, в том числе предлагать товары или услуги бесплатно, если наше предложение связано с вашим добровольным участием. в добросовестной программе лояльности, вознаграждениях, премиальных функциях, скидках или клубных картах.
Как реализовать свои права
Чтобы воспользоваться описанными выше правами, вам необходимо отправить нам запрос, связавшись с нами через контактную информацию, указанную в этом документе.
Чтобы мы могли ответить на ваш запрос, нам необходимо знать, кто вы и какое право вы хотите реализовать. Мы не будем отвечать на любой запрос, если мы не сможем подтвердить вашу личность, используя коммерчески разумные усилия, и, следовательно, подтвердить, что личные данные, которыми мы располагаем, действительно относятся к вам. В таких случаях мы можем запросить у вас дополнительную информацию, которая разумно необходима для аутентификации вас и вашего запроса. Мы можем сохранить ваш адрес электронной почты, чтобы ответить на ваш запрос.
Если вы взрослый человек, вы можете подать запрос от имени ребенка, находящегося под вашей родительской опекой.
Как и когда мы должны обработать ваш запрос
Мы ответим на ваш запрос без неоправданной задержки, но во всех случаях и не позднее, чем в течение 45 дней с момента его получения. Если нам понадобится больше времени, мы объясним вам причины и сколько еще времени нам нужно. В связи с этим обратите внимание, что выполнение вашего запроса может занять до 90 дней.
Если мы отклоним ваш запрос, мы объясним вам причины нашего отказа без неоправданной задержки, но во всех случаях и не позднее, чем в течение 45 дней с момента получения запроса.
Мы не взимаем плату за ответ на ваш запрос, но не более одного запроса в год.
Дополнительная информация о сборе и обработке данных
Юридический иск
Персональные данные Пользователя могут использоваться Владельцем в юридических целях в суде или на стадиях, ведущих к возможным судебным искам, возникающим в результате ненадлежащего использования данного Приложения или связанных с ним Услуг. Пользователь заявляет, что осознает, что от Владельца может потребоваться раскрытие персональных данных по запросу государственных органов.
Дополнительная информация о персональных данных Пользователя
Помимо информации, содержащейся в настоящей политике конфиденциальности, данное Приложение может по запросу предоставлять Пользователю дополнительную и контекстную информацию, касающуюся конкретных Сервисов или сбора и обработки Персональных данных.
Системные журналы и обслуживание
В целях эксплуатации и обслуживания данное Приложение и любые сторонние сервисы могут собирать файлы, записывающие взаимодействие с этим Приложением (Системные журналы), использовать для этой цели другие Персональные данные (например, IP-адрес).
Информация, не содержащаяся в настоящей политике
Более подробную информацию о сборе или обработке Персональных данных можно запросить у Владельца в любое время. Пожалуйста, смотрите контактную информацию в начале этого документа.
Изменения в настоящей Политике конфиденциальности
Владелец оставляет за собой право вносить изменения в настоящую политику конфиденциальности в любое время, уведомив своих Пользователей на этой странице и, возможно, в этом Приложении и/или – насколько это технически и юридически возможно – отправив уведомление Пользователям через любую контактную информацию, доступную владелец. Настоятельно рекомендуется часто проверять эту страницу, обращая внимание на дату последнего изменения, указанную внизу.
Если изменения затрагивают действия по обработке, выполняемые на основании согласия Пользователя, Владелец должен получить от Пользователя новое согласие, если это необходимо.
Определения и юридические ссылки
- Персональные данные (или Данные) — любая информация, которая прямо, косвенно или в связи с другой информацией, включая личный идентификационный номер, позволяет идентифицировать или идентифицировать физическое лицо.
- Данные об использовании — информация, автоматически собираемая через это Приложение (или сторонние службы, используемые в этом Приложении), которая может включать в себя: IP-адреса или доменные имена компьютеров, используемых Пользователями, использующими это Приложение, адреса URI (унифицированный идентификатор ресурса). ), время запроса, метод отправки запроса на сервер, размер файла, полученного в ответ, числовой код, указывающий статус ответа сервера (успешный результат, ошибка и т. д.), страна происхождение, особенности браузера и операционной системы, используемые Пользователем, различные сведения о времени каждого посещения (например, время, проведенное на каждой странице в Приложении), а также сведения о пути, пройденном в Приложении, со специальной ссылкой на последовательность посещенных страниц и другие параметры операционной системы устройства и/или ИТ-среды Пользователя.
- Пользователь – физическое лицо, использующее данное Приложение, которое, если не указано иное, совпадает с Субъектом данных.
- Субъект данных – физическое лицо, к которому относятся Персональные данные.
- Обработчик данных (или Обработчик) — физическое или юридическое лицо, государственный орган, агентство или другой орган, который обрабатывает Персональные данные от имени Контролера, как описано в настоящей политике конфиденциальности.
- Контроллер данных (или владелец) — физическое или юридическое лицо, государственный орган, агентство или другой орган, который самостоятельно или совместно с другими определяет цели и средства обработки Персональных данных, включая меры безопасности, касающиеся эксплуатации и использования это приложение. Контроллер данных, если не указано иное, является Владельцем настоящего Приложения.
- Настоящее Приложение – Средство, с помощью которого собираются и обрабатываются Персональные данные Пользователя.
- Сервис — сервис, предоставляемый данным Приложением, как описано в соответствующих условиях (если таковые имеются) и на этом сайте/приложении.
- Европейский Союз (или ЕС) . Если не указано иное, все ссылки на Европейский Союз в этом документе включают все нынешние государства-члены Европейского Союза и Европейской экономической зоны.
- Файлы cookie — файлы cookie представляют собой трекеры, состоящие из небольших наборов данных, хранящихся в браузере Пользователя.
- Трекер . Трекер обозначает любую технологию (например, файлы cookie, уникальные идентификаторы, веб-маяки, встроенные скрипты, электронные теги и снятие отпечатков пальцев), которая позволяет отслеживать Пользователей, например, путем доступа к информации или ее сохранения на устройстве Пользователя.
Легальная информация
Настоящее заявление о конфиденциальности было подготовлено на основе положений нескольких законодательных актов.
Настоящая политика конфиденциальности относится исключительно к данному Приложению, если иное не указано в настоящем документе.
Последнее обновление: 20 ноября 2024 г.
```