feat(admin): compact UTM percentages and shared notes
deploy-preland / deploy zoomstage.online (push) Successful in 1m1s
deploy-preland / deploy zoomstage.online (push) Successful in 1m1s
This commit is contained in:
1 parent
4fc4fd8162
commit
f2097d4800
10 files changed
+240
-16
No files matched your search
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace PrelandAdmin;
|
||||
|
||||
final class Notes
|
||||
{
|
||||
private const MARKERS=['indpic','theme','degr','reg','banch','tp','cons','focus'];
|
||||
public function __construct(private readonly Store $store) {}
|
||||
|
||||
public function all(): array {
|
||||
return $this->store->locked('utm-notes',fn()=>$this->load());
|
||||
}
|
||||
|
||||
public function save(array $input): array {
|
||||
if(count($input)!==3||array_diff(array_keys($input),['marker','text','version'])||!is_string($input['marker']??null)||!in_array($input['marker'],self::MARKERS,true)||!is_string($input['text']??null)||!is_string($input['version']??null))throw new Problem('Передайте маркер UTM, текст и версию заметки.');
|
||||
if(!self::validText($input['text']))throw new Problem('Заметка должна содержать корректный UTF-8, не более 10000 символов, без управляющих символов кроме перевода строки и табуляции.');
|
||||
return $this->store->locked('utm-notes',function() use($input){
|
||||
$notes=$this->load();$marker=$input['marker'];
|
||||
if(!hash_equals($notes[$marker]['version'],$input['version']))throw new Problem('Заметка уже изменена. Загрузите актуальную версию перед сохранением.',409);
|
||||
$note=['text'=>$input['text'],'version'=>bin2hex(random_bytes(32)),'saved_at'=>gmdate(DATE_ATOM)];
|
||||
$notes[$marker]=$note;$this->store->write('utm-notes.json',$notes);return $note;
|
||||
});
|
||||
}
|
||||
|
||||
private function load(): array {
|
||||
$path=$this->store->path('utm-notes.json');clearstatcache(true,$path);
|
||||
if(is_link($path)||file_exists($path)&&(!is_file($path)||filesize($path)>1048576))throw new Problem('Повреждено приватное состояние заметок.',503);
|
||||
if(!file_exists($path)){
|
||||
$notes=[];foreach(self::MARKERS as $marker)$notes[$marker]=['text'=>'','version'=>hash('sha256','utm-notes:'.$marker.':empty'),'saved_at'=>null];return $notes;
|
||||
}
|
||||
$notes=$this->store->read('utm-notes.json',null);
|
||||
if(!is_array($notes)||count($notes)!==8||array_diff(self::MARKERS,array_keys($notes)))throw new Problem('Повреждено приватное состояние заметок.',503);
|
||||
$ordered=[];
|
||||
foreach(self::MARKERS as $marker){
|
||||
$note=$notes[$marker];
|
||||
if(!is_array($note)||count($note)!==3||array_diff(array_keys($note),['text','version','saved_at'])||!is_string($note['text']??null)||!self::validText($note['text'])||!is_string($note['version']??null)||!preg_match('/^[a-f0-9]{64}$/D',$note['version'])||!array_key_exists('saved_at',$note)||($note['saved_at']!==null&&(!is_string($note['saved_at'])||!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+00:00$/D',$note['saved_at']))))throw new Problem('Повреждено приватное состояние заметок.',503);
|
||||
if($note['saved_at']===null&&($note['text']!==''||$note['version']!==hash('sha256','utm-notes:'.$marker.':empty')))throw new Problem('Повреждено приватное состояние заметок.',503);
|
||||
$ordered[$marker]=$note;
|
||||
}
|
||||
return $ordered;
|
||||
}
|
||||
|
||||
private static function validText(string $text): bool {
|
||||
return strlen($text)<=40000&&preg_match('//u',$text)===1&&preg_match('/[\x{0000}-\x{0008}\x{000B}\x{000C}\x{000E}-\x{001F}\x{007F}-\x{009F}]/u',$text)===0&&preg_match_all('/./us',$text)<=10000;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user