Compare commits

..

143 commits
master ... php8

Author SHA1 Message Date
5988eca22b chore: Типы 2025-12-11 16:08:16 +03:00
481f76add4 chore: Типы 2025-12-11 13:27:11 +03:00
1b6630e5f5 chore: Типы 2025-12-10 19:50:04 +03:00
233749f1ea chore: Типы 2025-12-10 17:46:56 +03:00
Wizard
f4d829119b chore: Типы 2025-12-09 23:25:13 +03:00
3d34ae4b84 chore: Типы 2025-12-09 19:46:55 +03:00
fbe5eb878e chore: Типы 2025-12-09 17:05:00 +03:00
b782af55a5 chore: Типы 2025-12-04 19:44:02 +03:00
08defbd046 chore: Типы 2025-12-04 16:39:24 +03:00
3169ea2032 chore: Аннотция типов 2025-12-04 13:43:22 +03:00
Wizard
0886109467 feat: getArray для HttpRequest 2025-12-04 00:46:30 +03:00
aa667d2489 fix: HttpRequest 2025-12-03 20:58:24 +03:00
74aa44bdc0 feat: Строгие типы для HttpRequest 2025-12-03 18:32:21 +03:00
5170ed8ed5 feat: FakeTemplate -> JsonView 2025-12-03 17:12:40 +03:00
18cc1cad01 chore: Проверки к типам 2025-12-02 13:16:02 +03:00
8786e84568 chore: Проверки к типам 2025-12-01 19:44:22 +03:00
5d3fae4249 chore: Проверки для типов 2025-12-01 16:19:28 +03:00
6fdc3eb46b chore: Мелкие правки 2025-11-28 17:44:08 +03:00
3e0b99c3d2 chore: Мелкие правки 2025-11-27 13:45:45 +03:00
0473d410d1 refactor: Упрощение контроллера 2025-11-13 18:51:18 +03:00
b20ea0e7dc fix: Доп. проверки значений 2025-11-12 19:41:24 +03:00
33565c9f7e fix: Проверка на пустое имя компонента 2025-11-11 16:22:01 +03:00
87fc94d700 fix: Аннотация типов 2025-11-06 11:59:54 +03:00
540805ae35 fix: Аннотации к типам. 2025-11-05 11:01:56 +03:00
Wizard
f07a668b30 chore: Аннотация типов 2025-11-02 12:33:26 +03:00
Wizard
cf0bc435ce chore: Аннотация типов 2025-11-01 23:17:40 +03:00
f964472e62 chore: Аннотации к типам 2025-10-30 12:59:36 +03:00
5e8958969f chore: Аннотации к типам 2025-10-29 20:43:32 +03:00
0267d3081f chore: Аннотации к типам 2025-10-29 17:38:30 +03:00
2aac407c4d chore: Аннотации к типам 2025-10-29 15:21:10 +03:00
8588173079 chore: Аннотации к типам 2025-10-29 13:52:15 +03:00
704e4e0bd5 chore: Аннотации к типам 2025-10-28 20:09:21 +03:00
245b5c6c19 chore: Аннотации к типам 2025-10-28 16:32:00 +03:00
386a927254 chore: Аннотации к типам 2025-10-28 12:26:00 +03:00
89913de4fe chore: Аннотации к типам 2025-10-27 16:39:44 +03:00
730a608f9b chore: Аннотации к типам 2025-10-23 15:54:14 +03:00
530a3b931d chore: Аннотации к типам 2025-10-23 11:24:33 +03:00
e5713e9015 chore: Аннотации к типам 2025-10-22 17:48:37 +03:00
e2ba6bd46e chore: Аннотации к типам 2025-10-21 15:55:49 +03:00
1e27648a12 chore: Аннотации к типам 2025-10-21 12:00:06 +03:00
09a61244ca fix: Аннотации типов 2025-10-16 11:43:27 +03:00
69370bdf38 Merge branch 'php8' of https://git.edu.yar.ru/libs/phplibrary into php8 2025-10-07 13:23:16 +03:00
ad920f656c chore: Добавлены аннотации к типам 2025-10-07 13:23:09 +03:00
System Administrator
a58f18c836 fix: Инициализация параметров 2025-10-07 12:07:59 +03:00
48269bd424 fix: phpstan level=6 2025-10-06 12:49:36 +03:00
acbf2c847d fix: Определения типов 2025-10-01 13:11:06 +03:00
987d4097c9 Merge branch 'php8' of https://git.edu.yar.ru/libs/phplibrary into php8 2025-10-01 12:39:01 +03:00
dd74a97894 fix: Определения типов 2025-10-01 12:37:39 +03:00
Wizard
df5fd1ca6a chore: Ошибка deprecated implicitly nullable в phpstan 2025-09-24 16:46:59 +03:00
Wizard
d98eec0b92 Merge branch 'php8' of https://gitlab.edu.yar.ru/composer/PHP_Library into php8 2025-09-24 13:04:44 +03:00
Wizard
170c30f821 fix: type decl 2025-09-24 13:04:25 +03:00
9f6fd74b17 fix: Добавлен метод к интерфейсу сайта 2025-08-01 12:41:09 +03:00
5474090f85 fix: Интерфейсы 2025-05-23 13:35:46 +03:00
36c81135f3 chore: Типы для параметров 2025-05-13 17:26:49 +03:00
6ef65fc826 refactor: Form больше не наследует View 2025-04-03 19:59:56 +03:00
2f8c788664 feat: Получение списка свойст компонента 2025-03-27 15:37:31 +03:00
2aa77efd94 fix: Ошибка при оптимизации пути 2025-01-22 11:06:30 +03:00
90cbd3b2b6 fix: noverify + типы для правил провеки 2025-01-21 19:33:33 +03:00
Wizard
9680409ba9 refactor: Название методов 2024-12-16 23:35:20 +03:00
82f6dd1630 refactor: Замена строк на имена классов 2024-12-16 17:10:44 +03:00
1d22953f68 fix: type 2024-12-12 12:12:00 +03:00
e5e0b6735f feat: Небольшой рефакторинг. Ограничение длинны значения в поле ввода 2024-12-05 12:48:08 +03:00
f599a68529 feat: sqlite in memory db 2024-11-14 12:04:09 +03:00
d692538163 style: Форматирование 2024-08-29 11:11:20 +03:00
8941f5f102 fix: noverify --fix (repeat) 2024-06-14 14:15:01 +03:00
117640a755 fix: noverify --fix 2024-06-14 14:12:02 +03:00
5aff28d2b8 В файле установки добавлен параметр указывающий на папку с файлами 2024-05-17 16:36:22 +03:00
85b957c8af Установка из папки вместо архива 2024-04-17 20:37:27 +03:00
431c68bb0e Merge branch 'php8' of https://gitlab.edu.yar.ru/composer/PHP_Library into php8 2024-03-12 12:43:31 +03:00
56e1f5b02f fix: Типы 2024-03-12 12:43:14 +03:00
6711630398 fix: Обьявления типов 2024-02-28 11:04:21 +03:00
6ae14fd5f0 fix: Расчет хеша для браузера 2024-02-13 15:33:25 +03:00
548d5daaa9 fix: Уточнения к типам 2024-01-26 16:56:51 +03:00
5cfd2ee773 fix: deprecated 2024-01-25 20:22:52 +03:00
2947e4aac3 fix: phpstan level=5 2024-01-24 17:13:38 +03:00
1638d558c5 fix: phpstan. Удаление State 2024-01-24 12:49:11 +03:00
4ac027b8ee fix: Неиспользуемый код 2024-01-23 18:12:29 +03:00
c514d747b8 fix: PHPMailer 2024-01-23 11:44:42 +03:00
dcf36cda7e fix: Локальные зависимости 2024-01-23 11:40:24 +03:00
4fc2e2ac7d fix: phpstan level=3 2024-01-22 19:56:45 +03:00
277a297b8a fix: BaseMapper 2024-01-22 14:07:45 +03:00
f594611656 fix: phpstan level=3 2024-01-22 14:00:52 +03:00
bbc9d0e7fe fix: php8 2024-01-18 17:42:44 +03:00
d6864daae4 fix: совместимость с php8.2 2024-01-17 15:43:49 +03:00
0d6da39e90 fix: совместимость с php8.2 2024-01-16 18:24:03 +03:00
3a2b273adc Merge branch 'noglob' into php8 2024-01-09 12:03:17 +03:00
cab1a44698 feat: Префикс добавляется автоматически к скриптам и стилям 2023-11-28 17:35:48 +03:00
ad6efaf36d fix: Установка таблиц из json 2023-11-16 12:58:28 +03:00
fd12f9a2f5 Merge branch 'noglob' of https://gitlab.edu.yar.ru/composer/PHP_Library into noglob 2023-10-06 19:23:53 +03:00
2ac4b25076 fix: При установке модуля записывается только версия и время 2023-10-06 19:23:14 +03:00
25210692ca Merge branch 'new-validator' into 'noglob'
new validator

See merge request composer/PHP_Library!11
2023-07-06 13:54:09 +00:00
anatoly
e9231ead67 new validator 2023-07-06 16:51:38 +03:00
bfaaf77b3e fix: Путь к action фильтрам 2023-07-03 16:11:57 +03:00
2b021ffb61 feat: Возможность добавлять правила валидации 2023-06-16 17:58:23 +03:00
9d730c2961 fix: Слеши 2023-06-16 11:34:51 +03:00
5748ea9148 new Совместимость с php8 2023-04-19 11:25:28 +03:00
2edd65d145 fix format 2023-04-13 11:38:49 +03:00
7163158baf Merge branch 'noglob' of https://gitlab.edu.yar.ru/composer/PHP_Library into noglob 2023-04-13 11:35:07 +03:00
29a18f5410 fix Параметры для компонентов из get 2023-04-13 11:34:36 +03:00
9e176f3ae2 Merge branch 'views' into 'noglob'
creating views

See merge request composer/PHP_Library!9
2023-03-31 15:40:43 +00:00
anatoly
93c0a67241 creating views 2023-03-31 18:09:34 +03:00
System Administrator
7c0a5ab310 fix Исправлено получения значения запроса 2023-03-16 16:27:52 +03:00
ca2bcc428f fix Опечатка 2023-03-01 12:05:27 +03:00
a1509bb914 Merge branch 'master' into noglob 2023-03-01 12:01:29 +03:00
949510924a fix Мелкие правки 2023-02-27 16:47:34 +03:00
8321ec244f fix Не записывать файл если он небыл прочтен, иначе можем потерять данные 2023-02-16 16:18:29 +03:00
fff5b7b5c4 fix Расчет путей до шаблонов и компонентов 2023-02-01 12:55:02 +03:00
0c3fba0d7e Мелкие правки 2023-01-31 10:49:00 +03:00
System Administrator
b3f6cfcbd7 fix tabletree 2023-01-30 18:28:45 +03:00
e4527ad94e fix Component class name 2023-01-26 19:42:10 +03:00
c912ceebb9 fix Запятая 2022-12-16 19:17:09 +03:00
b7bac2b6a7 new Поле для html 2022-12-16 16:24:37 +03:00
d9b6faafc6 fix Доп. проверка для имени класса для прохождения тестов 2022-12-16 12:38:01 +03:00
36b7d93a98 fix При установке модуля неправильно записывались настройки 2022-12-15 18:16:58 +03:00
ab13ebd289 fix Определение пути к модулю 2022-12-15 15:50:40 +03:00
73112f5bf0 fix Передача обьекта пользователя в модель 2022-12-09 17:39:23 +03:00
9f3523de6b fix Исправлено определение имени модуля 2022-12-09 16:09:40 +03:00
7fd7c4fd12 fix Классы для полей формы вынесены в отдельные файлы 2022-12-08 18:40:00 +03:00
aaba3d7585 fix type 2022-11-29 16:15:58 +03:00
526262c80b fix Validator 2022-11-28 18:44:09 +03:00
28429039a4 ref Исправление типов 2022-11-24 19:12:00 +03:00
a09fc396d8 fix 2022-11-22 12:46:15 +03:00
7d35a8f3f0 Merge branch 'master' into noglob 2022-11-18 16:07:32 +03:00
CORP\phedor
706e12c06f Merge branch 'noglob' of http://gitlab.edu.yar.ru/composer/PHP_Library into noglob 2018-05-04 14:58:29 +03:00
CORP\phedor
11370eecc9 Постфиксная запись типов вместо префиксной 2018-05-04 14:57:52 +03:00
CORP\phedor
40f40a8d6d Поправил определение название модуля 2018-04-28 14:13:19 +03:00
CORP\phedor
04662a94df Merge branch 'noglob' of http://gitlab.edu.yar.ru/composer/PHP_Library into noglob 2018-04-26 12:31:57 +03:00
CORP\phedor
8065253db0 OptionFactory вынес в cms 2018-04-26 12:31:43 +03:00
CORP\phedor
df00756a41 Интерфейс вместо класса Site 2018-04-25 14:51:04 +03:00
CORP\phedor
c2b9254fd0 Прохождение автризации 2018-04-23 11:18:53 +03:00
CORP\phedor
aaa9c2e1bf Другая интерпретация реестра 2018-04-20 18:03:39 +03:00
CORP\phedor
40fad0e75b Изменен механизм расширения ссылок. Избавление от глобальных переменных 2018-04-18 18:20:56 +03:00
CORP\phedor
524b27936a Правки namespace 2018-04-13 17:59:57 +03:00
CORP\phedor
ee06f1febb Поправил namespace 2018-04-03 14:35:20 +03:00
CORP\phedor
bef7165777 Поправил инициализацию модулей 2018-03-28 19:06:30 +03:00
CORP\phedor
0b66a72484 Поправил название классов 2018-03-28 17:02:20 +03:00
CORP\phedor
f041488554 Избавляемся от констант и глобальных переменных 2018-03-28 12:43:06 +03:00
CORP\phedor
8b38b2a3cc Обьединение с namespace 2018-03-28 11:15:16 +03:00
CORP\phedor
96aec642b1 Настройка для composer 2018-03-27 18:10:46 +03:00
CORP\phedor
32ec09a66a Добавил namespace и зависимости 2018-03-27 17:40:33 +03:00
CORP\phedor
e9f7c23990 Модули c namespace 2018-03-27 17:31:49 +03:00
CORP\phedor
805fb6654d Избавляемся от статических классов и синглтонов 2018-03-27 12:23:58 +03:00
CORP\phedor
0f4b2fb722 Оптимизации и исправления топов. 2018-03-23 12:35:10 +03:00
120 changed files with 5431 additions and 3331 deletions

View file

@ -8,8 +8,11 @@
} }
], ],
"autoload": { "autoload": {
"psr-0": { "psr-4": {
"": "src/" "ctiso\\": "src/"
} }
},
"require": {
"phpmailer/phpmailer": "^6.8.0"
} }
} }

4
config.php Normal file
View file

@ -0,0 +1,4 @@
<?php
define('PHPTAL_PHP_CODE_DESTINATION', '');
define('PHPTAL_TEMPLATE_REPOSITORY', '');
define('PHPTAL_DEFAULT_ENCODING', '');

10
phpstan.neon Normal file
View file

@ -0,0 +1,10 @@
parameters:
level: 3
editorUrl: 'vscode://file/%%file%%:%%line%%'
paths:
- src
universalObjectCratesClasses:
- ctiso\View\Composite
bootstrapFiles:
- config.php

View file

@ -1,16 +1,28 @@
<?php <?php
namespace ctiso;
/** /**
* Интерфейс к массиву и обьекту как к коллекции * Интерфейс к массиву и обьекту как к коллекции
*/ */
class Adapter class Adapter
{ {
/** @var array|object */
protected $adaptee; protected $adaptee;
/**
* @param array|object $adaptee
*/
public function __construct ($adaptee) public function __construct ($adaptee)
{ {
$this->adaptee = $adaptee; $this->adaptee = $adaptee;
} }
/**
* @param string $name
* @return mixed
*/
public function get($name) public function get($name)
{ {
if (is_array ($this->adaptee)) { if (is_array ($this->adaptee)) {

View file

@ -1,7 +1,16 @@
<?php <?php
namespace ctiso;
class Arr { class Arr {
/**
* @param array<string|int, mixed> $data
* @param string|int $key
* @param mixed $default
* @return mixed
*/
static function get($data, $key, $default = null) { static function get($data, $key, $default = null) {
return isset($data[$key]) ? $data[$key] : $default; return $data[$key] ?? $default;
} }
} }

View file

@ -1,21 +1,20 @@
<?php <?php
namespace ctiso;
/** /**
* Коллекция * Коллекция
* * @implements \ArrayAccess<string,mixed>
*/ */
class Collection implements ArrayAccess class Collection implements \ArrayAccess
{ {
/** /** @var array */
* Holds collective request data protected $data = [];
*
* @var array
*/
protected $data = array();
/** /**
* Преобразование массива в коллекцию * Преобразование массива в коллекцию
* *
* @param array $data * @param array $data
* @return bool
*/ */
public function import(array $data) public function import(array $data)
{ {
@ -25,6 +24,8 @@ class Collection implements ArrayAccess
/** /**
* Преобразование коллекции в массив * Преобразование коллекции в массив
*
* @return array
*/ */
public function export() public function export()
{ {
@ -32,14 +33,13 @@ class Collection implements ArrayAccess
} }
/** /**
* Store "request data" in GPC order.
*
* @param string $key * @param string $key
* @param mixed $value * @param mixed $value
* *
* @return void * @return void
*/ */
public function set($key/*: string*/, $value/*: any*/) public function set(string $key, mixed $value)
{ {
$this->data[$key] = $value; $this->data[$key] = $value;
} }
@ -48,7 +48,7 @@ class Collection implements ArrayAccess
* Read stored "request data" by referencing a key. * Read stored "request data" by referencing a key.
* *
* @param string $key * @param string $key
* * @param mixed $default
* @return mixed * @return mixed
*/ */
public function get($key, $default = null) public function get($key, $default = null)
@ -56,44 +56,114 @@ class Collection implements ArrayAccess
return isset($this->data[$key]) && $this->data[$key] != '' ? $this->data[$key] : $default; return isset($this->data[$key]) && $this->data[$key] != '' ? $this->data[$key] : $default;
} }
public function getInt($key, $default = 0) /**
* @param string $key
* @param int $default
* @return int
*/
public function getInt(string $key, int $default = 0): int
{ {
return (int)$this->get($key, $default); $value = $this->get($key);
// Фильтруем как целое число
if (is_numeric($value)) {
// Приводим к int, но сначала проверим, что не float с дробной частью
$floatVal = (float)$value;
if (is_finite($floatVal) && floor($floatVal) === $floatVal) {
return (int)$floatVal;
}
}
return $default;
} }
public function getString($key, $default = '') /**
* @param string $key
* @param string $default
* @return string
*/
public function getString(string $key, string $default = ''): string
{ {
return ((string) $this->get($key, $default)); $value = $this->get($key);
if (is_string($value)) {
return $value;
}
if (is_numeric($value)) {
return (string)$value;
}
return $default;
} }
public function getNat($key, $default = 1) /**
* Получает булево значение
* Поддерживает: 1, '1', 'true', 'on', 'yes' true
* Иначе false
*/
public function getBool(string $key, bool $default = false): bool
{
$value = $this->get($key);
if (is_bool($value)) {
return $value;
}
if (is_string($value)) {
$value = strtolower(trim($value));
return in_array($value, ['1', 'true', 'on', 'yes'], true);
}
if (is_numeric($value)) {
return (bool)$value;
}
return $default;
}
function getArray(string $key, array $default = []): array {
$result = $this->get($key);
if (is_array($result)) {
return $result;
}
return $default;
}
/**
* @param string $key
* @param int $default
* @return int
*/
public function getNat(string $key, int $default = 1): int
{ {
$result = (int)$this->get($key, $default); $result = (int)$this->get($key, $default);
return (($result > 0) ? $result : $default); return (($result > 0) ? $result : $default);
} }
public function clear() public function clear(): void
{ {
$this->data = array(); $this->data = [];
} }
public function offsetSet($key, $value) public function offsetSet($key, $value): void
{ {
$this->data[$key] = $value; $this->data[$key] = $value;
} }
public function offsetExists($key) public function offsetExists($key): bool
{ {
return isset($this->data[$key]); return isset($this->data[$key]);
} }
public function offsetUnset($key) public function offsetUnset($key): void
{ {
unset($this->data[$key]); unset($this->data[$key]);
} }
public function offsetGet($key) public function offsetGet($key): mixed
{ {
return isset($this->data[$key]) ? $this->data[$key] : null; return $this->data[$key] ?? null;
} }
} }

51
src/ComponentRequest.php Normal file
View file

@ -0,0 +1,51 @@
<?php
namespace ctiso;
use ctiso\HttpRequest;
use ctiso\Arr;
class ComponentRequest {
/** @var int */
public $component_id;
/** @var string */
public $component_title;
/** @var HttpRequest */
public $r;
/**
* @param int $c
* @param HttpRequest $r
*/
function __construct($c, HttpRequest $r) {
$this->component_id = $c;
$this->r = $r;
}
/**
* @param string $key
* @param mixed $default
* @return mixed
*/
function get($key, $default = null) {
if ($key == 'active_page') {
return $this->r->get($key);
}
$arr = $this->r->get($key);
if (!is_null($arr)) {
if (is_array($arr)) {
return Arr::get($arr, $this->component_id, $default);
} else {
return $arr;
}
}
return $default;
}
/**
* @return string
*/
function getAction() {
return $this->r->getAction();
}
}

View file

@ -1,26 +1,39 @@
<?php <?php
class Connection_HttpRequest namespace ctiso\Connection;
use ctiso\File;
class HttpRequest
{ {
const POST = "POST"; const POST = "POST";
const GET = "GET"; const GET = "GET";
private $param = array(); // Параметры запроса /** @var array Параметры запроса */
public $data = null; // Содержание private $param = [];
public $url; // Адресс /** @var string Содержание */
public $method; // Метод public $data = null;
/** @var string Адресс */
public $url;
/** @var string Метод */
public $method;
/** @var int */
public $port = 80; public $port = 80;
/** @var string */
public $host = ""; public $host = "";
/** @var ?string */
public $proxy_host = null; public $proxy_host = null;
/** @var ?int */
public $proxy_port = null; public $proxy_port = null;
/** @var string */
public $http_version = 'HTTP/1.1'; public $http_version = 'HTTP/1.1';
function __construct() { function __construct() {
$this->method = self::GET; $this->method = self::GET;
} }
/** /**
* Возвращает заголовок соединения * Возвращает заголовок соединения
* @return string
*/ */
public function getHeader() public function getHeader()
{ {
@ -33,47 +46,58 @@ class Connection_HttpRequest
$result .= $this->data; $result .= $this->data;
return $result; return $result;
} }
/** /**
* Установка параметров запроса * Установка параметров запроса
* @parma string $name * @param string $name
* @parma string $value * @param string $value
*/ */
public function setParameter($name, $value) public function setParameter($name, $value): void
{ {
$this->param[$name] = $value; $this->param[$name] = $value;
} }
/** /**
* Метод запроса GET или POST * Метод запроса GET или POST
* @param string $method
*/ */
public function setMethod($method) public function setMethod($method): void
{ {
$this->method = $method; $this->method = $method;
} }
public function setUrl($url) /**
* Установка URL
* @param string $url
*/
public function setUrl($url): void
{ {
$this->url = $url; $this->url = $url;
$this->host = parse_url($this->url, PHP_URL_HOST); $host = parse_url($this->url, PHP_URL_HOST);
if (!$host) {
throw new \RuntimeException("Не удалось получить хост");
}
$this->host = $host;
} }
public function getUrl() public function getUrl(): string
{ {
return $this->url; return $this->url;
} }
/** /**
* Содержание запроса * Содержание запроса
* @param string $data
*/ */
public function setContent($data) public function setContent($data): void
{ {
$this->setParameter ("Content-length", strlen($data)); $this->setParameter("Content-length", (string)strlen($data));
$this->data = $data; $this->data = $data;
} }
/** /**
* Посылает запрос и возвращает страницу * Посылает запрос и возвращает страницу
* @return string|null
*/ */
public function getPage() public function getPage()
{ {
@ -86,7 +110,7 @@ class Connection_HttpRequest
$header = $this->getHeader(); $header = $this->getHeader();
fwrite($socket, $header); fwrite($socket, $header);
$result = null; $result = '';
while (! feof($socket)) { while (! feof($socket)) {
$result .= fgets($socket, 128); $result .= fgets($socket, 128);
} }
@ -96,6 +120,12 @@ class Connection_HttpRequest
return null; return null;
} }
/**
* Получение JSON
* @param string $url
* @param array $data
* @return array
*/
static function getJSON($url, $data) { static function getJSON($url, $data) {
$query = http_build_query($data); $query = http_build_query($data);
$q = $url . '?' . $query; $q = $url . '?' . $query;

View file

@ -3,32 +3,43 @@
/** /**
* Обрабатывает HTTP ответ * Обрабатывает HTTP ответ
*/ */
class Connection_HttpResponse namespace ctiso\Connection;
class HttpResponse
{ {
/** @var int */
private $offset; private $offset;
private $param = array (); /** @var array */
private $param = [];
/** @var int */
private $code; private $code;
/** @var string */
public $response; public $response;
/** @var string */
public $version; public $version;
/** @var string */
public $data; public $data;
/**
* @param string $response HTTP ответ
*/
public function __construct($response) public function __construct($response)
{ {
$this->offset = 0; $this->offset = 0;
$this->response = $response; $this->response = $response;
$this->parseMessage(); $this->parseMessage();
} }
/** /**
* Обработка HTTP ответа * Обработка HTTP ответа
*/ */
private function parseMessage() private function parseMessage(): void
{ {
$http = explode(" ", $this->getLine()); $http = explode(" ", $this->getLine());
$this->version = $http[0]; $this->version = $http[0];
$this->code = $http[1]; $this->code = (int)$http[1];
$line = $this->getLine(); $line = $this->getLine();
while ($offset = strpos($line, ":")) { while ($offset = strpos($line, ":")) {
$this->param[substr($line, 0, $offset)] = trim(substr($line, $offset + 1)); $this->param[substr($line, 0, $offset)] = trim(substr($line, $offset + 1));
$line = $this->getLine(); $line = $this->getLine();
@ -36,12 +47,12 @@ class Connection_HttpResponse
if (isset($this->param['Transfer-Encoding']) && $this->param['Transfer-Encoding'] == 'chunked') { if (isset($this->param['Transfer-Encoding']) && $this->param['Transfer-Encoding'] == 'chunked') {
//$this->data = substr($this->response, $this->offset); //$this->data = substr($this->response, $this->offset);
$nline = hexdec($this->getLine()); $index = (int)hexdec($this->getLine());
$chunk = array(); $chunk = [];
while ($nline > 0) { while ($index > 0) {
$chunk [] = substr($this->response, $this->offset, $nline); $chunk [] = substr($this->response, $this->offset, $index);
$this->offset += $nline; $this->offset += $index;
$nline = hexdec($this->getLine()); $index = (int)hexdec($this->getLine());
} }
$this->data = implode("", $chunk); $this->data = implode("", $chunk);
@ -53,32 +64,33 @@ class Connection_HttpResponse
/** /**
* Обработка строки HTTP ответа * Обработка строки HTTP ответа
*/ */
private function getLine() private function getLine(): string
{ {
$begin = $this->offset; $begin = $this->offset;
$offset = strpos($this->response, "\r\n", $this->offset); $offset = strpos($this->response, "\r\n", $this->offset);
$result = substr($this->response, $begin, $offset - $begin); $result = substr($this->response, $begin, $offset - $begin);
$this->offset = $offset + 2; $this->offset = $offset + 2;
return $result; return $result;
} }
/** /**
* Значение параметра HTTP ответа * Значение параметра HTTP ответа
* @param string $name Имя параметра
*/ */
public function getParameter($name) public function getParameter($name): string
{ {
return $this->param[$name]; return $this->param[$name];
} }
public function getData() public function getData(): string
{ {
return $this->data; return $this->data;
} }
/** /**
* Состояние * Состояние
*/ */
public function getCode() public function getCode(): int
{ {
return $this->code; return $this->code;
} }

View file

@ -1,112 +1,139 @@
<?php <?php
function forceUrl($name) namespace ctiso\Controller;
{
if (is_callable($name)) { use Exception;
return call_user_func($name); use ctiso\Path;
} use ctiso\Url;
return $name; use ctiso\Model\Factory;
} use ctiso\HttpRequest;
use ctiso\Settings;
use ctiso\Database;
use ctiso\View\Composite;
use ctiso\View\View;
use App\Controller\State;
/** /**
* Контроллер страниц * Контроллер страниц
*/ */
class Controller_Action class Action implements ActionInterface
{ {
const TEMPLATE_EXTENSION = ".html"; // Расширение для шаблонов const TEMPLATE_EXTENSION = ".html"; // Расширение для шаблонов
const ACTION_PREFIX = "action"; // Префикс для функций действий const ACTION_PREFIX = "action"; // Префикс для функций действий
public $jsPath; // Глобальный путь к скриптам // Параметры устанавливаются при создании контроллера
public $themePath; // Глобальный путь к текущей теме public string $name = ''; // Имя модуля
/** @var \ctiso\Controller\Front */
public $front;
// Параметры устанавливаются при создании контроллера public string $modulePath = ''; // Путь к модулю
public $name = ''; // Имя модуля public string $moduleTitle = '';
public $viewPath = null; // Путь к шаблонам контроллера
public $viewPathPrefix = null; // Путь к шаблонам контроллера
public $moduleTitle = ""; public string $viewPathPrefix = ''; // Путь к шаблонам контроллера
/** /** Соединение с базой данных */
* Соединение с базой данных public Database $db;
*/
public $db;
// Фильтры // Фильтры
public $access = null; // Обьект хранит параметры доступа /** @var ?\ctiso\Filter\ActionAccess Обьект хранит параметры доступа */
public $logger = null; // Обьект для ведения лога public $access = null;
/** @var ?\ctiso\Filter\ActionLogger Обьект для ведения лога */
public $logger = null;
/** @var Factory Обьект для создания моделей */
private $factory = null;
private $factory = null; // Ссылка на обьект создания модели /** @var ?Url Параметры для ссылки */
private $helpers = array(); // Помошники для действий public $part = null;
public $param = array(); // Параметры для ссылки
public $_registry/*: Registry*/; // Ссылка на реестр /** @var \ctiso\Registry Ссылка на настройки */
public $_shortcut; public $config;
public $modulePrefix = ''; /** @var \ctiso\Role\User Обьект пользователя */
public $iconPath = ''; public $user;
// Для Widgets
public $view = null;
public $childNodes = array();
public $ctrlValues = array();
public $childViews = array();
function __construct() { function __construct() {
$this->part = new Url();
} }
public function setUp() { public function setUp(): void {
} }
/**
* Загрузка файла настроек
* @param string $name
* @return array
*/
public function loadConfig($name) { public function loadConfig($name) {
$filename = Shortcut::getUrl('config', $name); $basePath = $this->config->get('site', 'path');
$filename = Path::join($basePath, 'modules', $name);
$settings = []; $settings = [];
if (file_exists($filename)) { if (file_exists($filename)) {
include($filename); $settings = include($filename);
} else { } else {
throw new Exception('Невозможно загрузить файл настроек ' . $name); throw new Exception('Невозможно загрузить файл настроек ' . $name);
} }
return $settings; return $settings;
} }
public function getConnection() public function getConnection(): Database
{ {
return $this->db; return $this->db;
} }
/**
* Путь к установке модуля
* @param string $name
* @return string
*/
public function installPath($name) public function installPath($name)
{ {
return Path::join(CMS_PATH, "modules", $name); $basePath = $this->config->get('system', 'path');
return Path::join($basePath, "modules", $name);
} }
public function addSuggest(View_View $view, $name) /**
{ * Добавляет подсказки
$suggest = array(); * @param View $view
$file = Path::join($this->viewPath, 'help', $name . '.suggest'); * @param string $name
*/
public function addSuggest(View $view, $name): void {
$file = Path::join($this->modulePath, 'help', $name . '.suggest');
if (file_exists($file)) { if (file_exists($file)) {
include($file); $view->suggestions = include($file);
$view->suggestions = $suggest;
} }
} }
function findIcon($icon, $size) /**
{ * Поиск иконки
return Path::join($this->iconPath, $size . 'x' . $size, $icon . '.png'); * @param string $icon
* @param int $size
* @return string Путь к иконке
*/
function findIcon($icon, $size) {
$webPath = $this->config->get('site', 'web');
return Path::join($webPath, 'icons', $size . 'x' . $size, $icon . '.png');
} }
/** /**
* Создает представление * Создает представление
* @param $name String * @param string $name
* @param $viewClass String * @param class-string $viewClass
* @return View_Composite * @return Composite
*/ */
public function getView($name, $viewClass = 'View_Composite') public function getView($name, $viewClass = Composite::class)
{ {
$file = $name . self::TEMPLATE_EXTENSION; $file = $name . self::TEMPLATE_EXTENSION;
$list = array( $basePath = $this->config->get('system', 'path');
Path::join($this->viewPath, TEMPLATES, $this->viewPathPrefix) => Path::join(WWW_PATH, "modules", $this->name, TEMPLATES, $this->viewPathPrefix), $webPath = $this->config->get('system', 'web');
Path::join(CMS_PATH, "templates") => Path::join(WWW_PATH, "templates")
); $list = [
Path::join($this->modulePath, 'templates', $this->viewPathPrefix)
=> Path::join($webPath, "modules", $this->name, 'templates', $this->viewPathPrefix),
Path::join($basePath, "templates") => Path::join($webPath, "templates")
];
// Поиск файла для шаблона // Поиск файла для шаблона
foreach($list as $ospath => $path) { foreach($list as $ospath => $path) {
@ -114,34 +141,41 @@ class Controller_Action
if(file_exists($template)) { break; } if(file_exists($template)) { break; }
} }
$tpl/*: View_Composite*/ = new $viewClass($template); /** @var \ctiso\View\Composite */
$tpl = new $viewClass($template);
$tpl->config = $this->config;
$assets = Path::join(enableHttps(WWW_PATH), "assets", "css"); $stylePath = Path::join($webPath, "assets", "css");
$tpl->set('icons', $this->iconPath); // Путь к файлам текущей темы $iconsPath = Path::join($webPath, 'icons');
$tpl->set('media', $this->themePath); // Путь к файлам текущей темы $scriptPath = Path::join($webPath, 'assets');
$tpl->set('assets', $assets);
$tpl->set('script', $this->jsPath); // Путь к файлам скриптов $tpl->set('icons', $iconsPath); // Путь к файлам текущей темы
$tpl->set('assets', $stylePath);
$tpl->set('script', $scriptPath); // Путь к файлам скриптов
$tpl->set('template', $path); // Путь к файлам текущего шаблона $tpl->set('template', $path); // Путь к файлам текущего шаблона
$tpl->setAlias(array( $tpl->setAlias([
'assets' => $assets, 'assets' => $stylePath,
'icons' => $this->iconPath, 'icons' => $iconsPath,
'script' => $this->jsPath, 'script' => $scriptPath,
// Для media и template поиск происходит как для файлов шаблонов // Для media и template поиск происходит как для файлов шаблонов
'media' => $list, 'media' => $list,
'template' => $list 'template' => $list
)); ]);
$tpl->loadImports(Path::skipExtension($template) . ".import");
$this->addSuggest($tpl, $name); $this->addSuggest($tpl, $name);
return $tpl; return $tpl;
} }
/**
* @template T
* @param class-string<T> $name
* @return T
*/
public function getModel($name) public function getModel($name)
{ {
if (!$this->factory) { if (!$this->factory) {
$this->factory = new Model_Factory($this->db, $this->_registry); $this->factory = new Factory($this->db, $this->config, $this->user);
} }
return $this->factory->getModel($name); return $this->factory->getModel($name);
} }
@ -151,125 +185,118 @@ class Controller_Action
* Т.к действия являются методами класса то * Т.к действия являются методами класса то
* 1. Можно переопределить действия * 1. Можно переопределить действия
* 2. Использовать наследование чтобы добавить к старому обработчику новое поведение * 2. Использовать наследование чтобы добавить к старому обработчику новое поведение
* @param $request Обьект запроса * @param HttpRequest $request запроса
* @return View|string
*/ */
public function preprocess(HttpRequest $request) public function preProcess(HttpRequest $request)
{ {
$action = self::ACTION_PREFIX . ucfirst($request->getAction()); $action = self::ACTION_PREFIX . ucfirst($request->getAction());
if (!method_exists($this, $action)) { if (!method_exists($this, $action)) {
$action = "actionIndex"; $action = "actionIndex";
} }
$view = $this->forward($action, $request); $view = $this->forward($action, $request);
if ($view instanceof View_View) { if ($view instanceof View) {
$view->active_module = get_class($this); $view->active_module = get_class($this);
$view->module_action = $action; $view->module_action = $action;
}
return $view;
}
public function execute(HttpRequest $request)
{
$result = $this->preprocess($request);
if (!empty($result)) {
$this->view = $result;
} }
$text = $this->render(); return $view;
return $text;
} }
/**
* Выполнение действия
* @param HttpRequest $request
* @return View|string|false
*/
public function execute(HttpRequest $request)
{
$result = $this->preProcess($request);
return $result;
}
/**
* Перенаправление на другой контроллер
* @param string $action
* @param HttpRequest $args
* @return mixed
*/
public function forward($action, HttpRequest $args) { public function forward($action, HttpRequest $args) {
$value = call_user_func(array($this, $action), $args); $actionFn = [$this, $action];
if (!is_callable($actionFn)) {
return false;
}
$value = call_user_func($actionFn, $args);
return $value; return $value;
} }
/** /**
* Страница по умолчанию * Страница по умолчанию
* @param HttpRequest $request
* @return string|false
*/ */
public function actionIndex(HttpRequest $request) { public function actionIndex(HttpRequest $request) {
return ""; return "";
} }
public function postUrl($name, $param) /**
{ * Добавление части ссылки
$uri = array_merge(array('module' => * @param string $key
strtr($this->modulePrefix . strtolower(get_class($this)), array('module_' => '')), "action" => $name), * @param string $value
$this->param, $param); */
public function addUrlPart($key, $value): void {
return "?" . http_build_query($uri); $this->part->addQueryParam($key, $value);
} }
/** /**
* Генерация ссылки c учетом прав пользователя на ссылки * Генерация ссылки c учетом прав пользователя на ссылки
* @param string $name Действие * @param string $actionName Действие
* @param array $param Дополнительные параметры * @param array $param Дополнительные параметры
* 'mode' означает что элемент до отправки обрабатывается javascript * 'mode' означает что элемент до отправки обрабатывается javascript
* @return array|null * @return Url|null
*/ */
public function nUrl($name, array $param = array()) public function nUrl($actionName, array $param = [])
{ {
$access/*: Filter_ActionAccess*/ = $this->access; $access = $this->access;
$url = new Url();
if ($access == null || $access->checkAction($name)) { if ($access == null || $access->checkAction($actionName)) {
return Functions::lcurry(array($this, 'postUrl'), $name, $param); $moduleName = explode("\\", strtolower(get_class($this)));
if (count($moduleName) > 2) {
array_shift($moduleName);
if ($moduleName[0] == $moduleName[1]) {
array_shift($moduleName);
}
}
$param = array_merge(['module' => implode("\\", $moduleName), "action" => $actionName], $param);
$url->setParent($this->part);
$url->setQuery($param);
} }
return null;
}
public function fUrl($name, array $param = array()) return $url;
{
return forceUrl($this->nUrl($name, $param));
}
/**
* Добавляет параметр для всех ссылок создаваемых функцией nUrl, aUrl
*/
public function addParameter($name, $value)
{
if ($value) {
$this->param [$name] = $value;
}
} }
/** /**
* Генерация ссылки на действие контроллера * Генерация ссылки на действие контроллера
* Ajax определяется автоматически mode = ajax используется для смены layout * Ajax определяется автоматически mode = ajax используется для смены layout
* @param $name * @param string $name
* @param array $param * @param array $param
* @return array|null * @return Url|null
* *
* @example ?action=$name&mode=ajax * @example ?action=$name&mode=ajax
* {$param[i].key = $param[i].value} * {$param[i].key = $param[i].value}
*/ */
public function aUrl($name, array $param = array()) public function aUrl($name, array $param = [])
{ {
return $this->nUrl($name, array_merge(array('mode' => 'ajax'), $param)); // FIXME return $this->nUrl($name, array_merge(['mode' => 'ajax'], $param));
}
/**
* Добавление помошника контроллера
*/
public function addHelper($class)
{
$this->helpers [] = $class;
}
/**
* Вызов помошников контроллера
*/
public function callHelpers(HttpRequest $request)
{
$action = self::ACTION_PREFIX . $request->getAction();
foreach ($this->helpers as $helper) {
if (method_exists($helper, $action)) {
return call_user_func(array($helper, $action), $request, $this);
} else {
return $helper->actionIndex($request, $this); // Вместо return response ???
}
}
} }
/** /**
* Загрузка файла класса * Загрузка файла класса
* @deprecated Веместо его нужно использовать автозагрузку
* @param string $path
* @param mixed $setup
* @param string $prefix
* @return mixed
*/ */
public function loadClass($path, $setup = null, $prefix = '') public function loadClass($path, $setup = null, $prefix = '')
{ {
@ -281,6 +308,11 @@ class Controller_Action
throw new Exception("NO CLASS $path"); throw new Exception("NO CLASS $path");
} }
/**
* Загрузка настроек
* @param string $path
* @return array
*/
public function loadSettings($path) public function loadSettings($path)
{ {
$result = new Settings($path); $result = new Settings($path);
@ -288,70 +320,22 @@ class Controller_Action
return $result->export(); return $result->export();
} }
public function setView($name)
{
$this->view = $this->getView($name);
}
/** /**
* Установка заголовка для отображения * Установка идентификатора страницы
*/ * @return int
public function setTitle($title)
{
$this->view->setTitle($title);
}
/**
* Добавление widget к отображению
*/ */
public function addChild(/*Widget*/ $section, $node) function getPageId(HttpRequest $request) {
{ $pageId = time();
$this->childNodes[$section] = $node;
}
public function setValue(/*Widget*/ $name, $value)
{
$this->ctrlValues[$name] = $value;
}
/**
* Добавление дочернего отображения к текущему отображению
*/
public function addView(/*CompositeView*/ $section, $node)
{
$this->childViews[$section] = $node;
}
/**
* Генерация содержания
* Путаница c execute и render
*/
public function render()
{
if ($this->view instanceof View_View) {
$this->view->assignValues($this->ctrlValues);
$node/*: Widgets_Widget*/ = null;
foreach ($this->childNodes as $name => $node) {
$node->make($this);
$this->view->setView($name, $node->view);
}
foreach ($this->childViews as $name => $node) {
$this->view->setView($name, $node);
}
}
return $this->view;
}
function getPageId(HttpRequest $request)
{
$pageId = time();
$request->session()->set('page', $pageId); $request->session()->set('page', $pageId);
return $pageId; return $pageId;
} }
function checkPageId(HttpRequest $request, $page) /**
* Проверка идентификатора страницы
* @param int $page Идентификатор страницы
* @return bool
*/
function checkPageId(HttpRequest $request, $page)
{ {
if ($request->get('__forced__')) { if ($request->get('__forced__')) {
return true; return true;
@ -362,19 +346,15 @@ class Controller_Action
return $result; return $result;
} }
function _getActionPath() /**
{ * @return State
return new Controller_State('index'); */
function _getActionPath() {
return new State('index');
} }
// Тоже убрать в метод Controller_Model function redirect(string $action): void {
function getActionPath(HttpRequest $request, $action = null) header('location: ' . $action);
{
$this->_getActionPath()->getPath($this, ($action) ? $action : $request->getAction());
}
function redirect($action) {
header('location: ' . $this->fUrl($action));
exit(); exit();
} }
} }

View file

@ -0,0 +1,28 @@
<?php
namespace ctiso\Controller;
use ctiso\Database;
use ctiso\HttpRequest;
interface ActionInterface {
/**
* Действие может вернуть Шаблон или строку
*
* @param HttpRequest $request
* @return \ctiso\View\View|string|false
*/
function execute(HttpRequest $request);
function getConnection(): Database;
/**
* @param string $name
* @param class-string<\ctiso\View\View> $class
* @return \ctiso\View\View
*/
function getView($name, $class);
/**
* @param string $key
* @param string $value
*/
function addUrlPart($key, $value): void;
}

View file

@ -1,72 +1,90 @@
<?php <?php
function replaceContent($match) { namespace ctiso\Controller;
$result = phptal_component(htmlspecialchars_decode($match[3]));
return $result;
}
function applyComponents($text) { use ctiso\HttpRequest;
return preg_replace_callback('/<(\w+)(\s+[a-zA-Z\-]+=\"[^\"]*\")*\s+tal:replace="structure\s+component:([^\"]*)"[^>]*>/u', 'replaceContent', $text); use ctiso\ComponentRequest;
} use ctiso\Arr;
use ctiso\Path;
class ComponentRequest { use ctiso\File;
public $component_id; use ctiso\Form\Form;
public $component_title; use ctiso\View\Composite;
public $r; use ctiso\Database;
use ctiso\Collection;
function __construct($c, HttpRequest $r) { use ctiso\Registry;
$this->component_id = $c; use ctiso\Controller\SiteInterface;
$this->r = $r; use ctiso\Database\PDOStatement;
} use PHPTAL;
use PHPTAL_PreFilter_Normalize;
function get($key, $default = null) { use ctiso\View\JsonView;
if ($key == 'active_page') {
return $this->r->get($key);
}
$arr = $this->r->get($key);
if ($arr !== NULL) {
if (is_array($arr)) {
return Arr::get($arr, $this->component_id, $default);
} else {
return $arr;
}
}
return $default;
}
function getAction() {
return $this->r->getAction();
}
}
/** /**
* Класс компонента * Класс компонента
*/ */
class Controller_Component class Component
{ {
public $viewPath = array(); /** @var string[] */
public $webPath = array(); public $viewPath = [];
/** @var string[] */
public $webPath = [];
/** @var ?string */
public $template = null; public $template = null;
public $templatePath; public string $templatePath;
/** @var int */
public $component_id; public $component_id;
/** @var string */
public $component_title; public $component_title;
/** @var string */
public $COMPONENTS_WEB; public $COMPONENTS_WEB;
public $registry/*: Settings*/; public Registry $config;
public $db/*: Database*/; public Database $db;
public $parameter/*: Collection*/; public Collection $parameter;
/** @var string */
public $output = 'html';
/** @var string */
public $module; public $module;
/** @var string */
public $item_module; public $item_module;
function before() { /**
* @var SiteInterface $site
*/
public $site;
function before(): void {
} }
function get($request, $key, $default) { /**
* @param string $match
* @return string
*/
static function replaceContent($match) {
return \ctiso\Tales::phptal_component(htmlspecialchars_decode($match[3]));
} }
/**
* @param string $text
* @return string
*/
static function applyComponents($text) {
$callback = fn($x) => self::replaceContent($x);
return preg_replace_callback('/<(\w+)(\s+[a-zA-Z\-]+=\"[^\"]*\")*\s+tal:replace="structure\s+component:([^\"]*)"[^>]*>/u',
$callback, $text);
}
/**
* Выполняет запрос компонента и возвращает результат
* Результат может быть строкой или View для обычных компонентов, или массивом для использования в сервисах
*
* @param HttpRequest $request
* @param bool $has_id
* @return mixed
*/
function execute(HttpRequest $request, $has_id = true) { function execute(HttpRequest $request, $has_id = true) {
$crequest = new ComponentRequest($this->component_id, $request); $crequest = new ComponentRequest($this->component_id, $request);
@ -76,56 +94,74 @@ class Controller_Component
} else { } else {
$action = 'action' . ucfirst($_action); $action = 'action' . ucfirst($_action);
} }
$this->before(); $this->before();
if (method_exists($this, $action)) { $actionMethod = [$this, $action];
return call_user_func(array($this, $action), $crequest); if (is_callable($actionMethod)) {
} else { return call_user_func($actionMethod, $crequest);
return $this->actionIndex($crequest);
} }
return $this->actionIndex($crequest);
} }
public function getTemplateName($_registry/*: Settings*/) { /**
return (isset($_COOKIE['with_template']) && preg_match('/^[\w\d-]{3,20}$/', $_COOKIE['with_template'])) * Получить имя шаблона
? $_COOKIE['with_template'] : ($_registry ? $_registry->readKey(array('system', 'template')) : 'modern'); * @param Registry $_registry
} * @return string
*/
public function getTemplateName($_registry) {
return (isset($_COOKIE['with_template']) && preg_match('/^[\w\d-]{3,20}$/', $_COOKIE['with_template']))
? $_COOKIE['with_template'] : ($_registry ? $_registry->get('site', 'template') : 'modern');
}
/**
* Получить шаблон
* @param string $name
* @return PHPTAL|JsonView
*/
public function getView($name) public function getView($name)
{ {
// if ($this->output === 'json') {
$registry/*: Settings*/ = $this->registry; return new JsonView($name);
$template = ($this->template) ? $this->template : $this->getTemplateName($registry); }
/** @var Registry $config */
$config = $this->config;
$default = $config->get('site', 'template');
$template = ($this->template) ? $this->template : $this->getTemplateName($config);
$selected = null; $selected = null;
$tpl = null;
foreach ($this->viewPath as $index => $viewPath) { foreach ($this->viewPath as $index => $viewPath) {
// Загружать шаблон по умолчанию если не найден текущий // Загружать шаблон по умолчанию если не найден текущий
$dir = Path::join($this->viewPath[$index], 'templates', $template); $dir = Path::join($this->viewPath[$index], 'templates', $template);
if(is_dir($dir)) { if (is_dir($dir)) {
$tpl = new PHPTAL(Path::join($this->viewPath[$index], 'templates', $template, $name)); $tpl = new PHPTAL(Path::join($this->viewPath[$index], 'templates', $template, $name));
$tpl->setPhpCodeDestination(PHPTAL_PHP_CODE_DESTINATION); $tpl->setPhpCodeDestination(PHPTAL_PHP_CODE_DESTINATION);
$selected = $index; $selected = $index;
break; break;
} }
} }
if ($selected === null) { if ($selected === null) {
// Последний вариант viewPath, путь к папке компонента // Последний вариант viewPath, путь к папке компонента
$selected = count($this->viewPath) - 1; $selected = count($this->viewPath) - 1;
$tpl = new PHPTAL(Path::join($this->viewPath[$selected], 'templates', 'modern', $name)); $tpl = new PHPTAL(Path::join($this->viewPath[$selected], 'templates', 'modern', $name));
$tpl->setPhpCodeDestination(PHPTAL_PHP_CODE_DESTINATION); $tpl->setPhpCodeDestination(PHPTAL_PHP_CODE_DESTINATION);
$template = 'modern'; $template = 'modern';
} }
$tpl->stripComments(true); $tpl->stripComments(true);
$tpl->addPreFilter(new PHPTAL_PreFilter_Normalize()); $tpl->addPreFilter(new \PHPTAL_PreFilter_Normalize());
$tpl->set('common', Path::join(WWW_PATH, '../', 'common')); $tpl->set('common', Path::join($this->config->get('system', 'web'), '../', 'common'));
$tpl->set('script', Path::join(WWW_PATH, 'js')); $tpl->set('script', Path::join($this->config->get('system', 'web'), 'js'));
$tpl->set('media', Path::join(TEMPLATE_WEB, $template)); $tpl->set('media', Path::join($this->config->get('system', 'templates.web'), $template));
if ($registry) {
$tpl->set('site_template', SITE_WWW_PATH . '/templates/' . $registry->readKey(array('system', 'template'))); if ($default) {
$tpl->set('site_template', Path::join($this->config->get('site', 'templates.web'), $default));
} }
$tpl->set('base', SITE_WWW_PATH); $tpl->set('base', $this->config->get('site', 'web'));
$tpl->set('component_base', $this->webPath[$selected]); $tpl->set('component_base', $this->webPath[$selected]);
$tpl->set('component', Path::join($this->webPath[$selected], 'templates', $template)); $tpl->set('component', Path::join($this->webPath[$selected], 'templates', $template));
@ -134,23 +170,36 @@ class Controller_Component
return $tpl; return $tpl;
} }
/**
* Возвращает путь к шаблону по умолчанию
* @return string
*/
function _getDefaultPath() { function _getDefaultPath() {
return $this->viewPath[count($this->viewPath) - 1]; return $this->viewPath[count($this->viewPath) - 1];
} }
/**
* Возвращает путь к шаблону
* @param string $name
* @return string
*/
public function getTemplatePath($name) { public function getTemplatePath($name) {
$registry/*: Settings*/ = $this->registry; $registry = $this->config;
// Брать настройки из куков если есть // Брать настройки из куков если есть
$template = ($this->template) ? $this->template : $this->getTemplateName($registry); $template = ($this->template) ? $this->template : $this->getTemplateName($registry);
foreach ($this->viewPath as $index => $viewPath) { foreach ($this->viewPath as $index => $_) {
if(is_dir(Path::join($this->viewPath[$index], 'templates', $template))) { if(is_dir(Path::join($this->viewPath[$index], 'templates', $template))) {
return Path::join($this->viewPath[$index], 'templates', $template, $name); return Path::join($this->viewPath[$index], 'templates', $template, $name);
} }
} }
return Path::join($this->viewPath[count($this->viewPath) - 1], 'templates', 'modern', $name); return Path::join($this->viewPath[count($this->viewPath) - 1], 'templates', 'modern', $name);
} }
/**
* Возвращает путь к шаблонам
* @return string
*/
public function getTemplateWebPath() public function getTemplateWebPath()
{ {
return Path::join($this->webPath[count($this->webPath) - 1], 'templates', 'modern'); return Path::join($this->webPath[count($this->webPath) - 1], 'templates', 'modern');
@ -158,49 +207,85 @@ class Controller_Component
/** /**
* Создает модель * Создает модель
* @param string $name *
* @return model * @template T
* @param class-string<T> $modelName
* @return T
*/ */
public function getModel($name) public function getModel($modelName)
{ {
$modelName = "Mapper_" . $name;
$model = new $modelName(); $model = new $modelName();
$model->config = $this->config;
$model->db = $this->db; $model->db = $this->db;
return $model; return $model;
} }
public function options($key, $val, $res/*: Database_PDOStatement*/) { /**
$result = array(); * @param string $key
* @param string $val
* @param PDOStatement $res
* @return array{value: string, name: string}[]
*/
public function options(string $key, string $val, $res) {
$result = [];
while($res->next()) { while($res->next()) {
$result[] = array('value' => $res->getString($key), 'name' => $res->getString($val)); $result[] = ['value' => $res->getString($key), 'name' => $res->getString($val)];
} }
return $result; return $result;
} }
public function optionsPair($list, $selected = false) { /**
$result = array(); * @param array $list
* @param bool $selected
* @return array{value: string, name: string, selected: bool}[]
*/
public function optionsPair(array $list, $selected = false) {
$result = [];
foreach ($list as $key => $value) { foreach ($list as $key => $value) {
$result [] = array('value' => $key, 'name' => $value, 'selected' => $key == $selected); $result [] = ['value' => $key, 'name' => $value, 'selected' => $key == $selected];
} }
return $result; return $result;
} }
/**
* Найти файл по пути
* @param string[] $pathList
* @param string $name
* @return string|null
*/
function findFile(array $pathList, string $name) {
foreach($pathList as $item) {
$filename = Path::join($item, $name);
if (file_exists($filename)) {
return $filename;
}
}
return null;
}
/**
* Получить информацию о параметрах
* @return array<mixed>
*/
function getInfo() { function getInfo() {
$filename = Path::join($this->viewPath[count($this->viewPath) - 1], 'install.json'); $filename = Path::join($this->viewPath[count($this->viewPath) - 1], 'install.json');
if (file_exists($filename)) { if (file_exists($filename)) {
$settings = json_decode(File::getContents($filename), true); $settings = json_decode(File::getContents($filename), true);
return $settings; if ($settings) {
return $settings;
}
} }
return array(); return ['parameter' => []];
} }
/** /**
* Генерация интерфейса для выбора галлереи фотографии * Генерация интерфейса для выбора галлереи фотографии
* @param Composite $view
* @param ?\ctiso\Form\OptionsFactory $options
*/ */
public function setParameters($view/*: View_Composite*/) public function setParameters(Composite $view, $options = null): void
{ {
$form = new Form_Form(); $form = new Form();
$options = new Form_OptionFactory($this->db, $this->registry);
$settings = $this->getInfo(); $settings = $this->getInfo();
$form->addFieldList($settings['parameter'], $options); $form->addFieldList($settings['parameter'], $options);
@ -210,72 +295,94 @@ class Controller_Component
$view->component_title = $settings['title']; $view->component_title = $settings['title'];
} }
static function loadComponent($expression, Database $db, $registry/*: Registry*/) /**
* @param \ctiso\Form\OptionsFactory $options
* @return array
*/
public function getFields($options = null) {
$form = new Form();
$settings = $this->getInfo();
$form->addFieldList($settings['parameter'], $options);
return $form->field;
}
/**
* Обьеденить с ComponentFactory
* @param string $expression
* @param SiteInterface $site
* @return Component
*/
static function loadComponent(string $expression, $site)
{ {
$expression = htmlspecialchars_decode($expression); $expression = htmlspecialchars_decode($expression);
$offset = strpos($expression, '?'); $offset = strpos($expression, '?');
$url = parse_url($expression); $url = parse_url($expression);
$arguments = array(); $arguments = [];
if ($offset === false) { $path = $expression;
$path = $expression; if (is_int($offset)) {
} else if (is_int($offset)) {
$path = substr($expression, 0, $offset); $path = substr($expression, 0, $offset);
$query = substr($expression, $offset + 1); $query = substr($expression, $offset + 1);
parse_str($query, $arguments); parse_str($query, $arguments);
} }
$name = $path; $name = $path;
$config = $site->getConfig();
$path = Path::join (BASE_PATH, 'components', $name, $name . '.php'); // FIXME: Если имя для компонента не задано то возвращаем пустой компонент
$className = 'Component_' . $name; // Нужно дополнительно проверить и файл или в autoloader просто не найдет файл копонента
if (!$name) {
return new Component();
}
$component/*: Controller_Component*/ = null; $filename = ucfirst($name);
$path = Path::join ($config->get('site', 'components'), $name, $filename . '.php');
$className = implode("\\", ['Components', ucfirst($name), $filename]);
$component = null;
if (file_exists($path)) { if (file_exists($path)) {
require_once ($path); /** @var Component $component */
$component = new $className();
$component = new $className(); $component->viewPath = [$config->get('site', 'components') . '/' . $name . '/'];
$component->db = $db; $component->webPath = [$config->get('site', 'components.web') . '/' . $name];
$component->registry = $registry; $component->COMPONENTS_WEB = $config->get('site', 'web') . '/components/';
$component->viewPath = array(BASE_PATH . '/components/' . $name . '/');
$component->webPath = array(SITE_WWW_PATH . '/components/' . $name);
$component->COMPONENTS_WEB = SITE_WWW_PATH . '/components/';
} else { } else {
$path = Path::join (COMPONENTS, $name, $name . '.php'); /** @var Component $component */
require_once ($path);
$component = new $className(); $component = new $className();
$component->db = $db; $template = $component->getTemplateName($site->getConfig());
$component->registry = $registry;
$template = $component->getTemplateName($registry); $component->viewPath = [
$component->viewPath = array(
// Сначало ищем локально // Сначало ищем локально
BASE_PATH . '/templates/' . $template . '/_components/' . $name . '/', $config->get('site', 'templates') . '/'. $template . '/_components/' . $name . '/',
BASE_PATH . '/components/' . $name . '/', $config->get('site', 'components') . '/' . $name . '/',
// Потом в общем хранилище // Потом в общем хранилище
CMS_PATH . '/../templates/' . $template . '/_components/' . $name . '/', $config->get('system', 'templates'). '/' . $template . '/_components/' . $name . '/',
COMPONENTS . '/' . $name . '/', $config->get('system', 'components') . '/' . $name . '/',
); ];
if (defined('COMPONENTS_WEB')) { if (defined('COMPONENTS_WEB')) {
$component->webPath = array( $component->webPath = [
// Сначало локально // Сначало локально
SITE_WWW_PATH . '/templates/' . $template . '/_components/' . $name, $config->get('site', 'templates.web') . '/' . $template . '/_components/' . $name,
SITE_WWW_PATH . '/components/' . $name, $config->get('site', 'components.web') . '/' . $name,
// Потом в общем хранилище // Потом в общем хранилище
TEMPLATE_WEB . '/' . $template . '/_components/' . $name, $config->get('system', 'templates.web') . '/' . $template . '/_components/' . $name,
COMPONENTS_WEB . '/' . $name, $config->get('system', 'components.web') . '/' . $name,
); ];
$component->COMPONENTS_WEB = COMPONENTS_WEB; $component->COMPONENTS_WEB = $config->get('system', 'components.web');
} else { } else {
$component->webPath = array('', SITE_WWW_PATH . '/components/' . $name, ''); $component->webPath = ['', $config->get('site', 'components.web') . '/' . $name, '', ''];
} }
} }
// Вынести в отдельную функцию
$db = $site->getDatabase();
$component->db = $db;
$component->config = $site->getConfig();
$component->site = $site;
$stmt = $db->prepareStatement("SELECT * FROM component WHERE code = ?"); $stmt = $db->prepareStatement("SELECT * FROM component WHERE code = ?");
$stmt->setString(1, $expression); $stmt->setString(1, $expression);
$cid = $stmt->executeQuery(); $cid = $stmt->executeQuery();
@ -283,6 +390,7 @@ class Controller_Component
$component->component_id = $cid->getInt('id_component'); $component->component_id = $cid->getInt('id_component');
} else { } else {
$last = $db->getIdGenerator(); $last = $db->getIdGenerator();
$result = null;
if ($last->isBeforeInsert()) { if ($last->isBeforeInsert()) {
$result = $last->getId('component_id_component_seq'); $result = $last->getId('component_id_component_seq');
@ -302,38 +410,40 @@ class Controller_Component
$params = new Collection(); $params = new Collection();
$params->import(array_merge($_GET, $arguments)); $params->import(array_merge($_GET, $arguments));
$component->parameter = $params; $component->parameter = $params;
$component->template = $params->get('template', false); $component->template = $params->get('template', false);
$editor = $component->getEditUrl(); $editor = $component->getEditUrl();
if ($editor) { if ($editor) {
if(class_exists("Controller_Site")){ //Если мы в CMS2 $site->addComponentConfig($editor);
$instance = Controller_Site::getInstance();
$instance->componentsConfig[] = $editor;
} else {
global $componentsConfig;
$componentsConfig[] = $editor;
}
} }
return $component; return $component;
} }
/**
* @return ?array{name: string, url: string}
*/
function getEditUrl() { function getEditUrl() {
return null; return null;
} }
function raw_query($request/*: ComponentRequest*/) /**
* @param ComponentRequest $request
* @return array
*/
function raw_query($request)
{ {
$arr = $request->r->export('get'); $arr = $request->r->export('get');
$param = array(); $param = [];
$parameter/*: Collection*/ = $this->parameter; $parameter = $this->parameter;
foreach($parameter->export() as $key => $value) { foreach($parameter->export() as $key => $value) {
$param[$key] = $value; $param[$key] = $value;
} }
$data = array(); $data = [];
foreach($arr as $key => $value) { foreach($arr as $key => $value) {
if (is_array($value)) { if (is_array($value)) {
$data[$key] = Arr::get($value, $this->component_id); $data[$key] = Arr::get($value, $this->component_id);
@ -346,7 +456,12 @@ class Controller_Component
} }
function query($request/*: ComponentRequest*/, $list) /**
* @param ComponentRequest $request
* @param array $list
* @return string
*/
function query($request, $list)
{ {
$arr = $request->r->export('get'); $arr = $request->r->export('get');
@ -359,14 +474,27 @@ class Controller_Component
} }
/** /**
* @deprecated В CMS2 перенесено в контроллер сайта! * @param string $name
*/ * @param string $path
* @param array $shim
function addRequireJsPath($name, $path, $shim = null) { */
Controller_Site::addRequireJsPath($name, $path, $shim); function addRequireJsPath($name, $path, $shim = null): void {
$this->site->addRequireJsPath($name, $path, $shim);
} }
function actionIndex($request/*: ComponentRequest*/) { /**
* @param ComponentRequest $request
* @return mixed
*/
function actionIndex($request) {
return "";
}
/**
* @param HttpRequest $request
* @return array
*/
function getDefaultPageEnvironment($request) {
return [];
} }
} }

View file

@ -4,107 +4,119 @@
* Первичный контроллер контроллер страниц * Первичный контроллер контроллер страниц
* @package system.controller * @package system.controller
*/ */
class Controller_Front extends Controller_Action namespace ctiso\Controller;
use ctiso\Controller\Action;
use ctiso\Registry;
use ctiso\Database;
use ctiso\Filter\ActionAccess;
use ctiso\Filter\ActionLogger;
use ctiso\Path;
use ctiso\UserMessageException;
use ctiso\HttpRequest;
use ctiso\Role\User;
class Front extends Action
{ {
/** @var Shortcut */ /**
protected $shortcut; // Ярлык к модулю * Параметр по которому выбирается модуль
protected $_param; // Параметр по которому выбирается модуль * @var string
protected $default; // Значение параметра по умолчанию */
protected $_param;
/**
* Значение параметра по умолчанию
* @var string
*/
protected $default;
protected $modules = array(); /** @var array<string, Action> */
protected $modules = [];
/** /**
* @param Settings $_registry * @param string $default
* @param Shortcut $_shortcut
*/ */
public function __construct(Settings $_registry, $_shortcut) // $db, $installer, $shortcut public function __construct(Database $db, Registry $config, User $user, $default) {
{
parent::__construct(); parent::__construct();
$registry = $_registry; $this->config = $config;
$this->_registry = $_registry; $this->db = $db;
$this->_shortcut = $_shortcut; // $cc->newShortcut(); $this->user = $user;
$this->default = $default;
$dsn = $registry->readKey(array('system', 'dsn'));
$this->db = Database::getConnection($dsn); // $cc->newConnection();
} }
public function isLoaded($name) /**
* Проверяет загружен ли модуль
* @param string $name Имя модуля
* @return bool
*/
public function isLoaded($name): bool
{ {
return isset($this->modules[$name]); return isset($this->modules[$name]);
} }
/** /**
* Создает экземпляр модуля и выполняет действия для него * Создает экземпляр модуля и выполняет действия для него
* @param string $name Имя модуля * @param string $name Имя модуля
* @param request $request Имя модуля * @param HttpRequest $request
* @return string * @return string
*/ */
public function loadModule($name, Collection $request, $controller = false) public function loadModule($name, HttpRequest $request)
{ {
if ($this->isLoaded($name)) { if ($this->isLoaded($name)) {
$module = $this->modules[$name]; $module = $this->modules[$name];
return $module->access->execute($request); return $module->access->execute($request);
} }
$suffix = ($controller) ? $controller : $name; $parts = explode('\\', $name);
$moduleFile = Shortcut::getUrl($this->shortcut, $name, $suffix); // ModuleLoader (2)
$module = $this->loadClass($moduleFile, null, 'Module_'); $config = $this->config;
if ($module) {
// Инициализация модуля
$module->front = $this;
$module->viewPath = Shortcut::getUrl('modulepath', $name);
$module->name = $name;
$module->param = $this->param; $moulesPath = Path::join($config->get('system', 'path'), 'modules');
// $logPath = Path::join($config->get('site', 'path'), $config->get('system', 'access.log'));
$module->_registry = $this->_registry;
$module->_shortcut = $this->_shortcut;
$module->iconPath = $this->iconPath; // -> Registry $first = $parts[0];
$module->themePath = $this->themePath; // -> Registry $second = (count($parts) >= 2) ? $parts[1] : $parts[0];
$module->jsPath = $this->jsPath; // -> Registry
$module->db = $this->db;
// Не для всех приложений нужно вести лог действий
// Ведение лога
$logger = $this->loadClass(__DIR__ . '/../Filter/ActionLogger.php', $module, 'Filter_');
$logger->before = $this->loadSettings(Shortcut::getUrl('logger', $name));
// Управление доступом
$module->access = $this->loadClass(__DIR__ . '/../Filter/ActionAccess.php', $logger, 'Filter_');
$module->access->name = $suffix;
$module->access->access = $this->loadSettings(Shortcut::getUrl('access', $name));
$module->setUp(); $ucname = ucfirst($first);
$ucpart = ucfirst($second);
$this->modules[$name] = $module;
$result = $module->access->execute($request); $moduleClass = "Modules\\$ucname\\$ucpart";
return $result; /** @var Action $module */
} $module = new $moduleClass();
return null; // throw new FileNotFoundException();
// Инициализация модуля
$modPath = Path::join($moulesPath, $first);
$module->modulePath = $modPath;
$module->name = $name;
//
$module->config = $this->config;
$module->db = $this->db;
$module->user = $this->user;
$module->front = $this;
// Ведение лога
$logger = new ActionLogger($module, $logPath, $this->user);
$filename = Path::join($modPath, 'filters', 'logger.json');
$logger->before = $this->loadSettings($filename);
// Управление доступом
$module->access = new ActionAccess($logger, $this->user);
$module->access->access = $this->loadSettings(Path::join($modPath, 'filters', 'access.json'));
$module->setUp();
$this->modules[$name] = $module;
$result = $module->access->execute($request);
return $result;
} }
public function setParameter($shortcut, $param, $name)
{
$this->shortcut = $shortcut;
// Параметр
$this->_param = $param;
$this->default = $name;
}
public function execute(HttpRequest $request) public function execute(HttpRequest $request)
{ {
$name = explode("_", $request->get($this->_param, $this->default)); $name = $request->getString('module', $this->default);
if (count($name) >= 2) { try {
$controller = $name[1]; return $this->loadModule($name, $request);
} else {
$controller = false;
}
try{
return $this->loadModule($name[0], $request, $controller);
} catch (UserMessageException $ex) { //Исключение с понятным пользователю сообщением } catch (UserMessageException $ex) { //Исключение с понятным пользователю сообщением
$mode = $request->get('mode'); $mode = $request->get('mode');
if($mode == 'ajax' || $mode == 'json'){ if ($mode == 'ajax' || $mode == 'json') {
return json_encode(['result'=>'fail', 'message'=> $ex->userMessage]); return json_encode(['result'=>'fail', 'message'=> $ex->userMessage]);
} else { } else {
return $ex->userMessage; return $ex->userMessage;

View file

@ -1,36 +1,67 @@
<?php <?php
class Controller_Installer namespace ctiso\Controller;
use ctiso\Settings;
use ctiso\Path;
use ctiso\Database\JsonInstall;
use ctiso\Database\Manager;
class Installer
{ {
/** @var Manager */
protected $db_manager; protected $db_manager;
/** @var callable */
protected $installPath; protected $installPath;
public $_registry; /** @var Settings */
public $_registry;
public function __construct(Settings $_registry) public function __construct(Settings $_registry)
{ {
$this->_registry = $_registry; $this->_registry = $_registry;
} }
public function setUp($db_manager, $installPath) /**
* Устанавливает параметры
* @param Manager $db_manager
* @param callable $installPath
*/
public function setUp($db_manager, $installPath): void
{ {
$this->db_manager = $db_manager; $this->db_manager = $db_manager;
$this->installPath = $installPath; $this->installPath = $installPath;
} }
/**
* Получение пути к файлу install.json
* @param string $name
* @return string
*/
function getSetupFile($name) function getSetupFile($name)
{ {
$setup = Path::join(call_user_func($this->installPath, $name), "install.json"); $setup = Path::join(call_user_func($this->installPath, $name), "install.json");
return $setup; return $setup;
} }
function getUninstallFile($name){ /**
* Получение пути к файлу unisntall.json
* @param string $name
* @return string
*/
function getUninstallFile($name)
{
return Path::join(call_user_func($this->installPath, $name), "sql", "uninstall.json"); return Path::join(call_user_func($this->installPath, $name), "sql", "uninstall.json");
} }
// Проверка версии обновления /**
* Проверка версии обновления
* @param string $name
* @return bool
*/
function isChanged($name) // Информация о модулях function isChanged($name) // Информация о модулях
{ {
$item = $this->_registry->readKey(array($name)); $item = $this->_registry->get($name);
if ($item) { if ($item) {
$setup = $this->getSetupFile($name); $setup = $this->getSetupFile($name);
if (file_exists($setup) && (filemtime($setup) > $item['time'])) { if (file_exists($setup) && (filemtime($setup) > $item['time'])) {
@ -41,10 +72,18 @@ class Controller_Installer
return true; return true;
} }
/**
* Устанавливает SQL
* @param array $sql
* @param string $version_new
* @param string $version_old
* @param string $name
* @return array
*/
function installSQL(array $sql, $version_new, $version_old, $name) function installSQL(array $sql, $version_new, $version_old, $name)
{ {
$result = []; $result = [];
$json_installer = new Database_JsonInstall($this->db_manager); $json_installer = new JsonInstall($this->db_manager);
foreach ($sql as $version => $install) { foreach ($sql as $version => $install) {
if (version_compare($version, $version_new, "<=") && version_compare($version, $version_old, ">")) { if (version_compare($version, $version_new, "<=") && version_compare($version, $version_old, ">")) {
$file = Path::join(call_user_func($this->installPath, $name), "sql", $install); $file = Path::join(call_user_func($this->installPath, $name), "sql", $install);
@ -55,60 +94,78 @@ class Controller_Installer
return $result; return $result;
} }
function uninstall($name){ /**
* @param string $name
* @return void
*/
function uninstall($name): void
{
$uninstall = $this->getUninstallFile($name); $uninstall = $this->getUninstallFile($name);
if (file_exists($uninstall)) { if (file_exists($uninstall)) {
$json_installer = new Database_JsonInstall($this->db_manager); $json_installer = new JsonInstall($this->db_manager);
$json_installer->install($uninstall,null); $json_installer->install($uninstall, null);
} }
$this->_registry->removeKey($name); $this->_registry->removeKey($name);
$this->_registry->write(); $this->_registry->write();
} }
// Устанавливает обновления если есть /**
function doUpdates($name, $force = false) // Установка модуля * Устанавливает обновления если есть
* @param string $name
* @param bool $force
* @return array
*/
function doUpdates($name, $force = false)
{ {
$result = array(); $result = [];
$setup = $this->getSetupFile($name); $setup = $this->getSetupFile($name);
if (file_exists($setup) && ($this->isChanged($name) || $force)) { if (file_exists($setup) && ($this->isChanged($name) || $force)) {
$registry = $this->_registry; $registry = $this->_registry;
$settings = new Settings($setup); $settings = new Settings($setup);
$settings->read(); $settings->read();
$item = $registry->readKey(array($name)); $item = $registry->get($name);
$version_new = $settings->get('version'); $version_new = $settings->get('version');
if ($item) { if ($item) {
$version_old = $item['version']; $version_old = $item['version'];
} else { } else {
$version_old = "0.0"; $version_old = "0.0";
$registry->writeKey(array($name), array()); $registry->writeKey([$name], []);
} }
if (version_compare($version_old, $settings->get('version'), "!=")) { if (version_compare($version_old, $settings->get('version'), "!=")) {
$sql = $settings->get('sql'); $sql = $settings->get('sql');
if (is_array($sql)) { if (is_array($sql)) {
$res = $this->installSQL($sql, $version_new, $version_old, $name); $res = $this->installSQL($sql, $version_new, $version_old, $name);
if($res){ if ($res) {
$result[]=$res; $result[] = $res;
} }
} }
}
// Обновление версии меню // Обновление версии меню
$registry->removeKey($name); $registry->removeKey($name);
$registry->writeKey(array($name), $settings->get('settings')); $registry->set($name, [
$registry->writeKey(array($name), 'version' => $version_new,
array('version' => $version_new, 'time' => filemtime($setup)
'time' => filemtime($setup))); ]);
} // $registry->writeKey([$name], $settings->export());
$registry->write(); $registry->write();
} }
return $result; return $result;
} }
function install($dbinit_path, $dbfill_path = null) { /**
$json_installer = new Database_JsonInstall($this->db_manager); * Устанавливает базу данных
* @param string $dbinit_path
* @param string|null $dbfill_path
*/
function install($dbinit_path, $dbfill_path = null): void
{
$json_installer = new JsonInstall($this->db_manager);
$json_installer->install($dbinit_path, $dbfill_path); $json_installer->install($dbinit_path, $dbfill_path);
} }
} }

View file

@ -1,19 +1,33 @@
<?php <?php
class Controller_Request { namespace ctiso\Controller;
use ctiso\HttpRequest;
class Request {
/** @var HttpRequest */
public $r; public $r;
/** @var string */
public $id; public $id;
/**
* @param HttpRequest $request
* @param string $id
*/
function __construct($request, $id) { function __construct($request, $id) {
$this->r = $request; $this->r = $request;
$this->id = $id; $this->id = $id;
} }
/**
* @param string $name
* @param mixed $def
* @return mixed
*/
function get($name, $def = null) { function get($name, $def = null) {
$v = $this->r->get($name); $v = $this->r->get($name);
$id = $this->id; $id = $this->id;
if ($id && is_array($v)) { if ($id && is_array($v)) {
return isset($v[$id]) ? $v[$id] : $def; return $v[$id] ?? $def;
} }
return $v; return $v;
} }

View file

@ -3,72 +3,101 @@
/** /**
* Класс сервиса = Упрощенный компонент * Класс сервиса = Упрощенный компонент
*/ */
class Controller_Service namespace ctiso\Controller;
use ctiso\Path;
use ctiso\Model\BaseMapper;
use ctiso\File;
use ctiso\Registry;
use ctiso\Database\PDOStatement;
use ctiso\Database;
class Service
{ {
/** @var array */
public $viewPath = []; public $viewPath = [];
/** @var array */
public $webPath = []; public $webPath = [];
public $registry; // Registry->getInstance /** @var Registry */
public $config;
/** @var string */
public $template; public $template;
/** @var string */
public $templatePath; public $templatePath;
/** @var string */
public $COMPONENTS_WEB; public $COMPONENTS_WEB;
/** @var Database */
public $db; public $db;
/**
* Возвращает путь к шаблонам
* @param string $name Имя шаблона
* @return string
*/
public function getTemplatePath($name) public function getTemplatePath($name)
{ {
return Path::join($this->viewPath[0], 'templates', 'modern', $name); return Path::join($this->viewPath[0], 'templates', 'modern', $name);
} }
/**
* Возвращает путь к шаблонам
* @return string
*/
public function getTemplateWebPath() public function getTemplateWebPath()
{ {
return Path::join($this->webPath[0], strtolower(get_class($this)), 'templates', 'modern'); return Path::join($this->webPath[0], strtolower(get_class($this)), 'templates', 'modern');
} }
/**
* @param $name Имя модели
*/
private function getModelPath($name)
{
return Path::join (CMS_PATH, "model", $name . ".php");
}
/** /**
* Создает модель * Создает модель
* @param string $name * @param class-string $modelName
* @return model * @return BaseMapper
*/ */
public function getModel($name) public function getModel($modelName)
{ {
require_once ($this->getModelPath ($name)); /** @var BaseMapper */
$modelName = $name . "Mapper"; $model = new $modelName();
$model = new $modelName ();
$model->db = $this->db; $model->db = $this->db;
return $model; return $model;
} }
/**
* @param string $key
* @param string $val
* @param PDOStatement $res
* @return array
*/
public function options($key, $val, $res) { public function options($key, $val, $res) {
$result = array(); $result = [];
while($res->next()) { while($res->next()) {
$result[] = array('value' => $res->getInt($key), 'name' => $res->getString($val)); $result[] = ['value' => $res->getInt($key), 'name' => $res->getString($val)];
} }
return $result; return $result;
} }
/**
* @param array $list
* @param bool $selected
* @return array
*/
public function optionsPair($list, $selected = false) { public function optionsPair($list, $selected = false) {
$result = array(); $result = [];
foreach ($list as $key => $value) { foreach ($list as $key => $value) {
$result [] = array('value' => $key, 'name' => $value, 'selected' => $key == $selected); $result [] = ['value' => $key, 'name' => $value, 'selected' => $key == $selected];
} }
return $result; return $result;
} }
/**
* @return array
*/
function getInfo() { function getInfo() {
$filename = Path::join($this->viewPath[0], 'install.json'); $filename = Path::join($this->viewPath[0], 'install.json');
if (file_exists($filename)) { if (file_exists($filename)) {
$settings = json_decode(File::getContents($filename), true); $settings = json_decode(File::getContents($filename), true);
return $settings; return $settings;
} }
return array(); return [];
} }
} }

View file

@ -0,0 +1,41 @@
<?php
namespace ctiso\Controller;
interface SiteInterface {
/**
* FIXME: Возвращает ресурс (но его тип опрделяется в App)
* @return mixed
*/
function getResource();
/**
* @return \ctiso\Database
*/
function getDatabase();
/**
* @return \ctiso\Registry
*/
function getConfig();
/**
* @return \ctiso\Settings
*/
function getTheme();
/**
* @param array $config
*/
function addComponentConfig($config): void;
function addRequireJsPath(string $name, string $path, ?array $shim = null): void;
function addStyleSheet(string $url): void;
/**
* @param string $expression
* @return ?Component
*/
function loadComponent(string $expression);
/**
* @return array{string, string, string}
*/
function findTemplate(string $name);
function replaceImg(string $src, int $width, int $height): string;
function updatePageTime(int $time): void;
}

View file

@ -1,70 +0,0 @@
<?php
class Controller_State
{
public $action = '';
public $states = array();
public $titles = array();
public function __construct($action)
{
$this->action = $action;
}
static function make($action)
{
return new Controller_State($action);
}
public function addTitle($name, $url = array())
{
$this->titles [] = array($name, $url);
return $this;
}
public function addState(Controller_State $state)
{
$this->states [$state->getAction()] = $state;
return $this;
}
public function getAction()
{
return $this->action;
}
function checkAction($action, &$list)
{
if ($this->action == $action) {
array_push($list, $this);
return true;
} else {
foreach ($this->states as $state) {
if ($state->checkAction($action, $list)) {
array_push($list, $this);
return true;
}
}
}
return false;
}
function makeTitle(Controller_Action $module)
{
foreach ($this->titles as $item) {
$module->path->addMenuItem($module->nUrl($this->action, $item[1]), $item[0]);
}
}
function getPath($module, $action)
{
$list = array();
if ($this->checkAction($action, $list)) {
foreach (array_reverse($list) as $item) {
$item->makeTitle($module);
}
} else {
$this->makeTitle($module);
}
}
}

View file

@ -1,190 +1,280 @@
<?php <?php
///<reference path="Database/PDOStatement.php" /> namespace {
require_once "Database/PDOStatement.php"; if (!function_exists('sqliteLower')) {
/**
/** * @param string $str
* Класс оболочка для PDO для замены Creole * @return string
*/ */
class Database/*<Database_PDOStatement>*/ extends PDO function sqliteLower($str)
{ {
return mb_strtolower($str, 'UTF-8');
public $dsn; }
public function __construct($dsn, $username = null, $password = null) }
{ }
parent::__construct($dsn, $username, $password);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); namespace ctiso {
$this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Database_PDOStatement', array())); use PDO;
} use ctiso\Database\Statement;
use ctiso\Database\PDOStatement;
function prepare($sql, $args = []) { use ctiso\Database\IdGenerator;
$result = parent::prepare($sql, $args);
return $result; /**
} * Класс оболочка для PDO для замены Creole
* @phpstan-type DSN = array{phptype: string, hostspec: string, database: string, username: string, password: string}
public function getDSN() */
{ class Database extends PDO
return $this->dsn; {
}
public function isPostgres(){ /** @var DSN */
return ($this->dsn["phptype"] == "pgsql"); public $dsn;
}
/** /**
* Создает соединение с базой данных * Создает соединение с базой данных
*/ * @param string $dsn - DSN
static function getConnection(array $dsn) * @param string|null $username - имя пользователя
{ * @param string|null $password - пароль
*/
if ($dsn['phptype'] == 'pgsql' || $dsn['phptype'] == 'mysql') { public function __construct($dsn, $username = null, $password = null)
$port = (isset($dsn['port'])) ? "port={$dsn['port']};" : ""; {
$connection/*: Database*/ = new static("{$dsn['phptype']}:host={$dsn['hostspec']}; $port dbname={$dsn['database']}", $dsn['username'], $dsn['password']); parent::__construct($dsn, $username, $password);
if ($dsn['phptype'] == 'pgsql') { $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$connection->query('SET client_encoding="UTF-8"'); $this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDOStatement::class, []]);
}
if (isset($dsn['schema'])) {
$connection->query('SET search_path TO ' . $dsn['schema']); /**
} * prepare возвращает только PDOStatement т.к установлен PDO::ERRMODE_EXCEPTION
*/
function prepare(string $sql, array $options = []): PDOStatement
{
/** @var PDOStatement */
$result = parent::prepare($sql, $options);
return $result;
}
function query($query, $fetchMode = PDO::FETCH_INTO, mixed $_arg1 = null, mixed $_arg2 = null): PDOStatement {
/** @var PDOStatement */
$result = parent::query($query, $fetchMode);
return $result;
}
/**
* Возвращает DSN
* @return DSN
*/
public function getDSN()
{
return $this->dsn;
}
/**
* Возвращает true, если база данных Postgres
* @return bool
*/
public function isPostgres()
{
return ($this->dsn["phptype"] == "pgsql");
}
/**
* Создает соединение с базой данных
* @param array $dsn - DSN
* @return Database|null
*/
static function getConnection(array $dsn)
{
/** @var ?Database */
$connection = null;
if ($dsn['phptype'] == 'pgsql' || $dsn['phptype'] == 'mysql') {
$port = (isset($dsn['port'])) ? "port={$dsn['port']};" : "";
$connection = new self("{$dsn['phptype']}:host={$dsn['hostspec']}; $port dbname={$dsn['database']}", $dsn['username'], $dsn['password']);
if ($dsn['phptype'] == 'pgsql') {
$connection->query('SET client_encoding="UTF-8"');
}
if (isset($dsn['schema'])) {
$connection->query('SET search_path TO ' . $dsn['schema']);
}
} elseif ($dsn['phptype'] == 'sqlite::memory') {
$connection = new self("{$dsn['phptype']}:");
$connection->sqliteCreateFunction('LOWER', 'sqliteLower', 1);
} elseif ($dsn['phptype'] == 'sqlite') {
$connection = new self("{$dsn['phptype']}:{$dsn['database']}");
$connection->setAttribute(PDO::ATTR_TIMEOUT, 5);
$mode = defined('SQLITE_JOURNAL_MODE') ? \SQLITE_JOURNAL_MODE : 'WAL';
$connection->query("PRAGMA journal_mode=$mode");
$connection->sqliteCreateFunction('LOWER', 'sqliteLower', 1);
}
$connection->dsn = $dsn;
return $connection;
}
/**
* Выполняет запрос к базе данных
* @param string $query - запрос
* @param ?array<string, mixed> $values - значения
*/
public function executeQuery($query, $values = null): PDOStatement
{
$stmt = $this->prepare($query);
$stmt->execute($values);
$stmt->cache = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $stmt;
}
/**
* Создает подготовленный запрос
* @param string $query - запрос
* @return Statement
*/
public function prepareStatement($query)
{
return new Statement($query, $this);
}
/**
* Извлекает из базы все элементы по запросу (Для совместимости со старым представлением баз данных CIS)
* @param string $query - запрос
* @param ?array<string, mixed> $values - значения
* @return array<array<string, mixed>>
*/
public function fetchAllArray($query, $values = null)
{
$sth = $this->prepare($query);
$prep = $this->prepareValues($values);
$sth->execute($prep);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Извлекает из базы первый элемент по запросу
* @param string $query - запрос
* @param ?array<string, mixed> $values - значения
* @return array<string, mixed>|false
*/
public function fetchOneArray($query, $values = null)
{
$sth = $this->prepare($query);
$prep = $this->prepareValues($values);
$sth->execute($prep);
return $sth->fetch(PDO::FETCH_ASSOC);
}
/**
* Преобразует значения в подготовленные значения
* @param array $values - значения
* @return ?array<string, string>
*/
private function prepareValues($values)
{
if (!$values) {
return null;
}
$pg = $this->isPostgres();
$prep = [];
foreach ($values as $key => $value) {
$result = null;
if (is_bool($value)) {
if ($pg) {
$result = $value ? 'true' : 'false';
} else {
$result = $value ? 1 : 0;
}
} else {
$result = $value;
}
$prep[":" . $key] = $result;
}
return $prep;
}
/**
* Создает INSERT запрос
* @param string $table - таблица
* @param array $values - значения
* @param bool $return_id - возвращать id
* @param string $index - индекс
* @return int|null
*/
function insertQuery($table, array $values, $return_id = false, $index = null)
{
$prep = $this->prepareValues($values);
$sql = "INSERT INTO $table (" . implode(",", array_keys($values))
. ") VALUES (" . implode(",", array_keys($prep)) . ")";
if ($return_id) {
if ($this->isPostgres()) {
$sql .= " RETURNING $index";
}
}
$stmt = $this->prepare($sql);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute($prep);
$result = $stmt->fetch();
if ($return_id) {
if ($this->isPostgres()) {
return $result[$index];
} else {
$result = $this->fetchOneArray("SELECT $index AS lastid FROM $table WHERE OID = last_insert_rowid()");
if ($result === false) {
throw new \RuntimeException("Ошибка получения идентификатора");
}
return $result['lastid'];
}
}
return null;
}
/**
* Создает UPDATE запрос
* @param string $table - таблица
* @param array $values - значения
* @param string $cond - условие
*/
function updateQuery($table, array $values, $cond): void
{
$prep = $this->prepareValues($values);
$sql = "UPDATE $table SET " . implode(
",",
array_map(function ($k, $v) {
return $k . "=" . $v; }, array_keys($values), array_keys($prep))
) . " WHERE $cond";
$stmt = $this->prepare($sql);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute($prep);
}
/**
* Создает генератор идентификаторов
* @return IdGenerator
*/
function getIdGenerator()
{
return new IdGenerator($this);
}
/**
* Замечание: Только для Postgres SQL
* @param string $seq Имя последовательности для ключа таблицы
* @return int Идентефикатор следующей записи
*/
function getNextId($seq)
{
$result = $this->fetchOneArray("SELECT nextval('$seq')");
if ($result === false) {
throw new \RuntimeException("Ошибка получения следующего идентификатора");
}
return $result['nextval'];
}
/**
* Закрывает соединение с базой данных
*/
function close(): void
{
} }
if ($dsn['phptype'] == 'sqlite') {
$connection/*: Database*/ = new static("{$dsn['phptype']}:{$dsn['database']}");
$connection->setAttribute(PDO::ATTR_TIMEOUT, 5);
$mode = defined('SQLITE_JOURNAL_MODE') ? SQLITE_JOURNAL_MODE : 'WAL';
$connection->query("PRAGMA journal_mode=$mode");
if(!function_exists('sqliteLower')){
function sqliteLower($str) {
return mb_strtolower($str, 'UTF-8');
}
$connection->sqliteCreateFunction('LOWER', 'sqliteLower', 1);
}
}
$connection->dsn = $dsn;
return $connection;
}
public function executeQuery($query, $values=null)
{
$stmt/*: Database_PDOStatement*/ = $this->prepare($query);
$stmt->execute($values);
$stmt->cache = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $stmt;
}
public function prepareStatement($query)
{
return new Database_Statement($query, $this);
}
// Для совместимости со старым представлением баз данных CIS
/**
* Извлекает из базы все элементы по запросу
*/
public function fetchAllArray($query, $values = null)
{
$sth/*: Database_PDOStatement*/ = $this->prepare($query);
$prep = $this->prepareValues($values);
$sth->execute($prep);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Извлекает из базы первый элемент по запросу
*/
public function fetchOneArray($query, $values = null)
{
$sth/*: Database_PDOStatement*/ = $this->prepare($query);
$prep = $this->prepareValues($values);
$sth->execute($prep);
return $sth->fetch(PDO::FETCH_ASSOC);
}
private static function assignQuote($x, $y)
{
return $x . "=" . $this->quote($y);
}
private function prepareValues($values)
{
if (!$values) {
return null;
}
$pg = $this->isPostgres();
$prep = array();
foreach ($values as $key => $value) {
$result = null;
if(is_bool($value)) {
if ($pg) {
$result = $value ? 'true' : 'false';
} else {
$result = $value ? 1 : 0;
}
} else {
$result = $value;
}
$prep[":" . $key] = $result;
}
return $prep;
}
/**
* Создает INSERT запрос
*/
function insertQuery($table, array $values, $return_id = false, $index = null)
{
$prep = $this->prepareValues($values);
$sql = "INSERT INTO $table (" . implode(",", array_keys($values))
. ") VALUES (" . implode(",", array_keys($prep)). ")";
if ($return_id) {
if ($this->isPostgres()){
$sql = $sql." RETURNING $index";
}
}
$stmt = $this->prepare($sql);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute($prep);
$result = $stmt->fetch();
if ($return_id) {
if ($this->isPostgres()) {
return $result[$index];
} else {
$result = $this->fetchOneArray("SELECT $index AS lastid FROM $table WHERE OID = last_insert_rowid()");
return $result['lastid'];
}
}
}
/**
* Создает UPDATE запрос
*/
function updateQuery($table, array $values, $cond)
{
$prep = $this->prepareValues($values);
$sql = "UPDATE $table SET " . implode(",",
array_map(function($k,$v){return $k."=".$v;}, array_keys($values), array_keys($prep))) . " WHERE $cond";
$stmt = $this->prepare($sql);
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute($prep);
}
function getIdGenerator() {
return new Database_IdGenerator($this);
}
/**
* Замечание: Только для Postgres SQL
* @param string $seq Имя последовательности для ключа таблицы
* @return int Идентефикатор следующей записи
*/
function getNextId($seq) {
$result = $this->fetchOneArray("SELECT nextval('$seq')");
return $result['nextval'];
}
function close()
{
return null;
} }
} }

View file

@ -1,26 +1,43 @@
<?php <?php
class Database_IdGenerator { namespace ctiso\Database;
use ctiso\Database;
class IdGenerator {
/** @var Database */
private $db; private $db;
function __construct(Database $db) { function __construct(Database $db) {
$this->db = $db; $this->db = $db;
} }
/**
* @return bool
*/
function isBeforeInsert() { function isBeforeInsert() {
return false; return false;
}
function isAfterInsert() {
return true;
} }
/**
* @return bool
*/
function isAfterInsert() {
return true;
}
/**
* @param string $seq
* @return int
*/
function getId($seq) { function getId($seq) {
if ($this->db->isPostgres()) { if ($this->db->isPostgres()) {
$result = $this->db->fetchOneArray("SELECT nextval('$seq') AS nextval"); $result = $this->db->fetchOneArray("SELECT nextval('$seq') AS nextval");
} else { } else {
$result = $this->db->fetchOneArray("SELECT last_insert_rowid() AS nextval"); $result = $this->db->fetchOneArray("SELECT last_insert_rowid() AS nextval");
} }
return intval($result['nextval']); if (!$result) {
throw new \Exception("nextval failed");
}
return (int)$result['nextval'];
} }
} }

View file

@ -1,143 +1,175 @@
<?php <?php
//Действия с базой данных согласно json файлу. //Действия с базой данных согласно json файлу.
class Database_JsonInstall { namespace ctiso\Database;
use ctiso\Database\Manager;
class JsonInstall {
/** @var Manager */
public $db_manager; public $db_manager;
/** @var array */
public $serialColumns; public $serialColumns;
public function __construct(Database_Manager $db_manager) { public function __construct(Manager $db_manager) {
$this->db_manager = $db_manager; $this->db_manager = $db_manager;
} }
function install($dbinit_path, $dbfill_path = null) { /**
$dbinit_file = file_get_contents($dbinit_path); * Установить базу данных
if (is_string($dbinit_file)) { * @param string $dbinit_path
$initActions = json_decode($dbinit_file, true); * @param ?string $dbfill_path
if (!$initActions) { * @return int
echo "Invalid ".$dbinit_path; */
return 0; function install($dbinit_path, $dbfill_path = null) {
} $dbinit_file = file_get_contents($dbinit_path);
} else { if (is_string($dbinit_file)) {
echo "No ".$dbinit_path; $initActions = json_decode($dbinit_file, true);
return 0; if (!$initActions) {
} echo "Invalid ".$dbinit_path;
return 0;
}
} else {
echo "No ".$dbinit_path;
return 0;
}
$this->initDataBase($initActions, $dbinit_path); $this->initDataBase($initActions, $dbinit_path);
if ($dbfill_path) { if ($dbfill_path) {
$this->fillDataBase($dbfill_path); $this->fillDataBase($dbfill_path);
} }
$this->makeConstraints($initActions); $this->makeConstraints($initActions);
} return 1;
}
function missingTables($tables) { /**
$actual_tables = $this->db_manager->GetAllTableNames(); * Получить список таблиц, которые не существуют в базе данных
$missingTables = []; * @param array $tables
foreach ($tables as $table) { * @return array
if (!in_array($table, $actual_tables)) */
$missingTables[] = $table; function missingTables($tables) {
} $actual_tables = $this->db_manager->getAllTableNames();
return $missingTables; $missingTables = [];
} foreach ($tables as $table) {
if (!in_array($table, $actual_tables))
$missingTables[] = $table;
}
return $missingTables;
}
//Создать таблицы /**
function initDataBase($initActions/*: array*/, $dbinit_path) { * Создать таблицы
$pg = $this->db_manager->db->isPostgres(); * @param array $initActions
if (!$pg) { * @param string $dbinit_path
$refs = []; * @return void
//В sqlite нет alter reference. Референсы надо создавать при создании таблицы. */
foreach ($initActions as $action) { function initDataBase(array $initActions, $dbinit_path) {
if ($action["type"] == "alterReference") { $pg = $this->db_manager->db->isPostgres();
if (!isset($refs[$action["table"]])) if (!$pg) {
$refs[$action["table"]] = []; $refs = [];
$refs[$action["table"]][]=$action;//добавить к списку референсов для таблицы //В sqlite нет alter reference. Референсы надо создавать при создании таблицы.
} foreach ($initActions as $action) {
} if ($action["type"] == "alterReference") {
} if (!isset($refs[$action["table"]]))
$refs[$action["table"]] = [];
$refs[$action["table"]][]=$action;//добавить к списку референсов для таблицы
}
}
}
foreach ($initActions as $action) { foreach ($initActions as $action) {
if (!$pg) { if (!$pg) {
if ($action["type"] == "createTable") { if ($action["type"] == "createTable") {
$table_name = $action["table_name"]; $table_name = $action["table_name"];
if (isset($refs[$table_name])) { if (isset($refs[$table_name])) {
foreach ($refs[$table_name] as $value) { foreach ($refs[$table_name] as $value) {
$action['fields'][$value['column']]['references'] = $action['fields'][$value['column']]['references'] = [
$value['refTable']."(".$value['refColumn'].")"; "refTable" => $value['refTable'],
} 'refColumn' => $value['refColumn']
}
}
}
if ($action["type"] != "alterReference") {
$this->db_manager->ExecuteAction($action, $dbinit_path);
}
}
//Запомнить все колонки serial
$this->serialColumns = [];
if ($pg) {
foreach ($initActions as $action) {
if ($action["type"] == "createTable") {
foreach ($action["fields"] as $name => $field) {
if ($field["type"]=="serial") {
$this->serialColumns[] = [
"table"=>$action["table_name"],
"column"=>$name
]; ];
} }
} }
}
}
}
}
//Заполнить данными }
function fillDataBase($dbfill_file_path) { }
$dbfill_file = file_get_contents($dbfill_file_path); if ($action["type"] != "alterReference") {
if (is_string($dbfill_file)) { $this->db_manager->executeAction($action, $dbinit_path);
$actions = json_decode($dbfill_file,true); }
if ($actions) { }
//Проверка что упоминаемые в списке действий таблицы уже есть в базе //Запомнить все колонки serial
$affected_tables = []; $this->serialColumns = [];
foreach ($actions as $action) { if ($pg) {
if ($action["table_name"]) { foreach ($initActions as $action) {
$affected_tables[$action["table_name"]] = 1; if ($action["type"] == "createTable") {
foreach ($action["fields"] as $name => $field) {
if ($field["type"]=="serial") {
$this->serialColumns[] = [
"table"=>$action["table_name"],
"column"=>$name
];
}
}
}
}
}
}
/**
* Заполнить данными
* @param string $dbfill_file_path
* @return void
*/
function fillDataBase($dbfill_file_path) {
$dbfill_file = file_get_contents($dbfill_file_path);
if (is_string($dbfill_file)) {
$actions = json_decode($dbfill_file,true);
if ($actions) {
//Проверка что упоминаемые в списке действий таблицы уже есть в базе
$affected_tables = [];
foreach ($actions as $action) {
if ($action["table_name"]) {
$affected_tables[$action["table_name"]] = 1;
} }
} }
$missing = $this->missingTables(array_keys($affected_tables)); $missing = $this->missingTables(array_keys($affected_tables));
if (!empty($missing)) { if (!empty($missing)) {
echo "dbfill error. Missing tables: ".implode(" ", $missing); echo "dbfill error. Missing tables: ".implode(" ", $missing);
return; return;
}
//Выполнение действий
foreach ($actions as $action) {
$this->db_manager->ExecuteAction($action, $dbfill_file_path);
}
} else {
echo "Invalid dbfill.json";
}
} else {
echo "No dbfill.json";
}
}
//Обновить ключи serial и создать ограничения
function makeConstraints($initActions) {
$pg = $this->db_manager->db->isPostgres();
if ($pg) {
foreach ($this->serialColumns as $serialColumn) {
$this->db_manager->UpdateSerial($serialColumn["table"], $serialColumn["column"]);
}
foreach ($initActions as $action) {
if ($action["type"] == "alterReference") {
$this->db_manager->ExecuteAction($action);
} }
}
} //Выполнение действий
} foreach ($actions as $action) {
$this->db_manager->executeAction($action, $dbfill_file_path);
}
} else {
echo "Invalid dbfill.json";
}
} else {
echo "No dbfill.json";
}
}
/**
* Обновить ключи serial и создать ограничения
* @param array $initActions
* @return void
*/
function makeConstraints($initActions) {
$pg = $this->db_manager->db->isPostgres();
if ($pg) {
foreach ($this->serialColumns as $serialColumn) {
$this->db_manager->updateSerial($serialColumn["table"], $serialColumn["column"]);
}
foreach ($initActions as $action) {
if ($action["type"] == "alterReference") {
$this->db_manager->executeAction($action);
}
}
}
}
} }

View file

@ -1,221 +1,397 @@
<?php <?php
class Database_Manager namespace ctiso\Database;
use ctiso\Database;
use ctiso\Tools\SQLStatementExtractor;
use ctiso\Path;
use Exception;
/**
* @phpstan-type DropAction array{
* type:"dropTable",
* table_name:string
* }
*
* @phpstan-type CreateAction array{
* type:"createTable",
* table_name:string,
* constraints?:array{fields: array<string>, type: string}|string,
* fields:array<mixed>,
* }
*
* @phpstan-type AddColumnAction array{
* type:"addColumn",
* table_name:string,
* column_name:string,
* field:ColumnProps
* }
*
* @phpstan-type AlterReferenceAction array{
* type:"alterReference",
* table:string,
* column:string,
* refTable:string,
* refColumn:string
* }
*
* @phpstan-type RenameColumnAction array{
* type:"renameColumn",
* table:string,
* old_name:string,
* new_name:string
* }
*
* @phpstan-type ExecuteFileAction array{
* type:"executeFile",
* source:string,
* pgsql:?string
* }
*
* @phpstan-type CreateViewAction array{
* type:"createView",
* view:string,
* select:string
* }
*
* @phpstan-type InsertAction array{
* type:"insert",
* table_name:string,
* values:array<mixed>
* }
*
* @phpstan-type Action DropAction
* | CreateAction
* | AddColumnAction
* | AlterReferenceAction
* | RenameColumnAction
* | ExecuteFileAction
* | CreateViewAction
* | InsertAction
*
* @phpstan-type ColumnProps array{
* name:string,
* type:string,
* not_null:bool,
* default:?string,
* references:?array{refTable:string,refColumn:string}
* }
*/
class Manager
{ {
public $db/*: Database*/; /** @var Database */
public $db;
function __construct(Database $db) { public function __construct(Database $db)
$this->db = $db; {
} $this->db = $db;
}
public function ExecuteAction($action/*: array*/, $db_file = "") { /**
switch($action["type"]) { * Выполняет действие
case "dropTable": * @param Action $action
$this->DropTableQuery($action["table_name"], true); * @param string $db_file
break; * @throws Exception
case "createTable": */
$constraints = isset($action["constraints"]) ? $action["constraints"] : NULL; public function executeAction(array $action, $db_file = ""): void
$this->CreateTableQuery($action["table_name"], $action["fields"], $constraints); {
break; switch ($action["type"]) {
case "addColumn": case "dropTable":
$this->AddColumn($action["table_name"], $action["column_name"], $action["field"]); $this->dropTableQuery($action["table_name"], true);
break;
case "insert":
$this->db->insertQuery($action["table_name"], $action["values"]);
break;
case "alterReference":
$this->AlterReference($action["table"], $action["column"], $action["refTable"], $action["refColumn"]);
break;
case "renameColumn":
$this->RenameColumn($action["table"], $action["old_name"], $action["new_name"]);
break; break;
case "executeFile": case "createTable":
$constraints = $action["constraints"] ?? null;
$this->createTableQuery($action["table_name"], $action["fields"], $constraints);
break;
case "addColumn":
$this->addColumn($action["table_name"], $action["column_name"], $action["field"]);
break;
case "insert":
$this->db->insertQuery($action["table_name"], $action["values"]);
break;
case "alterReference":
$this->alterReference($action["table"], $action["column"], $action["refTable"], $action["refColumn"]);
break;
case "renameColumn":
$this->renameColumn($action["table"], $action["old_name"], $action["new_name"]);
break;
case "createView":
$this->recreateView($action["view"], $action["select"]);
break;
case "executeFile":
if ($this->db->isPostgres() && isset($action["pgsql"])) { if ($this->db->isPostgres() && isset($action["pgsql"])) {
$file = $action["pgsql"]; $file = $action["pgsql"];
} else { } else {
$file = $action["source"]; $file = $action["source"];
} }
$stmtList = Tools_SQLStatementExtractor::extractFile(Path::join(dirname($db_file), $file)); $stmtList = SQLStatementExtractor::extractFile(Path::join(dirname($db_file), $file));
foreach($stmtList as $stmt) { foreach ($stmtList as $stmt) {
$this->db->executeQuery($stmt); $this->db->executeQuery($stmt);
} }
break; break;
default: default:
throw new Exception("unknown action ". $action["type"] . PHP_EOL); throw new Exception("unknown action " . $action["type"] . PHP_EOL);
} }
} }
public function DropTableQuery($table, $cascade=false) { /**
$statement = "DROP TABLE IF EXISTS ".$table; * Дропает и создаёт SQL VIEW
if ($this->db->isPostgres()&&$cascade) { * @param string $viewName
$statement = $statement." CASCADE"; * @param string $selectStatement
} */
$this->db->query($statement); public function recreateView($viewName, $selectStatement): void
} {
$this->db->query("DROP VIEW " . $viewName);
$this->db->query("CREATE VIEW " . $viewName . " AS " . $selectStatement);
}
public function AlterReference($table,$column,$refTable,$refColumn) { /**
$this->db->query("ALTER TABLE ".$table." ADD CONSTRAINT ".$table."_".$column."fk"." FOREIGN KEY (".$column.") REFERENCES ".$refTable." (".$refColumn.")"); * Дропает таблицу
} * @param string $table
* @param bool $cascade
*/
public function dropTableQuery($table, $cascade = false): void
{
$statement = "DROP TABLE IF EXISTS " . $table;
if ($this->db->isPostgres() && $cascade) {
$statement .= " CASCADE";
}
$this->db->query($statement);
}
//Извлечение информации о полях таблицы /**
public function TableInfo($table) { * Добавляет ссылку на другую таблицу
$pg = $this->db->isPostgres(); * @param string $table
if ($pg) { * @param string $column
throw new Exception("Not implemented for postgres"); * @param string $refTable
} else { * @param string $refColumn
$results = $this->db->fetchAllArray("PRAGMA table_info(".$table.");"); */
if (empty($results)) { public function alterReference($table, $column, $refTable, $refColumn): void
return null; {
$this->db->query("ALTER TABLE " . $table . " ADD CONSTRAINT " . $table . "_" . $column . "fk" . " FOREIGN KEY (" . $column . ") REFERENCES " . $refTable . " (" . $refColumn . ") ON DELETE CASCADE ON UPDATE CASCADE");
}
/**
* Извлечение информации о полях таблицы
* @param string $table
* @return array{type:string,not_null:bool,constraint:?string}[]|null
*/
public function tableInfo($table)
{
$pg = $this->db->isPostgres();
if ($pg) {
throw new Exception("Not implemented for postgres");
} else {
$results = $this->db->fetchAllArray("PRAGMA table_info(" . $table . ");");
if (empty($results)) {
return null;
} }
$fields = []; $fields = [];
foreach ($results as $result) { foreach ($results as $result) {
$fields[$result["name"]] = [ $fields[$result["name"]] = [
"type"=> $result["type"], "type" => $result["type"],
"not_null"=> boolval($result["notnull"]), "not_null" => boolval($result["notnull"]),
"constraint"=> ((boolean) $result["pk"]) ? "PRIMARY KEY" : null "constraint" => ((bool) $result["pk"]) ? "PRIMARY KEY" : null
]; ];
} }
return $fields; return $fields;
} }
} }
public function RenameColumn($table, $old_name, $new_name) { /**
$pg = $this->db->isPostgres(); * Переименование столбца в таблице
if ($pg) { * @param string $table
$this->db->query("ALTER TABLE ".$table." RENAME COLUMN ".$old_name." TO ".$new_name); * @param string $old_name
} else { * @param string $new_name
$tmp_table = "tmp_" . $table; */
$this->DropTableQuery($tmp_table); public function renameColumn($table, $old_name, $new_name): void
$table_info = $this->TableInfo($table); {
$pg = $this->db->isPostgres();
if ($pg) {
$this->db->query("ALTER TABLE " . $table . " RENAME COLUMN " . $old_name . " TO " . $new_name);
} else {
$tmp_table = "tmp_" . $table;
$this->dropTableQuery($tmp_table);
$table_info = $this->tableInfo($table);
if (isset($table_info[$new_name])) { if (isset($table_info[$new_name])) {
return; return;
} }
$data/*: array*/ = $this->DumpTable($table); $data = $this->dumpTable($table);
$this->db->query("ALTER TABLE ".$table." RENAME TO ".$tmp_table.";"); $this->db->query("ALTER TABLE " . $table . " RENAME TO " . $tmp_table . ";");
$table_info[$new_name] = $table_info[$old_name]; $table_info[$new_name] = $table_info[$old_name];
unset($table_info[$old_name]); unset($table_info[$old_name]);
$this->CreateTableQuery($table,$table_info,null); $this->createTableQuery($table, $table_info, null);
foreach ($data as $row) { foreach ($data as $row) {
$values = $row['values']; $values = $row['values'];
$values[$new_name] = $values[$old_name]; $values[$new_name] = $values[$old_name];
unset($values[$old_name]); unset($values[$old_name]);
$this->db->insertQuery($table, $values); $this->db->insertQuery($table, $values);
} }
$this->DropTableQuery($tmp_table); $this->dropTableQuery($tmp_table);
} }
} }
//Обновление ключа serial после ручной вставки /**
public function UpdateSerial($table,$column) { * Обновление ключа serial после ручной вставки
$this->db->query("SELECT setval(pg_get_serial_sequence('".$table."', '".$column."'), coalesce(max(".$column."),0) + 1, false) FROM ".$table); * @param string $table
} * @param string $column
*/
public function updateSerial($table, $column): void
{
$this->db->query("SELECT setval(pg_get_serial_sequence('" . $table . "', '" . $column . "'), coalesce(max(" . $column . "),0) + 1, false) FROM " . $table);
}
public function Column_Definition($name,$data,$pg){ /**
$constraint = isset($data['constraint'])?" ".$data['constraint']:""; * Возвращает определение столбца
$references = ""; * @param string $name
if (isset($data['references'])) { * @param ColumnProps $data
$references = " REFERENCES ".$data['references']; * @param bool $pg
} * @return string
if (isset($data["not_null"]) && $data["not_null"]) */
$constraint .=" NOT NULL"; public function columnDefinition($name, $data, $pg)
$type = $data['type']; {
if (!$pg) { $constraint = isset($data['constraint']) ? " " . $data['constraint'] : "";
if (strtolower($type)=="serial") $references = "";
$type = "integer"; if (isset($data['references'])) {
//if (strtolower($type)=="boolean") $references = " REFERENCES " . $data['references']['refTable'] . '(' . $data['references']['refColumn'] . ')';
// $type = "integer"; }
} if (isset($data["not_null"]) && $data["not_null"]) {
return $name." ".$type.$references.$constraint; $constraint .= " NOT NULL";
} }
$type = $data['type'];
if (!$pg) {
if (strtolower($type) == "serial") {
$type = "integer";
}
}
return $name . " " . $type . $references . $constraint;
}
public function AddColumn($table_name,$column_name,$field){ /**
$pg = $this->db->isPostgres(); * Добавляет столбец в таблицу
$q = "ALTER TABLE ".$table_name." ADD COLUMN ". * @param string $table_name
$this->Column_Definition($column_name, $field, $pg); * @param string $column_name
$this->db->query($q); * @param ColumnProps $field
} */
public function addColumn($table_name, $column_name, $field): void
{
$pg = $this->db->isPostgres();
$q = "ALTER TABLE " . $table_name . " ADD COLUMN " .
$this->columnDefinition($column_name, $field, $pg);
$this->db->query($q);
}
function getConstraintDef($c/*: array*/) { /**
* Возвращает определение ограничения
* @param array{fields: string[], type: string} $c
* @return string
*/
public function getConstraintDef(array $c)
{
if ($c['type'] == 'unique') { if ($c['type'] == 'unique') {
return "UNIQUE(" . implode(", ", $c['fields']) . ")"; return "UNIQUE(" . implode(", ", $c['fields']) . ")";
} }
return ""; return "";
} }
//CreateTableQuery('users',['id'=>['type'=>'integer','constraint'=>'PRIMARY KEY']])
public function CreateTableQuery($table, $fields, $constraints) { /**
$pg = $this->db->isPostgres(); * Создает таблицу
if ($constraints) { * @example createTableQuery('users',['id'=>['type'=>'integer','constraint'=>'PRIMARY KEY']])
* @param string $table
* @param array $fields
* @param array{fields: array<string>, type: string}|string|null $constraints
*/
public function createTableQuery($table, $fields, $constraints): void
{
$pg = $this->db->isPostgres();
if ($constraints) {
if (is_array($constraints)) { if (is_array($constraints)) {
$constraints = $this->getConstraintDef($constraints); $constraints = $this->getConstraintDef($constraints);
} }
$constraints = ", " . $constraints; $constraints = ", " . $constraints;
} }
$statement = "CREATE TABLE $table (" . implode(",", $statement = "CREATE TABLE $table (" . implode(
array_map(function($name,$data) use ($pg) { ",",
return $this->Column_Definition($name,$data,$pg); array_map(function ($name, $data) use ($pg) {
}, array_keys($fields), array_values($fields)) return $this->columnDefinition($name, $data, $pg);
) . " " . $constraints . ")"; }, array_keys($fields), array_values($fields))
$this->db->query($statement); ) . " " . $constraints . ")";
} $this->db->query($statement);
}
public function DumpTable($table_name) { /**
$pg = $this->db->isPostgres(); * Возвращает дамп таблицы
* @param string $table_name
* @return array
*/
public function dumpTable($table_name)
{
$pg = $this->db->isPostgres();
$result/*: array*/ = array(); $result = [];
$data/*: array*/ = $this->db->fetchAllArray("SELECT * FROM ".$table_name.";"); $data = $this->db->fetchAllArray("SELECT * FROM " . $table_name . ";");
if (!$pg) { if (!$pg) {
$table_fields = $this->TableInfo($table_name); $table_fields = $this->tableInfo($table_name);
foreach ($table_fields as $name => $value) { foreach ($table_fields as $name => $value) {
$type = strtolower($value['type']); $type = strtolower($value['type']);
if ($type == "boolean") { if ($type == "boolean") {
foreach ($data as &$row) { foreach ($data as &$row) {
$row/*: array*/ = $row; if (isset($row[$name])) {
if (isset($row[$name])) { $row[$name] = boolval($row[$name]);
$row[$name] = boolval($row[$name]); }
} }
} }
} }
} }
} foreach ($data as $r) {
foreach ($data as $r) { $result[] = [
$result[] = array(
"type" => "insert", "type" => "insert",
"table_name" => $table_name, "table_name" => $table_name,
"values" => $r "values" => $r
); ];
}
return $result;
}
public function GetAllTableNames() {
$result = [];
if ($this->db->isPostgres()) {
$query = "SELECT table_name as name FROM information_schema.tables WHERE table_schema='public'";
} else {
$query = "SELECT * FROM sqlite_master WHERE type='table'";
} }
$tables = $this->db->fetchAllArray($query); return $result;
foreach ($tables as $table) { }
$result[] = $table['name'];
}
return $result;
}
public function DumpInserts() { /**
$table_names = $this->GetAllTableNames(); * Возвращает все имена таблиц
$result = array(); * @return list<string>
foreach ($table_names as $table_name) { */
$result = array_merge($result, $this->DumpTable($table_name)); public function getAllTableNames()
} {
return $result; $result = [];
} if ($this->db->isPostgres()) {
} $query = "SELECT table_name as name FROM information_schema.tables WHERE table_schema='public'";
} else {
$query = "SELECT * FROM sqlite_master WHERE type='table'";
}
$tables = $this->db->fetchAllArray($query);
foreach ($tables as $table) {
$result[] = $table['name'];
}
return $result;
}
/**
* Возвращает дамп всех таблиц
* @return array
*/
public function dumpInserts()
{
$table_names = $this->getAllTableNames();
$result = [];
foreach ($table_names as $table_name) {
$result = array_merge($result, $this->dumpTable($table_name));
}
return $result;
}
}

View file

@ -1,48 +1,67 @@
<?php <?php
class Database_PDOStatement extends PDOStatement implements IteratorAggregate namespace ctiso\Database;
use ctiso\Database\StatementIterator,
ctiso\Tools\StringUtil,
PDO;
use TheSeer\Tokenizer\Exception;
/**
* @implements \IteratorAggregate<int, array>
*/
class PDOStatement extends \PDOStatement implements \IteratorAggregate
{ {
/** @var int */
protected $cursorPos = 0; protected $cursorPos = 0;
public $cache = array(); /** @var array<int, mixed> */
public $cache = [];
/** @var ?array */
public $fields; public $fields;
function getIterator(): Iterator { function getIterator(): \Iterator {
return new Database_StatementIterator($this); return new StatementIterator($this);
} }
protected function __construct() { protected function __construct() {
} }
function rewind() { function rewind(): void {
$this->cursorPos = 0; $this->cursorPos = 0;
} }
public function seek($rownum) { /**
if ($rownum < 0) { * @param int $rownum
return false; * @return bool
} */
public function seek($rownum): bool {
// PostgreSQL rows start w/ 0, but this works, because we are if ($rownum < 0) {
// looking to move the position _before_ the next desired position return false;
$this->cursorPos = $rownum; }
return true;
}
function valid() { // PostgreSQL rows start w/ 0, but this works, because we are
return true; // looking to move the position _before_ the next desired position
$this->cursorPos = $rownum;
return true;
} }
function valid(): bool {
return true;
}
/**
* @return bool
*/
public function first() { public function first() {
if($this->cursorPos !== 0) { $this->seek(0); } if ($this->cursorPos !== 0) { $this->seek(0); }
return $this->next(); return $this->next();
} }
function next() { function next(): bool{
if ($this->getRecordCount() > $this->cursorPos) { if ($this->getRecordCount() > $this->cursorPos) {
if (!isset($this->cache[$this->cursorPos])) { if (!isset($this->cache[$this->cursorPos])) {
$this->cache[$this->cursorPos] = $this->fetch(PDO::FETCH_ASSOC); $this->cache[$this->cursorPos] = $this->fetch(PDO::FETCH_ASSOC);
} }
$this->fields = $this->cache[$this->cursorPos]; $this->fields = $this->cache[$this->cursorPos];
$this->cursorPos++; $this->cursorPos++;
@ -53,50 +72,87 @@ class Database_PDOStatement extends PDOStatement implements IteratorAggregate
} }
} }
function key() { function key(): int {
return $this->cursorPos; return $this->cursorPos;
} }
/**
* @return mixed
*/
function current() { function current() {
return $this->cache[$this->cursorPos]; return $this->cache[$this->cursorPos];
} }
/**
* @return array|null
*/
function getRow() { function getRow() {
return $this->fields; return $this->fields;
} }
function getInt($name) { /**
* @param string $name
* @return int
*/
function getInt($name): int {
if (!$this->fields) { if (!$this->fields) {
throw new Error('no fields'); throw new \Exception('no fields');
} }
return (int)$this->fields[$name]; return (int)$this->fields[$name];
} }
/**
* @param string $name
* @return string
*/
function getBlob($name) { function getBlob($name) {
return $this->fields[$name]; return $this->fields[$name];
} }
/**
* @param string $name
* @return string
*/
function getString($name) { function getString($name) {
return isset($this->fields[$name]) ? $this->fields[$name]: null; return $this->fields[$name] ?? null;
} }
/**
* @param string $name
* @return bool
*/
function getBoolean($name) { function getBoolean($name) {
return (bool)$this->fields[$name]; return (bool)$this->fields[$name];
} }
/**
* @param string $name
* @return mixed
*/
function get($name) { function get($name) {
return $this->fields[$name]; return $this->fields[$name];
} }
/**
* @param string $name
* @return array
*/
function getArray($name) { function getArray($name) {
return Tools_String::strToArray($this->fields[$name]); return StringUtil::strToArray($this->fields[$name]);
} }
/**
* @return int
*/
function getRecordCount() { function getRecordCount() {
return count($this->cache); return count($this->cache);
} }
function execute($args = null) { /**
* @param array $args
* @return bool
*/
function execute($args = null): bool {
$result = parent::execute($args); $result = parent::execute($args);
return $result; return $result;
} }

View file

@ -1,47 +1,83 @@
<?php <?php
/** /**
* Класс оболочка для PDOStatement для замены Creole * Класс оболочка для PDOStatement для замены Creole
*/ */
class Database_Statement namespace ctiso\Database;
use PDO;
use ctiso\Database;
class Statement
{ {
/** @var ?int */
protected $limit = null; protected $limit = null;
/** @var ?int */
protected $offset = null; protected $offset = null;
/** @var never */
protected $statement = null; protected $statement = null;
protected $binds = array(); /** @var array{int|string, mixed, int}[] */
protected $binds = [];
/** @var Database */
protected $conn; protected $conn;
/** @var string */
protected $query; protected $query;
function __construct($query, $conn/*: Database*/) { /**
* @param string $query
* @param Database $conn
*/
function __construct($query, $conn) {
$this->query = $query; $this->query = $query;
$this->conn = $conn; $this->conn = $conn;
} }
function setInt($n, $value) { /**
$this->binds [] = array($n, $value, PDO::PARAM_INT); * @param int|string $n
* @param int $value
*/
function setInt($n, $value): void {
$this->binds [] = [$n, $value, PDO::PARAM_INT];
} }
function setString($n, $value) { /**
$this->binds [] = array($n, $value, PDO::PARAM_STR); * @param int|string $n
* @param string $value
*/
function setString($n, $value): void {
$this->binds [] = [$n, $value, PDO::PARAM_STR];
} }
function setBlob($n, $value) { /**
$this->binds [] = array($n, $value, PDO::PARAM_LOB); * @param int|string $n
* @param mixed $value
*/
function setBlob($n, $value): void {
$this->binds [] = [$n, $value, PDO::PARAM_LOB];
} }
function setLimit($limit) { /**
* @param int $limit
*/
function setLimit($limit): void {
$this->limit = $limit; $this->limit = $limit;
} }
function setOffset($offset) { /**
* @param int $offset
*/
function setOffset($offset): void {
$this->offset = $offset; $this->offset = $offset;
} }
/**
* @return PDOStatement
*/
function executeQuery() { function executeQuery() {
if ($this->limit) { if ($this->limit) {
$this->query .= " LIMIT {$this->limit} OFFSET {$this->offset}"; $this->query .= " LIMIT {$this->limit} OFFSET {$this->offset}";
} }
$stmt/*: Database_PDOStatement*/ = $this->conn->prepare($this->query); $stmt = $this->conn->prepare($this->query);
foreach ($this->binds as $bind) { foreach ($this->binds as $bind) {
list($n, $value, $type) = $bind; list($n, $value, $type) = $bind;
$stmt->bindValue($n, $value, (int) $type); $stmt->bindValue($n, $value, (int) $type);

View file

@ -1,46 +1,59 @@
<?php <?php
class Database_StatementIterator implements Iterator namespace ctiso\Database;
{ use PDO;
/**
* @implements \Iterator<array>
*/
class StatementIterator implements \Iterator
{
/** @var PDOStatement */
private $result; private $result;
/** @var int */
private $pos = 0; private $pos = 0;
private $fetchmode; /** @var int */
private $row_count; private $row_count;
public function __construct($rs/*: Database_PDOStatement*/) { /**
* @param PDOStatement $rs
*/
public function __construct($rs) {
$this->result = $rs; $this->result = $rs;
$this->row_count = $rs->getRecordCount(); $this->row_count = $rs->getRecordCount();
} }
function rewind() { function rewind(): void{
$this->pos = 0; $this->pos = 0;
} }
function valid() { function valid(): bool {
return ($this->pos < $this->row_count); return ($this->pos < $this->row_count);
} }
function key() { function key(): mixed {
return $this->pos; return $this->pos;
} }
function current() { function current(): mixed{
if (!isset($this->result->cache[$this->pos])) { if (!isset($this->result->cache[$this->pos])) {
$this->result->cache[$this->pos] = $this->result->fetch(PDO::FETCH_ASSOC); $this->result->cache[$this->pos] = $this->result->fetch(PDO::FETCH_ASSOC);
} }
return $this->result->cache[$this->pos]; return $this->result->cache[$this->pos];
} }
function next() { function next(): void {
$this->pos++; $this->pos++;
} }
function seek($index) { /**
$this->pos = $index; * @param int $index
*/
function seek($index): void {
$this->pos = $index;
} }
function count() { function count(): int {
return $this->row_count; return $this->row_count;
} }
} }

View file

@ -1,16 +1,22 @@
<?php <?php
class Excel_DateTime namespace ctiso\Excel;
class DateTime
{ {
/** @var int */
public $value; public $value;
function __construct($value) /**
* @param int $value
*/
function __construct($value)
{ {
$this->value = intval($value); $this->value = (int)$value;
} }
function getString() function getString(): string
{ {
return date('Y-m-d\TH:i:s.u', $this->value); return date('Y-m-d\TH:i:s.u', $this->value);
} }
} }

View file

@ -3,33 +3,45 @@
/** /**
* Документ * Документ
*/ */
class Excel_Document { namespace ctiso\Excel;
static $ns = "urn:schemas-microsoft-com:office:spreadsheet"; use XMLWriter,
private $table = array (); Exception;
protected $styles = array();
function addTable($table) { class Document {
/** @var string */
static $ns = "urn:schemas-microsoft-com:office:spreadsheet";
/** @var list<Table|callable> */
private $table = [];
/** @var array<string, array> */
protected $styles = [];
/**
* Добавление таблицы в документ
* @param Table|callable $table Таблица или функция, возвращающая таблицу
*/
function addTable($table): void {
$this->table [] = $table; $this->table [] = $table;
} }
/** /**
* Добавление стиля к документу * Добавление стиля к документу
* @param $name string Имя стиля * @param string $name string Имя стиля
* @param $values array Параметры стиля * @param array $values array Параметры стиля
* @param $type Тип стиля * @param string $type Тип стиля
*/ */
function setStyle ($name, array $values, $type = 'Interior') function setStyle ($name, array $values, $type = 'Interior'): void
{ {
if(!isset($this->styles[$name])) { if(!isset($this->styles[$name])) {
$this->styles[$name] = array(); $this->styles[$name] = [];
} }
$this->styles[$name][$type] = $values; $this->styles[$name][$type] = $values;
} }
/** /**
* Генерация стилей * Генерация стилей
*/ */
private function createStyles (XMLWriter $doc) { private function createStyles (XMLWriter $doc): void
{
$doc->startElement('Styles'); $doc->startElement('Styles');
foreach ($this->styles as $name => $sn) { foreach ($this->styles as $name => $sn) {
$doc->startElement('Style'); $doc->startElement('Style');
@ -39,7 +51,6 @@ class Excel_Document {
if ($type == 'Borders') { if ($type == 'Borders') {
$doc->startElement('Borders'); $doc->startElement('Borders');
foreach ($s as $border) { foreach ($s as $border) {
$border/*: array*/ = $border;
$doc->startElement('Border'); $doc->startElement('Border');
foreach ($border as $key => $value) { foreach ($border as $key => $value) {
$doc->writeAttribute('ss:' . $key, $value); $doc->writeAttribute('ss:' . $key, $value);
@ -62,21 +73,25 @@ class Excel_Document {
/** /**
* Преобразует переводы строки в спец символы * Преобразует переводы строки в спец символы
* @param string $s
* @return string
*/ */
function clean ($s) { function clean ($s) {
assert(is_string($s)); return strtr($s, ["\n" => "&#10;"]);
return strtr($s, array ("\n" => "&#10;"));
} }
/** /**
* Сохраняет таблицу в формате Office 2003 XML * Сохраняет таблицу в формате Office 2003 XML
* http://en.wikipedia.org/wiki/Microsoft_Office_XML_formats * http://en.wikipedia.org/wiki/Microsoft_Office_XML_formats
*
* @param string $filename
* @throws Exception
* @return void
*/ */
function save($filename) function save($filename): void
{ {
$doc = new XMLWriter(); $doc = new XMLWriter();
if (!$doc->openURI($filename)) { if (!$doc->openUri($filename)) {
throw new Exception("unknown file " . $filename); throw new Exception("unknown file " . $filename);
} }
$doc->setIndent(false); $doc->setIndent(false);
@ -86,9 +101,9 @@ class Excel_Document {
$doc->writeAttribute('xmlns:ss', self::$ns); $doc->writeAttribute('xmlns:ss', self::$ns);
$this->createStyles($doc); $this->createStyles($doc);
foreach ($this->table as $table) { foreach ($this->table as $table) {
if ($table instanceof Excel_Table) { if ($table instanceof Table) {
$table->createTable($doc); $table->createTable($doc);
} else { } else {
$table_data = call_user_func($table); $table_data = call_user_func($table);

View file

@ -1,17 +1,23 @@
<?php <?php
class Excel_Number namespace ctiso\Excel;
class Number
{ {
/** @var int */
public $value; public $value;
function __construct($value) /**
* @param int|float $value
*/
function __construct($value)
{ {
$this->value = (int)($value); $this->value = (int)($value);
} }
function getString() function getString(): string
{ {
return $this->value; return (string) $this->value;
} }
} }

View file

@ -1,14 +1,25 @@
<?php <?php
/** /**
* Клетка таблицы * Клетка таблицы
*/ */
class TableCell namespace ctiso\Excel;
use XMLWriter,
ctiso\Excel\DateTime as Excel_DateTime,
ctiso\Excel\Number as Excel_Number;
class TableCell
{ {
/** @var string|false */
public $style = false; public $style = false;
/** @var string */
public $value; public $value;
/** @var bool */
public $merge = false; public $merge = false;
/**
* @param string $value Значение клетки
*/
function __construct ($value) function __construct ($value)
{ {
$this->value = $value; $this->value = $value;
@ -16,65 +27,86 @@ class TableCell
} }
/** /**
* Ряд таблицы * Ряд таблицы
*/ */
class TableRow class TableRow
{ {
/** @var string|false */
public $style = false; public $style = false;
public $cells = array(); /** @var TableCell[] */
public $cells = [];
/** @var int|false */
public $height = false; public $height = false;
function setCell($y, $value) /**
* Устанавливает значение для клетки
* @param int $y Номер столбца
* @param string $value Значение клетки
*/
function setCell($y, $value): void
{ {
$this->cells[$y] = new TableCell($value); $this->cells[$y] = new TableCell($value);
} }
function setCellStyle($y, $name) /**
* Устанавливает стиль для клетки
* @param int $y Номер столбца
* @param string $name Имя стиля
*/
function setCellStyle($y, $name): void
{ {
$this->cells[$y]->style = $name; $this->cells[$y]->style = $name;
} }
} }
/** /**
* Таблица * Таблица
*/ */
class Excel_Table class Table
{ {
/** @var int */
static $index; static $index;
/** @var string */
private $name; private $name;
private $style; /** @var TableRow[] */
protected $rows = array(); protected $rows = [];
/** @var int|false */
protected $_splitVertical = false; protected $_splitVertical = false;
/** @var int|false */
protected $_splitHorizontal = false; protected $_splitHorizontal = false;
function __construct() function __construct()
{ {
$this->name = "Page " . intval(self::$index ++); $this->name = "Page " . ((int)self::$index ++);
} }
/** /**
* Записать значение в клетку с заданными координатами * Записать значение в клетку с заданными координатами
* @param int $x Номер ряда
* @param int $y Номер столбца
* @param string $value Значение клетки
*/ */
function setCell($x, $y, $value) function setCell(int $x, int $y, $value): void
{ {
assert(is_numeric($x) && $x > 0); assert($x > 0);
assert(is_numeric($y) && $y > 0); assert($y > 0);
if(! isset($this->rows[$x])) { if(! isset($this->rows[$x])) {
$this->rows[$x] = new TableRow(); $this->rows[$x] = new TableRow();
} }
$row/*: TableRow*/ = $this->rows[$x];
$row = $this->rows[$x];
$row->setCell($y, $value); $row->setCell($y, $value);
} }
/** /**
* Заполняет ряд начиная с указанного столбца значениями из массива * Заполняет ряд начиная с указанного столбца значениями из массива
*/ */
function setRow($row, $index, array $data) function setRow(int $row, int $index, array $data): void
{ {
assert(is_numeric($index) && $index > 0); assert($index > 0);
assert(is_numeric($row) && $row > 0); assert($row > 0);
reset($data); reset($data);
for ($i = $index; $i < $index + count($data); $i++) { for ($i = $index; $i < $index + count($data); $i++) {
@ -85,115 +117,125 @@ class Excel_Table
/** /**
* Устанавливает высоту ряда * Устанавливает высоту ряда
* @param $row integer Номер ряда * @param int $row Номер ряда
* @parma $value real Высота ряда * @param int $value Высота ряда
*/ */
function setRowHeight ($row, $value) function setRowHeight (int $row, $value): void
{ {
assert(is_numeric($row) && $row > 0); assert($row > 0);
$this->rows[$row]->height = $value; $this->rows[$row]->height = $value;
} }
/** /**
* Устанавливает стиль ряда * Устанавливает стиль ряда
* @param $row integer Номер ряда * @param int $row Номер ряда
* @parma $name string Имя стиля * @param string $name Имя стиля
*/ */
function setRowStyle ($row, $name) function setRowStyle(int $row, $name): void
{ {
assert(is_numeric($row) && $row > 0); assert($row > 0);
$this->rows[$row]->style = $name; $this->rows[$row]->style = $name;
} }
/** /**
* Обьединяет клетки в строке * Обьединяет клетки в строке
* @param $row Номер ряда * @param int $x Номер ряда
* @param $cell Номер столбца * @param int $cell Номер столбца
* @param $merge Количество клеток для обьединения * @param bool $merge Количество клеток для обьединения
*/ */
function setCellMerge($x, $cell, $merge) function setCellMerge(int $x, int $cell, $merge): void
{ {
assert(is_numeric($x) && $x > 0); assert($x > 0);
assert(is_numeric($cell) && $cell > 0); assert($cell > 0);
$row/*: TableRow*/ = $this->rows[$x]; $row = $this->rows[$x];
$row->cells[$cell]->merge = $merge; $row->cells[$cell]->merge = $merge;
} }
/** /**
* Устанавливает стиль для клеток ряда * Устанавливает стиль для клеток ряда
* @param $row integer Номер ряда * @param int $row Номер ряда
* @param $y integer Номер столбца * @param int $y Номер столбца
* @parma $name string Имя стиля * @param string $name Имя стиля
*/ */
function setCellStyle ($row, $y, $name) function setCellStyle ($row, $y, $name): void
{ {
if (isset($this->rows[$row])) if (isset($this->rows[$row])) {
$this->rows[$row]->setCellStyle($y, $name); $this->rows[$row]->setCellStyle($y, $name);
}
} }
/** /**
* Добавляет строку к таблице * Добавляет строку к таблице
*/ * @return int Номер добавленной строки
function addRow($index = 1, array $data = array("")) */
function addRow(int $index = 1, array $data = [""])
{ {
assert(is_numeric($index) && $index > 0); assert($index > 0);
$offset = $this->getRows() + 1; $offset = $this->getRows() + 1;
$this->setRow($offset, $index, $data); $this->setRow($offset, $index, $data);
return $offset; return $offset;
} }
/** /**
* Количество строк в таблице * Количество строк в таблице
* *
* @return int * @return int
*/ */
function getRows() function getRows()
{ {
$keys/*: array*/ = array_keys($this->rows); // Высчитываем максимальный индекс, массив может быть разрежен поэтому используем array_keys
return max($keys); $keys = array_keys($this->rows);
return $keys === [] ? 0 : max($keys);
} }
/** /**
* Количество столбцов в строке * Количество столбцов в строке
* *
* @return int * @return int
*/ */
function getRowCells(TableRow $row) function getRowCells(TableRow $row)
{ {
$keys/*: array*/ = array_keys($row->cells); $keys = array_keys($row->cells);
return max($keys); return $keys === [] ? 0 :max($keys);
} }
/** /**
* Разделяет таблицу на две части по вертикали * Разделяет таблицу на две части по вертикали
* @param $n integer Количество столбцов слева * @param int $n Количество столбцов слева
*/ */
function splitVertical($n) { function splitVertical($n): void {
$this->_splitVertical = $n; $this->_splitVertical = $n;
} }
/** /**
* Разделяет таблицу на две части по горизонтали * Разделяет таблицу на две части по горизонтали
* @param $n integer Количество столбцов сверху * @param int $n Количество столбцов сверху
*/ */
function splitHorizontal($n) { function splitHorizontal($n): void {
$this->_splitHorizontal = $n; $this->_splitHorizontal = $n;
} }
/** /**
* Количество столбцов в таблице * Количество столбцов в таблице
* *
* @return int * @return int
*/ */
function getColumns() { function getColumns() {
return max(array_map(array($this, 'getRowCells'), $this->rows)); $columns = array_map($this->getRowCells(...), $this->rows);
return $columns === [] ? 0 : max($columns);
} }
/**
* Кодирование строки
* @deprecated Можно заменить на значение
* @param string $s Строка
* @return string
*/
function encode($s) function encode($s)
{ {
return $s; return $s;
@ -201,8 +243,13 @@ class Excel_Table
/** /**
* Генерация клетки таблицы (Переработать) * Генерация клетки таблицы (Переработать)
* @param TableCell $ncell Клетка таблицы
* @param XMLWriter $doc XMLWriter
* @param int $j Индекс клетки
* @param mixed $value Значение клетки
* @param bool $setIndex Устанавливать индекс клетки в атрибут ss:Index
*/ */
function createCell (TableCell $ncell, XMLWriter $doc, $j, $value/*: any*/, $setIndex) { function createCell (TableCell $ncell, XMLWriter $doc, $j, mixed $value, $setIndex): void {
$doc->startElement("Cell"); $doc->startElement("Cell");
if ($ncell->style) { if ($ncell->style) {
@ -210,11 +257,11 @@ class Excel_Table
} }
if ($ncell->merge) { if ($ncell->merge) {
$doc->writeAttribute('ss:MergeAcross', $ncell->merge); $doc->writeAttribute('ss:MergeAcross', (string)$ncell->merge);
} }
if ($setIndex) { if ($setIndex) {
$doc->writeAttribute('ss:Index', $j); $doc->writeAttribute('ss:Index', (string)$j);
} }
$doc->startElement("Data"); $doc->startElement("Data");
@ -223,23 +270,23 @@ class Excel_Table
$doc->text($value->getString()); $doc->text($value->getString());
} else if ($value instanceof Excel_Number) { } else if ($value instanceof Excel_Number) {
$doc->writeAttribute('ss:Type', "Number"); $doc->writeAttribute('ss:Type', "Number");
$doc->text($value->getString()); $doc->text($value->getString());
} else { } else {
if (is_string($value)) { if (is_string($value)) {
$doc->writeAttribute('ss:Type', "String"); $doc->writeAttribute('ss:Type', "String");
} else { } else {
$doc->writeAttribute('ss:Type', "Number"); $doc->writeAttribute('ss:Type', "Number");
} }
$doc->writeCData($this->encode($value)); $doc->writeCdata($value);
} }
$doc->endElement();
$doc->endElement(); $doc->endElement();
$doc->endElement();
} }
/** /**
* Генерация таблицы * Генерация таблицы
*/ */
public function createTable (XMLWriter $doc) { public function createTable (XMLWriter $doc): void {
$doc->startElement('Worksheet'); $doc->startElement('Worksheet');
$doc->writeAttribute('ss:Name', $this->name); $doc->writeAttribute('ss:Name', $this->name);
@ -247,8 +294,8 @@ class Excel_Table
$rows = $this->getRows(); $rows = $this->getRows();
$doc->startElement('Table'); $doc->startElement('Table');
$doc->writeAttribute('ss:ExpandedColumnCount', $columns); $doc->writeAttribute('ss:ExpandedColumnCount', (string)$columns);
$doc->writeAttribute('ss:ExpandedRowCount', $rows); $doc->writeAttribute('ss:ExpandedRowCount', (string)$rows);
// Переписать цыкл !!!!!!! // Переписать цыкл !!!!!!!
for ($i = 1; $i <= $rows; $i++) { for ($i = 1; $i <= $rows; $i++) {
@ -259,26 +306,26 @@ class Excel_Table
} }
if ($this->rows[$i]->height) { if ($this->rows[$i]->height) {
$doc->writeAttribute('ss:Height', $this->rows[$i]->height); $doc->writeAttribute('ss:Height', (string)$this->rows[$i]->height);
} }
/** @var TableRow $nrow */
$nrow/*: TableRow*/ = $this->rows[$i]; $nrow = $this->rows[$i];
// Флаг индикатор подстановки номера столбца // Флаг индикатор подстановки номера столбца
$setIndex = false; $setIndex = false;
for ($j = 1; $j <= $columns; $j++) { for ($j = 1; $j <= $columns; $j++) {
$value = null; $value = null;
if (isset($nrow->cells[$j])) { if (isset($nrow->cells[$j])) {
$value = $nrow->cells[$j]->value; $value = $nrow->cells[$j]->value;
} }
if (!empty($value)) { if (!empty($value)) {
$this->createCell($nrow->cells[$j], $doc, $j, $value, $setIndex); $this->createCell($nrow->cells[$j], $doc, $j, $value, $setIndex);
$setIndex = false; $setIndex = false;
} else { } else {
$setIndex = true; $setIndex = true;
} }
} }
} }
$doc->endElement(); $doc->endElement();
} }
@ -287,25 +334,24 @@ class Excel_Table
$doc->endElement(); $doc->endElement();
} }
protected function splitPane (XMLWriter $doc) { protected function splitPane (XMLWriter $doc): void {
$doc->startElement('WorksheetOptions'); $doc->startElement('WorksheetOptions');
$doc->writeAttribute('xmlns', 'urn:schemas-microsoft-com:office:excel'); $doc->writeAttribute('xmlns', 'urn:schemas-microsoft-com:office:excel');
$doc->writeElement('FrozenNoSplit'); $doc->writeElement('FrozenNoSplit');
if ($this->_splitVertical) { if ($this->_splitVertical) {
$doc->writeElement('SplitVertical', $this->_splitVertical); $doc->writeElement('SplitVertical', (string) $this->_splitVertical);
$doc->writeElement('LeftColumnRightPane', $this->_splitVertical); $doc->writeElement('LeftColumnRightPane', (string) $this->_splitVertical);
} }
if ($this->_splitHorizontal) { if ($this->_splitHorizontal) {
$doc->writeElement('SplitHorizontal', $this->_splitHorizontal); $doc->writeElement('SplitHorizontal', (string) $this->_splitHorizontal);
$doc->writeElement('TopRowBottomPane', $this->_splitHorizontal); $doc->writeElement('TopRowBottomPane', (string) $this->_splitHorizontal);
} }
if ($this->_splitHorizontal && $this->_splitVertical) { if ($this->_splitHorizontal && $this->_splitVertical) {
$doc->writeElement('ActivePane', 0); $doc->writeElement('ActivePane', (string) 0);
} else if($this->_splitHorizontal) { } else if($this->_splitHorizontal) {
$doc->writeElement('ActivePane', 2); $doc->writeElement('ActivePane', (string) 2);
} }
$doc->endElement(); $doc->endElement();
} }
} }

View file

@ -1,8 +1,16 @@
<?php <?php
namespace ctiso;
use Exception;
class File { class File {
/**
* @param string $filename
* @return string
* @throws Exception
*/
static function getContents($filename) { static function getContents($filename) {
$buffer = file_get_contents($filename); $buffer = @file_get_contents($filename);
if ($buffer !== false) { if ($buffer !== false) {
return $buffer; return $buffer;
} }

View file

@ -1,29 +1,48 @@
<?php <?php
/** /**
* Фильтр действий * Фильтр действий
*/ */
class Filter_ActionAccess namespace ctiso\Filter;
{
public $access = array();
public $processor;
public $name;
function __construct($processor/*: Filter_Filter*/) { use ctiso\HttpRequest;
use ctiso\Role\User;
class ActionAccess
{
/** @var array */
public $access = [];
/** @var FilterInterface */
public $processor;
/** @var User */
public $user;
/**
* @param FilterInterface $processor
* @param User $user
*/
function __construct($processor, $user) {
$this->processor = $processor; $this->processor = $processor;
$this->user = $user;
} }
/** /**
* Проверка доступных действий для пользователя * Проверка доступных действий для пользователя
* !! Реализация класса проверки действий не должна быть внутри Контроллера!!! * !! Реализация класса проверки действий не должна быть внутри Контроллера!!!
* Информация о доступе может быть в файле, базе данных и т.д. * Информация о доступе может быть в файле, базе данных и т.д.
*
* @param string $action
* @return bool
*/ */
function checkAction($action) { function checkAction($action) {
// Импликация !! http://ru.wikipedia.org/wiki/Импликация // Импликация !! http://ru.wikipedia.org/wiki/Импликация
$name = $this->name; return (!isset($this->access[$action]) || in_array($this->user->access, $this->access[$action]));
return (!isset($this->access[$name][$action]) || in_array(Filter_UserAccess::$access, $this->access[$name][$action]));
} }
/**
* @param HttpRequest $request
* @return mixed
*/
function execute(HttpRequest $request) { function execute(HttpRequest $request) {
$action = $request->getAction(); $action = $request->getAction();
if(! $this->checkAction($action)) { if(! $this->checkAction($action)) {

View file

@ -1,21 +1,44 @@
<?php <?php
class Filter_ActionLogger namespace ctiso\Filter;
{ use ctiso\Role\UserInterface,
public $before = array(); ctiso\HttpRequest;
/* Переделать формат Логов на список json */
class ActionLogger implements FilterInterface
{
/** @var array */
public $before = [];
/** @var resource */
public $file; public $file;
/** @var UserInterface */
public $user;
/** @var string */
public $action; public $action;
/** @var \ctiso\Controller\ActionInterface */
public $processor; public $processor;
function __construct($processor/*: Filter_Filter*/) { /**
* @param \ctiso\Controller\ActionInterface $processor
* @param string $logPath
* @param UserInterface $user
*/
function __construct($processor, $logPath, $user) {
$this->processor = $processor; $this->processor = $processor;
$this->file = fopen(Shortcut::getUrl('access.log'), "a"); $this->user = $user;
$file = fopen($logPath, "a");
if (is_resource($file)) {
$this->file = $file;
} else {
throw new \Exception('Ошибка открытия файла ' . $logPath);
}
} }
function execute(HttpRequest $request) { function execute(HttpRequest $request) {
$action = $request->getAction(); $action = $request->getAction();
if(in_array($action, $this->before)) { if(in_array($action, $this->before)) {
$line = ['time' => time(), 'user' => Filter_UserAccess::$name, 'sid' => session_id(), 'query' => array_merge($_POST, $_GET)]; $line = ['time' => time(), 'user' => $this->user->getName(), 'sid' => session_id(), 'query' => array_merge($_POST, $_GET)];
fwrite($this->file, json_encode($line) . "\n"); fwrite($this->file, json_encode($line) . "\n");
} }
return $this->processor->execute($request); return $this->processor->execute($request);

View file

@ -1,10 +1,24 @@
<?php <?php
class Filter_Authorization { namespace ctiso\Filter;
class Authorization {
const SESSION_BROWSER_SIGN_SECRET = '@w3dsju45Msk#'; const SESSION_BROWSER_SIGN_SECRET = '@w3dsju45Msk#';
const SESSION_BROWSER_SIGN_KEYNAME = 'session.app.browser.sign'; const SESSION_BROWSER_SIGN_KEYNAME = 'session.app.browser.sign';
/** @var string */
public $group;
static function isLogged($group = 'access') { /**
* @param string $group
*/
function __construct($group) {
$this->group = $group;
}
/**
* @param string $group
*/
static function isLogged($group = 'access'): bool {
// echo session_status(); // echo session_status();
if (session_status() == PHP_SESSION_NONE) { if (session_status() == PHP_SESSION_NONE) {
session_start(); session_start();
@ -16,37 +30,45 @@ class Filter_Authorization {
// UserAccess::getUserById($_SESSION ['access']); // Поиск по идентификатору // UserAccess::getUserById($_SESSION ['access']); // Поиск по идентификатору
return true; return true;
} else { } else {
return false; return false;
} }
} }
return false; return false;
} }
static function enter($id, $group = 'access') /**
{ * @param int $id
* @param string $group
*/
static function enter($id, $group = 'access'): void {
// $db->executeQuery("UPDATE visitor SET sid = '' WHERE id_visitor = " . $result->getInt('id_user')); // $db->executeQuery("UPDATE visitor SET sid = '' WHERE id_visitor = " . $result->getInt('id_user'));
// session_register("access"); // session_register("access");
// session_register("time"); // session_register("time");
// $_SESSION ["group"] = $result->getInt('access'); $_SESSION [$group] = $id;
$_SESSION [$group] = $id; // id_user
$_SESSION [self::SESSION_BROWSER_SIGN_KEYNAME] = self::getBrowserSign(); $_SESSION [self::SESSION_BROWSER_SIGN_KEYNAME] = self::getBrowserSign();
$_SESSION ["time"] = time(); $_SESSION ["sign"] = self::getRawSign();
$_SESSION ["time"] = time();
} }
private static function getBrowserSign() static function getRawSign(): string
{ {
$rawSign = self::SESSION_BROWSER_SIGN_SECRET; $rawSign = self::SESSION_BROWSER_SIGN_SECRET;
// $signParts = array('HTTP_USER_AGENT', 'HTTP_ACCEPT_ENCODING'); $signParts = ['HTTP_USER_AGENT'];
$signParts = array();
foreach ($signParts as $signPart) { foreach ($signParts as $signPart) {
$rawSign .= '::' . (isset($_SERVER[$signPart]) ? $_SERVER[$signPart] : 'none'); $rawSign .= '::' . ($_SERVER[$signPart] ?? 'none');
} }
return md5($rawSign);
return $rawSign;
} }
static function logout() { static function getBrowserSign(): string
{
return md5(self::getRawSign());
}
function logout(): void {
session_destroy(); session_destroy();
} }
} }

View file

@ -3,26 +3,46 @@
/** /**
* Попытка реализовать фильтр для запросов * Попытка реализовать фильтр для запросов
*/ */
class Filter_Filter namespace ctiso\Filter;
use ctiso\Database;
use ctiso\HttpRequest;
use ctiso\Controller\ActionInterface;
class Filter implements ActionInterface
{ {
/** @var ActionInterface */
public $processor; public $processor;
public function __construct($processor/*: Controller_Action*/)
/**
* @param ActionInterface $processor
*/
public function __construct($processor)
{ {
$this->processor = $processor; $this->processor = $processor;
} }
public function execute(HttpRequest $request) public function execute(HttpRequest $request)
{ {
return $this->processor->execute($request); return $this->processor->execute($request);
} }
public function getView($name, $class = 'View_Top') /**
* @param string $name
* @param class-string<\ctiso\View\View> $class
* @return \ctiso\View\View
*/
public function getView($name, $class = \ctiso\View\Top::class)
{ {
return $this->processor->getView($name, $class); return $this->processor->getView($name, $class);
} }
public function getConnection() public function getConnection(): Database
{ {
return $this->processor->getConnection(); return $this->processor->getConnection();
} }
public function addUrlPart($key, $value): void {
$this->processor->addUrlPart($key, $value);
}
} }

View file

@ -0,0 +1,12 @@
<?php
namespace ctiso\Filter;
use ctiso\HttpRequest;
interface FilterInterface {
/**
* @param HttpRequest $request
* @return mixed
*/
function execute(HttpRequest $request);
}

View file

@ -2,48 +2,70 @@
/** /**
* Фильтр для проверки авторизации * Фильтр для проверки авторизации
*
* action: login(password, login)
* action: logout()
*/ */
namespace ctiso\Filter;
use ctiso\Filter\Filter;
use ctiso\HttpRequest;
use ctiso\Settings;
use ctiso\Registry;
use ctiso\Database;
use ctiso\Role\User;
use ctiso\Collection;
use ctiso\Path;
use ctiso\Database\PDOStatement;
// В класс авторизации передавать обьект для управления пользователем // В класс авторизации передавать обьект для управления пользователем
// Вынести в отдельный файл // Вынести в отдельный файл
class Filter_Login extends Filter_Filter class Login extends Filter
{ {
const SESSION_BROWSER_SIGN_SECRET = '@w3dsju45Msk#'; const SESSION_BROWSER_SIGN_SECRET = '@w3dsju45Msk#';
const SESSION_BROWSER_SIGN_KEYNAME = 'session.app.browser.sign'; const SESSION_BROWSER_SIGN_KEYNAME = 'session.app.browser.sign';
const AUTH_MAX_ATTEMPT = 10; const AUTH_MAX_ATTEMPT = 10;
const AUTH_LAST_ATTEMPT_TIMER = 600; const AUTH_LAST_ATTEMPT_TIMER = 600;
/** @var string */
public $mode = 'ajax'; public $mode = 'ajax';
/** @var PDOStatement */
public $user; public $user;
/** @var User */
public $role;
/** @var Registry */
public $config;
function __construct($processor, User $role, Registry $config) {
parent::__construct($processor);
$this->role = $role;
$this->config = $config;
}
/** /**
* Проверка авторизации * Проверка авторизации
* @return Boolean Авторизовани пользователь или нет * @param HttpRequest $request
* @return bool Авторизовани пользователь или нет
*/ */
public function isLoggin(HttpRequest $request) public function isLoggin(HttpRequest $request)
{ {
// Авторизация // Авторизация
session_start(); session_start();
$db = $this->getConnection();
Filter_UserAccess::setUp($db); // Соединение
switch ($request->getAction()) { switch ($request->getAction()) {
// Авторизация по постоянному паролю // Авторизация по постоянному паролю
case 'login': case 'login':
$login = $request->get('login'); $login = $request->getString('login', '') ;
$password = $request->get('password'); $password = $request->getString('password');
$result = Filter_UserAccess::getUserByLogin($login); // Поиск по логину $result = $this->role->getUserByLogin($login); // Поиск по логину
if ($result) { if ($result) {
$userPassword = $result->getString('password'); $userPassword = $this->role->getUserPassword($result);
if (Filter_UserAccess::$access == 'site_root' && defined('PARENT_PATH')) { if ($this->role->access == 'site_root' && defined('PARENT_PATH')) {
$s = new Settings(PARENT_PATH . '/settings.json'); $s = new Settings(PARENT_PATH . '/settings.json');
$s->read(); $s->read();
$dsn = $s->readKey(array('system', 'dsn')); $dsn = $s->readKey(['system', 'dsn']);
$db = Database::getConnection($dsn); $db = Database::getConnection($dsn);
$user = $db->fetchOneArray("SELECT * FROM users WHERE login = :login", ['login' => $login]); $user = $db->fetchOneArray("SELECT * FROM users WHERE login = :login", ['login' => $login]);
if ($user === false) {
return false;
}
$userPassword = $user['password']; $userPassword = $user['password'];
} /*else if (time() - $result->getInt('lastupdate') > 60*60*24*60) { } /*else if (time() - $result->getInt('lastupdate') > 60*60*24*60) {
// Проверить давность пароля, 60 дней // Проверить давность пароля, 60 дней
@ -52,30 +74,23 @@ class Filter_Login extends Filter_Filter
return false; return false;
}*/ }*/
// Проверка на количества попыток авторизации // Проверка на количества попыток авторизации
$lastAttempt = $db->fetchOneArray( $lastAttempt = $result;
"SELECT trie_count, trie_time FROM users WHERE login = :login", ['login' => $request->get('login')]);
if ($lastAttempt['trie_count'] >= self::AUTH_MAX_ATTEMPT /*&& time() - $lastAttempt['trie_time'] < self::AUTH_LAST_ATTEMPT_TIMER*/) { if ($lastAttempt->get('trie_count') >= self::AUTH_MAX_ATTEMPT /*&& time() - $lastAttempt['trie_time'] < self::AUTH_LAST_ATTEMPT_TIMER*/) {
if (time() - $lastAttempt['trie_time'] < self::AUTH_LAST_ATTEMPT_TIMER) { if (time() - $lastAttempt->get('trie_time') < self::AUTH_LAST_ATTEMPT_TIMER) {
$request->set('timeout_error', true); $request->set('timeout_error', true);
break; break;
} else { } else {
$db->executeQuery( $this->role->resetTries($request->getString('login'));
"UPDATE users SET trie_count = :count WHERE login = :login",
['count' => 0, 'login' => $request->get('login')]
);
} }
} }
// Извлечнеие пользователя из родительской CMS, для проверки пароля // Извлечнеие пользователя из родительской CMS, для проверки пароля
if (md5($password) == $userPassword) { // password if (md5($password) == $userPassword) { // password
$this->enter($db, $result); $this->enter($result);
return true; return true;
} else { } else {
// Обновление количества неудачных попыток входа // Обновление количества неудачных попыток входа
$user = $db->fetchOneArray("SELECT id_user, trie_count FROM users WHERE login = :login", ['login' => $login]); $this->role->updateTries($login);
$db->executeQuery(
"UPDATE users SET trie_time = :cur_time, trie_count = :count WHERE id_user = :id_user",
['cur_time' => time(), 'count' => $user['trie_count']+=1, 'id_user' => $user['id_user']]
);
} }
} }
$request->set('error', true); $request->set('error', true);
@ -88,22 +103,22 @@ class Filter_Login extends Filter_Filter
case 'enter': case 'enter':
$login = $request->get('login'); $login = $request->get('login');
$password = $request->get('sid'); $password = $request->get('sid');
$result = Filter_UserAccess::getUserByLogin($login); // Поиск по логину $result = $this->role->getUserByLogin($login); // Поиск по логину
if ($result) { if ($result) {
$temp = md5($result->getString('password') . $result->getString('login') . $result->getString('sid')); $temp = md5($result->getString('password') . $result->getString('login') . $result->getString('sid'));
if ($password == $temp) { if ($password == $temp) {
$this->enter($db, $result); $this->enter($result);
return true; return true;
} }
} }
break; break;
*/ */
default: default:
$hash = $this->getBrowserSign(); $hash = Authorization::getBrowserSign();
// Если $hash не совпадает $_SESSION['hash'] то удаляем сессию // Если $hash не совпадает $_SESSION['hash'] то удаляем сессию
if (isset($_SESSION ['access']) && isset($_SESSION[self::SESSION_BROWSER_SIGN_KEYNAME])) { if (isset($_SESSION ['access']) && isset($_SESSION[self::SESSION_BROWSER_SIGN_KEYNAME])) {
if ($hash == $_SESSION[self::SESSION_BROWSER_SIGN_KEYNAME]) { if ($hash == $_SESSION[self::SESSION_BROWSER_SIGN_KEYNAME]) {
$this->user = $user = Filter_UserAccess::getUserById($_SESSION['access']); // Поиск по идентификатору $this->user = $user = $this->role->getUserById($_SESSION['access']); // Поиск по идентификатору
if ($user && isset($_SESSION['random']) && ($user->get('sid') == $_SESSION['random'])) { if ($user && isset($_SESSION['random']) && ($user->get('sid') == $_SESSION['random'])) {
return true; return true;
} }
@ -116,40 +131,34 @@ class Filter_Login extends Filter_Filter
return false; return false;
} }
private function getBrowserSign() /**
{ * Вход в систему
$rawSign = self::SESSION_BROWSER_SIGN_SECRET; * @param PDOStatement $result
//$signParts = array('HTTP_USER_AGENT', 'HTTP_ACCEPT_ENCODING'); */
$signParts = array(); private function enter($result): void
foreach ($signParts as $signPart) {
$rawSign .= '::' . (isset($_SERVER[$signPart]) ? $_SERVER[$signPart] : 'none');
}
return md5($rawSign);
}
private function enter($db, $result)
{ {
$this->user = $result; $this->user = $result;
$random = rand(0, 1024 * 1024); $random = rand(0, 1024 * 1024);
$db->executeQuery("UPDATE users SET sid = '$random', trie_count = 0 WHERE id_user = " . $result->getInt('id_user')); $this->role->setSID((string)$random, $result);
$_SESSION["group"] = $result->getInt('access'); $_SESSION["group"] = $result->getInt('access');
$_SESSION["access"] = $result->getInt('id_user'); // id_user $_SESSION["access"] = $result->getInt('id_user'); // id_user
$_SESSION["random"] = $random; // id_user $_SESSION["random"] = $random; // id_user
$_SESSION[self::SESSION_BROWSER_SIGN_KEYNAME] = $this->getBrowserSign(); $_SESSION[self::SESSION_BROWSER_SIGN_KEYNAME] = Authorization::getBrowserSign();
$_SESSION["time"] = time(); $_SESSION["time"] = time();
} }
public function execute(HttpRequest $request) /**
* @return mixed
*/
public function execute(HttpRequest $request): mixed
{ {
$logged = $this->isLoggin($request); $logged = $this->isLoggin($request);
if ($request->get('action') == 'user_access') { if ($request->getString('action') == 'user_access') {
if ($logged) { if ($logged) {
$result = array(); $result = [];
$result['fullname'] = $this->user->getString('patronymic') . " " . $this->user->getString('firstname'); $result['fullname'] = $this->user->getString('patronymic') . " " . $this->user->getString('firstname');
$result['email'] = $this->user->getString('email'); $result['email'] = $this->user->getString('email');
$result['site'] = 187;
$result['hash'] = sha1(self::SESSION_BROWSER_SIGN_SECRET . $this->user->getString('email')); $result['hash'] = sha1(self::SESSION_BROWSER_SIGN_SECRET . $this->user->getString('email'));
return json_encode($result); return json_encode($result);
} else { } else {
@ -157,29 +166,32 @@ class Filter_Login extends Filter_Filter
} }
} }
if ($request->get('action') == 'relogin') { if ($request->getString('action') == 'relogin') {
if ($logged) { if ($logged) {
return json_encode(array('result' => 'ok', 'message' => "Авторизация успешна")); return json_encode(['result' => 'ok', 'message' => "Авторизация успешна"]);
} else { } else {
return json_encode(array('result' => 'fail', 'message' => "Неправильное имя пользователя или пароль")); return json_encode(['result' => 'fail', 'message' => "Неправильное имя пользователя или пароль"]);
} }
} }
if (!$logged) { if (!$logged) {
// Параметры при неправильной авторизации // Параметры при неправильной авторизации
// Действия по умолчанию !! Возможно переход на форму регистрации // Действия по умолчанию !! Возможно переход на форму регистрации
if ($request->get('mode') == 'ajax') { if ($request->getString('mode') == 'ajax') {
if (!$this->requestIsWhite($request)) { if (!$this->requestIsWhite($request)) {
return json_encode(array('result' => 'fail', 'message' =>"NOT_AUTHORIZED")); return json_encode(['result' => 'fail', 'message' =>"NOT_AUTHORIZED"]);
} }
} else { } else {
$request->set('module', 'login'); $request->set('module', 'login');
$request->set('mode', $this->mode); $request->set('mode', $this->mode);
} }
} else if (isset($_SERVER['HTTP_REFERER'])) { } else if (isset($_SERVER['HTTP_REFERER'])) {
$arr = array(); $arr = [];
parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $arr); parse_str(parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_QUERY) ?: '', $arr);
if (isset($arr['back_page']) && $request->get('mode') != 'ajax') { if (isset($arr['back_page'])
&& is_string($arr['back_page'])
&& $request->getString('mode') != 'ajax')
{
$request->redirect($arr['back_page']); $request->redirect($arr['back_page']);
} }
} }
@ -188,20 +200,23 @@ class Filter_Login extends Filter_Filter
return $text; return $text;
} }
/* --------------------- /**
* Проверка на попадание реквеста в белый список * Проверка на попадание реквеста в белый список
*/ */
public function requestIsWhite(Collection $request): bool {
public function requestIsWhite(Collection $request) {
$module = $request->get('module'); $module = $request->get('module');
$action = $request->get('action'); $action = $request->get('action');
$moduleDir = explode('_',$module)[0]; $moduleDir = explode('\\',$module)[0];
$file = Path::join(CMS_PATH, 'modules', $moduleDir, 'filters', 'white.json'); // TODO: Параметр для белого списка перенести в install.json
$file = Path::join($this->config->get('system', 'path'), 'modules', $moduleDir, 'filters', 'white.json');
if (file_exists($file)) { if (file_exists($file)) {
$whiteList = json_decode(file_get_contents($file), true); $text = file_get_contents($file);
if (!$text) {
if (in_array($action, $whiteList)) { return false;
}
$whiteList = json_decode($text, true);
if (is_array($whiteList) && in_array($action, $whiteList, true)) {
return true; return true;
} }
} }

View file

@ -1,69 +0,0 @@
<?php
// Класс должен быть в библиотеке приложения
class Filter_UserAccess
{
const LIFE_TIME = 1800; // = 30min * 60sec;
static $fullname;
static $name;
static $access;
static $password;
static $id;
static $db;
protected function __construct()
{
}
public static function setUp(Database $db)
{
self::$db = $db;
}
public static function getUserByQuery(Database_Statement $stmt)
{
global $GROUPS;
$result = $stmt->executeQuery();
if ($result->next()) {
self::$access = $GROUPS[$result->getString('access')];
self::$name = $result->getString('login');
self::$id = $result->getInt('id_user');
self::$password = $result->getString('password');
self::$fullname = implode(' ', array(
$result->getString('surname'),
$result->getString('firstname'),
$result->getString('patronymic')));
return $result;
}
return null;
}
public static function getUserByLogin($login)
{
$stmt = self::$db->prepareStatement("SELECT * FROM users WHERE login = ?");
$stmt->setString(1, $login);
$result = self::getUserByQuery($stmt);
if ($result) {
$time = time();
$id = self::$id;
self::$db->executeQuery("UPDATE users SET lasttime = $time WHERE id_user = $id"); // Время входа
}
return $result;
}
public static function getUserById($id)
{
$stmt = self::$db->prepareStatement("SELECT * FROM users WHERE id_user = ?");
$stmt->setInt(1, $_SESSION ['access']);
$result = self::getUserByQuery($stmt);
if ($result) {
$lasttime = $result->getInt('lasttime');
$time = time();
if ($time - $lasttime > self::LIFE_TIME) return null; // Вышло время сессии
$id = self::$id;
// self::$db->executeQuery("UPDATE users SET lasttime = $time WHERE id_user = $id"); // Время последнего обращения входа
}
return $result;
}
}

View file

@ -0,0 +1,6 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Input;
class BrowserInput extends Input {
}

15
src/Form/CheckBox.php Normal file
View file

@ -0,0 +1,15 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Field;
class CheckBox extends Field
{
/** @var bool */
public $checked = false;
function setValue($value): void
{
$this->value = $value;
$this->checked = $value;
}
}

View file

@ -3,6 +3,9 @@
/** /**
* Поле с цветом * Поле с цветом
*/ */
class Form_Color extends Form_Field namespace ctiso\Form;
use ctiso\Form\Field;
class Color extends Field
{ {
} }

View file

@ -2,5 +2,8 @@
/** /**
* Поле с датой * Поле с датой
*/ */
class Form_Date extends Form_Field { namespace ctiso\Form;
use ctiso\Form\Field;
class Date extends Field {
} }

7
src/Form/DateTime.php Normal file
View file

@ -0,0 +1,7 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Input;
class DateTime extends Input {
}

View file

@ -2,27 +2,53 @@
/** /**
* Элемент формы * Элемент формы
*/ */
class Form_Field namespace ctiso\Form;
use ctiso\Form\OptionsFactory;
class Field
{ {
/** @var bool */
public $hidden = false; public $hidden = false;
/** @var string */
public $name; public $name;
/** @var string */
public $label; // Метка поля public $label; // Метка поля
/** @var mixed */
public $value; // Значение поля public $value; // Значение поля
/** @var string */
public $type = ""; // Каждому типу элемента соответствует макрос TAL public $type = ""; // Каждому типу элемента соответствует макрос TAL
public $error_msg = null; /** @var ?string */
public $default = null; public $error_msg = null;
public $error = false; /** @var ?mixed */
public $default = null;
/** @var bool */
public $error = false;
/** @var bool */
public $require = false; public $require = false;
/** @var ?string */
public $hint = null; public $hint = null;
/** @var ?int */
public $maxlength = null;
/** @var ?string */
public $fieldset = null; public $fieldset = null;
// Блоки (Убрать в отдельный класс!!!) // Блоки (Убрать в отдельный класс!!!)
public $_title = array(); /** @var array */
public $_title = [];
/** @var string */
public $description = ""; public $description = "";
public $alias = array(); /** @var array */
public $alias = [];
public function __construct ($input = array(), $factory = null)
{
/**
* @param array $input
* @param OptionsFactory|null $factory
* @phpstan-ignore-next-line
*/
public function __construct ($input = [], $factory = null)
{
$this->default = null; $this->default = null;
if (isset($input['validate'])) { if (isset($input['validate'])) {
$this->require = strpos($input['validate'], 'require') !== false; $this->require = strpos($input['validate'], 'require') !== false;
@ -31,19 +57,22 @@ class Form_Field
$this->fieldset = $input['fieldset']; $this->fieldset = $input['fieldset'];
} }
// Инициализация свойст обьетка // Инициализация свойст обьетка
foreach (array('label', 'name', 'type', 'description') as $name) { foreach (['label', 'name', 'type', 'description', 'maxlength'] as $name) {
if (isset($input[$name])) { if (isset($input[$name])) {
$this->$name = $input[$name]; $this->$name = $input[$name];
} }
} }
} }
function setValue($value/*: any*/) /**
* @param mixed $value
*/
function setValue($value): void
{ {
$this->value = $value; $this->value = $value;
} }
function getId() function getId(): string
{ {
return $this->name . '_label'; return $this->name . '_label';
} }

View file

@ -4,119 +4,100 @@
* При рендеринге каждому классу соответствует шаблон (см. themes/maxim/templates/macros.html) * При рендеринге каждому классу соответствует шаблон (см. themes/maxim/templates/macros.html)
*/ */
class TCheckbox extends Form_Field namespace ctiso\Form;
{
public $checked = false;
function setValue($value)
{
$this->value = $value;
$this->checked = $value;
}
}
class TQuestionType extends Form_Select use ctiso\Form\Field;
{ use ctiso\Form\Select;
function setValue($value) use ctiso\Form\Input;
{ use ctiso\Validator\Validator;
// Установить selected у options use ctiso\HttpRequest;
$this->value = $value;
foreach ($this->options as &$option) {
$option['selected'] = ($option['value'] == $value);
}
}
}
class TDateTime extends Form_Input {
}
/**
* Поле для ввода пароля
*/
class TSecret extends Form_Field {
}
class TUpload extends Form_Field {
}
class THidden extends Form_Input {
public $hidden = true;
}
class TComponentBrowserInput extends Form_Input {
}
// вставка простого html
class Html_Text extends Form_Field {
}
/** /**
* Форма для ввода * Форма для ввода
*/ */
class Form_Form extends View_View { class Form {
public $field = array(); //Поля формы /** @var array<Field> */
public $fieldsets = array(); //Группы полей (fieldset). Некоторые поля могут не принадлежать никаким группам public $field = []; //Поля формы
/** @var array */
public $fieldsets = []; //Группы полей (fieldset). Некоторые поля могут не принадлежать никаким группам
/** @var string */
public $action = ""; public $action = "";
/** @var string */
public $method = 'post'; public $method = 'post';
/** @var string */
public $header; public $header;
/** @var array */
protected $replace; protected $replace;
/** @var array */
protected $before; protected $before;
public $_title = array(); /** @var array<string> */
public $alias = array(); public $_title = [];
public $constructor = array(); /** @var array */
public $alias = [];
/** @var class-string<Field>[] */
private $constructor = [];
/** /**
* Строим форму по ее структуре. Каждому типу соответствует определенный класс. * Строим форму по ее структуре. Каждому типу соответствует определенный класс.
*/ */
public function __construct() public function __construct()
{ {
$this->constructor = array( $this->constructor = [
'input' => 'Form_Input', 'input' => Input::class,
'inputreq' => 'Form_Input', // input с проверкой на заполненность // input с проверкой на заполненность
'inputreq' => Input::class,
'date' => Date::class,
'datereq' => Date::class,
'datetime' => DateTime::class,
'date' => 'Form_Date', 'color' => Color::class,
'datereq' => 'Form_Date', 'textarea' => TextArea::class,
'datetime' => 'TDateTime', 'text' => TextArea::class,
'multiselect' => SelectMany::class,
'select1' => SelectOne::class,
'select' => SelectOne::class,
'color' => 'Form_Color', 'questiontype'=> QuestionType::class,
'textarea' => 'Form_TextArea', 'secret' => Secret::class,
'text' => 'Form_TextArea', 'upload' => Upload::class,
'multiselect' => 'Form_SelectMany', 'image' => Upload::class,
// 'selectmany' => 'TSelectMany', 'checkbox' => CheckBox::class,
'select1' => 'Form_SelectOne', 'checkmany' => SelectMany::class,
'select' => 'Form_SelectOne', 'hidden' => Hidden::class,
'questiontype'=> 'TQuestionType', 'radio' => SelectOne::class,
'secret' => 'TSecret', 'filebrowser' => BrowserInput::class,
'upload' => 'TUpload', 'documents' => BrowserInput::class,
'image' => 'TUpload', 'chooser' => Input::class,
'checkbox' => 'TCheckbox', 'select_chooser' => SelectOne::class,
'checkmany' => 'Form_SelectMany', 'html_text' => HtmlText::class
'hidden' => 'THidden', ];
'radio' => 'Form_SelectOne',
'filebrowser' => 'TComponentBrowserInput',
'documents' => 'TComponentBrowserInput',
'chooser' => 'Form_Input',
'select_chooser' => 'Form_SelectOne',
'html_text' => 'Html_Text'
);
} }
function getId() function getId(): string
{ {
return '_form_edit'; return '_form_edit';
} }
public function addFieldClass($name, $class) /**
* Добавление конструкторя для поля формы
* @param string $name Краткое название поля
* @param class-string<Field> $class
*/
public function addFieldClass($name, $class): void
{ {
$this->constructor [$name] = $class; $this->constructor [$name] = $class;
} }
/** /**
* Добавляет одно поле ввода на форму * Добавляет одно поле ввода на форму
* @param array{ type: string, name: string, hint?: string } $init
* @param OptionsFactory|null $factory
*/ */
public function addField(array $init, $factory = null) public function addField(array $init, $factory = null): Field
{ {
assert(isset($init['type'])); assert(isset($init['type']));
assert(isset($init['name'])); assert(isset($init['name']));
@ -130,16 +111,15 @@ class Form_Form extends View_View {
if(isset($init['hint'])) { if(isset($init['hint'])) {
$el->hint = $init['hint']; $el->hint = $init['hint'];
} }
$this->field [$init['name']] = $el; $this->field[$init['name']] = $el;
return $el; return $el;
} }
/** /**
* Добавление fieldset на форму * Добавление fieldset на форму
*/ */
public function addFieldSet(array $fieldset): void
public function addFieldSet(array $fieldset)
{ {
$this->fieldsets[$fieldset['name']] = $fieldset; $this->fieldsets[$fieldset['name']] = $fieldset;
} }
@ -147,8 +127,7 @@ class Form_Form extends View_View {
/** /**
* Добавление массива fieldset на форму * Добавление массива fieldset на форму
*/ */
public function addFieldSetList(array $list): void
public function addFieldSetList(array $list)
{ {
foreach ($list as $fieldset) { foreach ($list as $fieldset) {
$this->addFieldSet($fieldset); $this->addFieldSet($fieldset);
@ -157,9 +136,10 @@ class Form_Form extends View_View {
/** /**
* Добавляет список полей для формы * Добавляет список полей для формы
* @param array $list * @param array $list
* @param OptionsFactory|null $factory
*/ */
public function addFieldList(array $list, $factory = null) public function addFieldList(array $list, $factory = null): void
{ {
foreach ($list as $init) { foreach ($list as $init) {
$this->addField($init, $factory); $this->addField($init, $factory);
@ -169,7 +149,7 @@ class Form_Form extends View_View {
/** /**
* Устанавливает ошибки после проверки * Устанавливает ошибки после проверки
*/ */
function setError(Validator_Validator $validator) function setError(Validator $validator): void
{ {
foreach ($validator->getErrorMsg() as $name => $error) foreach ($validator->getErrorMsg() as $name => $error)
{ {
@ -178,7 +158,12 @@ class Form_Form extends View_View {
} }
} }
function setFieldError($name, $message) /**
* Устанавливает ошибку для поля
* @param string $name
* @param string $message
*/
function setFieldError($name, $message): void
{ {
$this->field[$name]->error = true; $this->field[$name]->error = true;
$this->field[$name]->error_msg = $message; $this->field[$name]->error_msg = $message;
@ -186,33 +171,41 @@ class Form_Form extends View_View {
/** /**
* Устанавливает значения из масива * Устанавливает значения из масива
*/ */
function setValues(HttpRequest $request) { function setValues(HttpRequest $request): void {
foreach ($this->field as $key => $el) { foreach ($this->field as $key => $_) {
$value = $request->getRawData($this->method, $key); $value = $request->getRawData($this->method, $key);
$this->field[$key]->setValue($value); $this->field[$key]->setValue($value);
} }
} }
/** /**
* Заполняет форму данными из обьекта * Заполняет форму данными из обьекта
* @param object $data * @param object $data
* @param array $schema Связь между элементами формы и свойствами обьекта * @param array $schema Связь между элементами формы и свойствами обьекта
*/ */
public function fill($data, array $schema) public function fill($data, array $schema): void
{ {
foreach ($schema as $key => $conv) { foreach ($schema as $key => $conv) {
list($value, $type) = $conv; list($value, $type) = $conv;
$this->field [$key]->setValue(call_user_func(array('Primitive', 'from_' . $type), $data->$value)); $convertFn = [\ctiso\Primitive::class, 'from_' . $type];
if (!is_callable($convertFn)) {
throw new \Exception('Не найден метод преобразования ' . $type);
}
$this->field[$key]->setValue(call_user_func($convertFn, $data->$value));
} }
} }
public function set($name, $value) /**
* @param string $name
* @param mixed $value
*/
public function set($name, $value): void
{ {
$this->field[$name]->setValue($value); $this->field[$name]->setValue($value);
} }
function execute() function execute(): self
{ {
return $this; return $this;
} }

9
src/Form/Hidden.php Normal file
View file

@ -0,0 +1,9 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Input;
class Hidden extends Input {
/** @var bool */
public $hidden = true;
}

8
src/Form/HtmlText.php Normal file
View file

@ -0,0 +1,8 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Field;
// вставка простого html
class HtmlText extends Field {
}

View file

@ -3,5 +3,8 @@
/** /**
* Поле ввода Input * Поле ввода Input
*/ */
class Form_Input extends Form_Field { namespace ctiso\Form;
use ctiso\Form\Field;
class Input extends Field {
} }

View file

@ -1,87 +0,0 @@
<?php
class Form_OptionFactory {
public $db;
public $registry;
function __construct($db, $registry = null) {
$this->db = $db;
$this->registry = $registry;
}
function create(Form_Select $field, $input) {
if (isset($input['options.resid'])) {
$type = $input['options.resid'];
$res = new Model_Resources($this->db);
$field->options = $this->optionsArray('id_section', 'title', $res->getSubsections('', $type));
} else if (isset($input['options.res'])) {
$type = $input['options.res'];
$res = new Model_Resources($this->db);
$field->options = $this->optionsArray('path', 'title', $res->getSubsections('', $type));
} else if (isset($input['options.all_res'])) {
$type = $input['options.all_res'];
$res = new Model_Resources($this->db);
$field->options = $this->optionsArray('id_resource', 'subtitle', $res->getAllResource($type));
} else if (isset($input['options.db'])) {
list($table, $keyvalue) = explode(":", $input['options.db']);
list($key, $value) = explode(",", $keyvalue);
try {
$query_result = $this->db->executeQuery("SELECT * FROM $table");
$field->options = $this->optionsDB($key, $value, $query_result);
} catch(Exception $ex) {
$field->options = [];
}
} elseif (isset($input['options.pair'])) {
$field->options = $this->optionsPair($input['options.pair']);
} elseif (isset($input['options.model'])) {
$factory = new Model_Factory($this->db, $this->registry);
$model = $factory->getModel($input['options.model']);
$field->options = $model->getAllAsOptions();
} else {
$field->options = $input['options'];
}
if (isset($input['default'])) {
array_unshift($field->options, ['value' => 0, 'name' => $input['default']]);
}
// Ставим корневой каталог в начало списка (скорее всего он будет в конце массива)
if ($field->options)
{
$root_elem = array_pop($field->options);
if ($root_elem['value'] == '/')
array_unshift($field->options, $root_elem);
else
array_push($field->options, $root_elem);
}
}
public function optionsDB($key, $val, $res) {
$result = array();
while($res->next()) {
$result[] = array('value' => $res->getInt($key), 'name' => $res->getString($val));
}
return $result;
}
public function optionsArray($key, $val, $res) {
$result = array();
foreach($res as $item) {
$result[] = array('value' => $item->{$key}, 'name' => $item->{$val});
}
return $result;
}
public function optionsPair($list, $selected = false) {
$result = array();
foreach ($list as $key => $value) {
$result [] = array('value' => $key, 'name' => $value, 'selected' => $key == $selected);
}
return $result;
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace ctiso\Form;
interface OptionsFactory {
function create(Select $field, array $options): void;
}

16
src/Form/QuestionType.php Normal file
View file

@ -0,0 +1,16 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Select;
class QuestionType extends Select
{
function setValue($value): void
{
// Установить selected у options
$this->value = $value;
foreach ($this->options as &$option) {
$option['selected'] = ($option['value'] == $value);
}
}
}

10
src/Form/Secret.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Field;
/**
* Поле для ввода пароля
*/
class Secret extends Field {
}

View file

@ -1,11 +1,22 @@
<?php <?php
class Form_Select extends Form_Field namespace ctiso\Form;
{ use ctiso\Form\Field;
public $options = array();
/**
* @phpstan-type Option = array{value: string, name: string, selected?: bool, class?: string|false}
*/
class Select extends Field
{
/** @var Option[] */
public $options = [];
/**
* @param array{ options?: Option[], 'options.pair'?: array } $input
* @param OptionsFactory $factory
*/
public function __construct ($input, $factory) { public function __construct ($input, $factory) {
parent::__construct($input, $factory); parent::__construct($input);
if ($factory != null) { if ($factory != null) {
$factory->create($this, $input); $factory->create($this, $input);
@ -21,10 +32,15 @@ class Form_Select extends Form_Field
} }
} }
/**
* @param string[] $list
* @param bool $selected
* @return Option[]
*/
public function optionsPair($list, $selected = false) { public function optionsPair($list, $selected = false) {
$result = array(); $result = [];
foreach ($list as $key => $value) { foreach ($list as $key => $value) {
$result [] = array('value' => $key, 'name' => $value, 'selected' => $key == $selected); $result [] = ['value' => $key, 'name' => $value, 'selected' => $key == $selected];
} }
return $result; return $result;
} }

View file

@ -1,11 +1,14 @@
<?php <?php
class Form_SelectMany extends Form_Select namespace ctiso\Form;
use ctiso\Form\Select;
class SelectMany extends Select
{ {
function setValue($value) function setValue(mixed $value): void
{ {
// Установить selected у options // Установить selected у options
if (!is_array($value)) { $value = array($value); } if (!is_array($value)) { $value = [$value]; }
$this->value = $value; $this->value = $value;
foreach ($this->options as &$option) { foreach ($this->options as &$option) {
$option['selected'] = (in_array($option['value'], $value)); $option['selected'] = (in_array($option['value'], $value));

View file

@ -3,9 +3,12 @@
/** /**
* Выбор из одного элемента * Выбор из одного элемента
*/ */
class Form_SelectOne extends Form_Select namespace ctiso\Form;
use ctiso\Form\Select;
class SelectOne extends Select
{ {
function setValue($value) function setValue($value): void
{ {
// Установить selected у options // Установить selected у options
$this->value = $value; $this->value = $value;

View file

@ -1,10 +1,13 @@
<?php <?php
/** /**
* Текстовое поле * Текстовое поле
*/ */
class Form_TextArea extends Form_Field { namespace ctiso\Form;
function setValue($value) use ctiso\Form\Field;
class TextArea extends Field {
function setValue($value): void
{ {
$this->value = $value; $this->value = $value;
} }

7
src/Form/Upload.php Normal file
View file

@ -0,0 +1,7 @@
<?php
namespace ctiso\Form;
use ctiso\Form\Field;
class Upload extends Field {
}

View file

@ -1,21 +1,33 @@
<?php <?php
/** /**
* http://www.alternateinterior.com/2006/09/a-viewstate-for-php.html * http://www.alternateinterior.com/2006/09/a-viewstate-for-php.html
* Управление состоянием между страницами * Управление состоянием между страницами
*/ */
class Form_ViewState // extends Collection namespace ctiso\Form;
{
private $values = array();
function set($name, $value) class ViewState // extends Collection
{
/** @var array */
private $values = [];
/**
* Устанавливает значение
* @param string $name
* @param mixed $value
*/
function set($name, $value): void
{ {
$this->values[$name] = $value; $this->values[$name] = $value;
} }
function get($_rest) /**
* Возвращает значение
* @param string ...$args
* @return mixed
*/
function get(...$args)
{ {
$args = func_get_args();
$result = $this->values; $result = $this->values;
foreach ($args as $name) { foreach ($args as $name) {
if (!isset($result[$name])) { if (!isset($result[$name])) {
@ -26,16 +38,28 @@ class Form_ViewState // extends Collection
return $result; return $result;
} }
function saveState() /**
* Сохраняет состояние
* @return string
*/
function saveState(): string
{ {
return base64_encode(serialize($this->values)); return base64_encode(serialize($this->values));
} }
function restoreState($value) /**
* Восстанавливает состояние
* @param string $value
*/
function restoreState($value): void
{ {
$this->values = unserialize(base64_decode($value)); $this->values = unserialize(base64_decode($value));
} }
/**
* Возвращает состояние
* @return array
*/
function export() function export()
{ {
return $this->values; return $this->values;

View file

@ -1,59 +1,84 @@
<?php <?php
/** namespace ctiso;
* Функциональное программирование в PHP
* package functional
*/
/** /**
* Эмуляция каррированой функции * Эмуляция каррированой функции
*/ */
class __right { class right {
protected $params; /** @var array<mixed> */
protected $params;
/** @var callable */
protected $fn; protected $fn;
/**
* @param array $params
*/
public function __construct($params) { public function __construct($params) {
$this->fn = array_shift($params); $this->fn = array_shift($params);
$this->params = $params; $this->params = $params;
} }
function apply() { /**
$params = func_get_args(); * Применение функции
* @param mixed ...$params
* @return mixed
*/
function apply(...$params) {
array_splice($params, count($params), 0, $this->params); array_splice($params, count($params), 0, $this->params);
return call_user_func_array($this->fn, $params); return call_user_func_array($this->fn, $params);
} }
} }
class __left { class left {
protected $params; /** @var array<mixed> */
protected $params;
/** @var callable */
protected $fn; protected $fn;
/**
* @param array $params
*/
public function __construct($params) { public function __construct($params) {
$this->fn = array_shift($params); $this->fn = array_shift($params);
$this->params = $params; $this->params = $params;
} }
function apply() { /**
$params = func_get_args(); * Применение функции
* @param mixed ...$params
* @return mixed
*/
function apply(...$params) {
array_splice ($params, 0, 0, $this->params); array_splice ($params, 0, 0, $this->params);
return call_user_func_array ($this->fn, $params); return call_user_func_array ($this->fn, $params);
} }
} }
define('__', '_ARGUMENT_PLACE_'); define('__', '_ARGUMENT_PLACE_');
class __partial { class partial {
protected $params; /** @var array<mixed> */
protected $params;
/** @var callable */
protected $fn; protected $fn;
/**
* @param array $params
*/
public function __construct($params) { public function __construct($params) {
$this->fn = array_shift($params); $this->fn = array_shift($params);
$this->params = $params; $this->params = $params;
} }
function apply() { /**
$params = func_get_args(); * Применение функции
$result = array(); * @param mixed ...$params
for($i = 0, $j = 0; $i < count($this->params); $i++) { * @return mixed
*/
function apply(...$params) {
$result = [];
$count = count($this->params);
for($i = 0, $j = 0; $i < $count; $i++) {
if ($this->params[$i] == __) { if ($this->params[$i] == __) {
$result [] = $params[$j]; $result [] = $params[$j];
$j++; $j++;
@ -63,21 +88,31 @@ class __partial {
} }
return call_user_func_array ($this->fn, $result); return call_user_func_array ($this->fn, $result);
} }
} }
/** /**
* Композиция функций * Композиция функций
*/ */
class __compose { class compose {
/** @var array<callable> */
protected $fns; protected $fns;
/**
* @param array<callable> $list
*/
function __construct($list) { function __construct($list) {
$this->fns = array_reverse($list); $this->fns = array_reverse($list);
} }
function apply () { /**
$params = func_get_args (); * Применение функций
* @param mixed ...$params
* @return mixed
*/
function apply (...$params) {
$result = call_user_func_array($this->fns[0], $params); $result = call_user_func_array($this->fns[0], $params);
for ($i = 1; $i < count($this->fns); $i++) { $count = count($this->fns);
for ($i = 1; $i < $count; $i++) {
$result = call_user_func($this->fns[$i], $result); $result = call_user_func($this->fns[$i], $result);
} }
return $result; return $result;
@ -86,54 +121,57 @@ class __compose {
class Functions { class Functions {
static function partial() { /**
$closure = new __partial(func_get_args()); * Частичное применение функции
return array($closure, 'apply'); * @param mixed ...$args
* @return mixed
*/
static function partial(...$args) {
$closure = new partial($args);
return [$closure, 'apply'];
} }
/** /**
* Композиция функций * Композиция функций
* @param mixed $a * @param mixed ...$args
* @param mixed $b * @return mixed
*
* @return array[int]mixed
*/ */
static function compose() { static function compose(...$args) {
$closure = new __compose(func_get_args()); $closure = new compose($args);
return array($closure, 'apply'); return [$closure, 'apply'];
} }
/** /**
* Карирование справа * Карирование справа
* * @param mixed ...$args
* @return array[int]mixed * @return mixed
*/ */
static function rcurry($_rest) { static function rcurry(...$args) {
$closure = new __right(func_get_args ()); $closure = new right($args);
return array($closure, 'apply'); return [$closure, 'apply'];
} }
/** /**
* Карирование слева * Карирование слева
* * @param mixed ...$args
* @return array[int]mixed * @return mixed
*/ */
static function lcurry($_rest) { static function lcurry(...$args) {
$closure = new __left(func_get_args ()); $closure = new left($args);
return array($closure, 'apply'); return [$closure, 'apply'];
} }
/** /**
* Разделение массива на два по условию * Разделение массива на два по условию
* @param mixed $pred Условие по которому разделяется массив * @param mixed $pred Условие по которому разделяется массив
* @param array $lst * @param array $lst
* *
* @return array[int]mixed * @return mixed
*/ */
static function partition($pred, $lst) { static function partition($pred, $lst) {
$left = array (); $left = [];
$right = array (); $right = [];
foreach ($lst as $n) { foreach ($lst as $n) {
if (call_user_func($pred, $n) !== false) { if (call_user_func($pred, $n) !== false) {
$left [] = $n; $left [] = $n;
@ -141,29 +179,37 @@ class Functions {
$right [] = $n; $right [] = $n;
} }
} }
return array ($left, $right); return [$left, $right];
} }
/** /**
* @param array $value * @deprecated
* @param string $name * @param array<string, mixed> $value
* @param string $name
* *
* @return mixed * @return mixed
*/ */
static function __key($value, $name) { static function __key($value, $name) {
return $value[$name]; return $value[$name];
} }
/**
* @deprecated
* @param mixed $value
*
* @return mixed
*/
static function identity($value) { static function identity($value) {
return $value; return $value;
} }
/** /**
* @deprecated use fn and <=> operator
* @param array $a * @param array $a
* @param array $b * @param array $b
* @param $key * @param string|int $key
* *
* @return int * @return int
*/ */
static function __cmp($a, $b, $key) { static function __cmp($a, $b, $key) {
if ($a[$key] == $b[$key]) { if ($a[$key] == $b[$key]) {
@ -171,108 +217,133 @@ class Functions {
} }
return ($a[$key] > $b[$key]) ? -1 : 1; return ($a[$key] > $b[$key]) ? -1 : 1;
} }
/**
* @deprecated use fn and <=> operator
* @param array $a
* @param array $b
* @param string|int $key
*
* @return int
*/
static function __cmp_less($a, $b, $key) { static function __cmp_less($a, $b, $key) {
if ($a[$key] == $b[$key]) { if ($a[$key] == $b[$key]) {
return 0; return 0;
} }
return ($a[$key] < $b[$key]) ? -1 : 1; return ($a[$key] < $b[$key]) ? -1 : 1;
} }
// Сравнение по ключу массиве /**
static function __index($n, $key, $row) { * @param string ...$args
return ($row[$key] == $n); * @return string
} */
static function concat(...$args) {
static function __div($x, $y) {
return $x / $y;
}
static function __self($name, $o) {
return call_user_func(array($o, $name));
}
static function concat(/* $args ...*/) {
$args = func_get_args();
return implode("", $args); return implode("", $args);
} }
/**
* @param mixed $x
* @return bool
*/
static function __empty($x) { static function __empty($x) {
return empty($x); return empty($x);
} }
// Отрицание
static function __not($x) {
return !$x;
}
// Не равно
static function __neq($x, $y) {
return $x != $y;
}
// Равно
static function __eq($x, $y) {
return $x == $y;
}
/** /**
* Извлекает из многомерого массива значения с определенным ключом * Извлекает из многомерого массива значения с определенным ключом
* @example key_values('a', array(1 => array('a' => 1, 'b' => 2))) => array(1) * @example key_values('a', array(1 => array('a' => 1, 'b' => 2))) => array(1)
* *
* @param string $key
* @param list<array>|\ArrayIterator<int, array> $array
* @return mixed * @return mixed
*/ */
static function key_values($key, /*array|ArrayIterator*/ $array) { static function key_values($key, $array) {
$result = array(); $result = [];
foreach($array as $item) { foreach($array as $item) {
$result[] = $item[$key]; $result[] = $item[$key];
} }
return $result; return $result;
} }
static function key_values_object($key, /*array|ArrayIterator*/ $array) { /**
$result = array(); * @param string $key
* @param array<object>|\ArrayIterator<int, object> $array
* @return array<mixed>
*/
static function key_values_object($key, $array) {
$result = [];
foreach($array as $item) { foreach($array as $item) {
$result[] = $item->{$key}; $result[] = $item->{$key};
} }
return $result; return $result;
} }
/**
* @param string $key
* @param string $value
* @param array<array<string, mixed>>|\ArrayIterator<int, array> $array
* @return array<string, mixed>
*/
static function assoc_key_values($key, $value, $array) { static function assoc_key_values($key, $value, $array) {
$result = array(); $result = [];
foreach ($array as $item) { foreach ($array as $item) {
$result[$item[$key]] = $item[$value]; $result[$item[$key]] = $item[$value];
} }
return $result; return $result;
} }
/**
* @param string $key
* @param array<array<string, mixed>>|\ArrayIterator<int, array> $array
* @return array<string, mixed>
*/
static function assoc_key($key, $array) { static function assoc_key($key, $array) {
$result = array(); $result = [];
foreach ($array as $item) { foreach ($array as $item) {
$result[$item[$key]] = $item; $result[$item[$key]] = $item;
} }
return $result; return $result;
} }
static function _get($key, $value/*: any*/, $array/*: array*/) { /**
* Возвращает значение по ключу
* @param string $key
* @param mixed $value
* @param array $array
* @return mixed
*/
static function _get($key, $value, $array) {
foreach ($array as $item) { foreach ($array as $item) {
if ($item[$key] == $value) return $item; if ($item[$key] == $value) {
return $item;
}
} }
return null; return null;
} }
/**
* Возвращает ключ по значению
* @param string $key
* @param mixed $value
* @param array $array
* @return mixed
*/
static function _get_key($key, $value, $array) { static function _get_key($key, $value, $array) {
foreach ($array as $name => $item) { foreach ($array as $name => $item) {
if ($item[$key] == $value) return $name; if ($item[$key] == $value) {
return $name;
}
} }
return null; return null;
} }
/** /**
* Логическа операция && ко всем элементам массива * Логическа операция && ко всем элементам массива
* @param array<mixed> $array Массив
* @param callable $callback Функция
* @return bool * @return bool
*/ */
static function every(array $array, $callback) { static function every(array $array, $callback) {
@ -283,17 +354,14 @@ class Functions {
} }
return true; return true;
} }
/** /**
* Логическа операция || ко всем элементам массива * Логическа операция || ко всем элементам массива
* @param array $array * @param array<mixed> $array Массив
* @param mixed $callback * @param callable $callback Функция
*
* @return mixed * @return mixed
*/ */
static function some(array $array, $callback) { static function some(array $array, callable $callback) {
assert(is_callable($callback));
foreach ($array as $key => $value) { foreach ($array as $key => $value) {
if (call_user_func($callback, $value) === true) { if (call_user_func($callback, $value) === true) {
return $key; return $key;
@ -301,80 +369,107 @@ class Functions {
} }
return false; return false;
} }
static function span($length, array $array) { /**
assert(is_int($length)); * Разбивает массив на массивы определенной длины
* @template T
$result = array(); * @param int $length Длина массива
for($i = 0; $i < count($array); $i += $length) { * @param T[] $array Массив
* @return T[][]
*/
static function span(int $length, array $array) {
$result = [];
$count = count($array);
for($i = 0; $i < $count; $i += $length) {
$result [] = array_slice($array, $i, $length); $result [] = array_slice($array, $i, $length);
} }
return $result; return $result;
} }
static function array_ref($data, $n) { /**
* Возвращает значение массива
* @param array $data Массив
* @param string|int $n Ключ
* @return mixed
*/
static function array_ref(array $data, string|int $n) {
return $data[$n]; return $data[$n];
} }
static function call() { /**
$args = func_get_args(); * Вызывает функцию с аргументами
* @param mixed ...$args Аргументы
* @return mixed
*/
static function call(...$args) {
$name = array_shift($args); $name = array_shift($args);
return call_user_func_array($name, $args); return call_user_func_array($name, $args);
} }
/** /**
* Поиск элемента в массиве * Поиск элемента в массиве
* @param function $cb сравнение с элементом массива * @param callable $cb сравнение с элементом массива
* @param array $hs массив в котором ищется значение * @param array $hs массив в котором ищется значение
* @param bool $strict
* *
* @return int|string ключ найденого элемента в массиве * @return int|string|null ключ найденого элемента в массиве
*/ */
static function array_usearch($cb, array $hs, $strict = false) { static function array_usearch($cb, array $hs, $strict = false) {
foreach($hs as $key => $value) if (call_user_func_array($cb, array($value, $key, $strict))) return $key; foreach($hs as $key => $value) {
} if (call_user_func_array($cb, [$value, $key, $strict])) {
return $key;
}
}
return null;
}
/** /**
* Выбирает все сроки из таблицы с уникальными значениями ключа * Выбирает все сроки из таблицы с уникальными значениями ключа
* @param $name Имя ключа
* @param $table Двухмерный массив
* @example * @example
* key_unique_values ('name', array (array ('name' => 1), array ('name' => 2), array ('name' => 1))) * key_unique_values ('name', array (array ('name' => 1), array ('name' => 2), array ('name' => 1)))
=> array (1, 2) * => array (1, 2)
* @end example *
* @param string $name Имя ключа
* @param array $table Двухмерный массив
* @return array Массив с уникальными значениями ключа
*/ */
static function key_unique_values ($name, $table) { static function key_unique_values ($name, $table) {
// Ищем уникальные значения для заданного ключа // Ищем уникальные значения для заданного ключа
$keys = array (); $keys = [];
foreach ($table as $row) { foreach ($table as $row) {
if (!in_array ($row[$name], $keys)) if (!in_array ($row[$name], $keys)) {
$keys[] = $row[$name]; $keys[] = $row[$name];
}
} }
return $keys; return $keys;
} }
/** /**
* Сортировка двумерного массива по заданному ключу * Сортировка двумерного массива по заданному ключу
* @param $array Массив * @param array $array Массив
* @param $key Имя ключа по значению которого будет идти сравнение * @param string $key Имя ключа по значению которого будет идти сравнение
* @return Отсортированный массив * @param callable $fn Функция сравнения
* @return array Отсортированный массив
*/ */
static function sortOn($array, $key, $fn = 'Functions::__cmp') { static function sortOn($array, $key, $fn = '\\ctiso\\Functions::__cmp') {
usort ($array, Functions::rcurry($fn, $key)); usort ($array, Functions::rcurry($fn, $key));
//usort ($array, create_function ('$x,$y', 'return __cmp ($x, $y, "'.$key.'");')); //usort ($array, create_function ('$x,$y', 'return __cmp ($x, $y, "'.$key.'");'));
return $array; return $array;
} }
/** /**
* Преобразует ключи элементов для многомерного массива * Преобразует ключи элементов для многомерного массива
* @param string $key_name Имя ключа
* @param array $array Многомерный массив
* @return mixed * @return mixed
*/ */
static function hash_key ($key_name,$array/*: array*/) { static function hash_key ($key_name, $array) {
$result = array(); $result = [];
foreach($array as $value) { foreach($array as $value) {
$result[$value[$key_name]] = $value; $result[$value[$key_name]] = $value;
} }
return $result; return $result;
} }
} }

View file

@ -1,17 +1,22 @@
<?php <?php
/** /**
* Неверный запрос * Неверный запрос
*/ */
class WrongRequestException extends Exception namespace ctiso;
{
}
// HTTPRequest = ArrayAccess
class HttpRequest extends Collection implements ArrayAccess
{
use ctiso\Collection;
use ctiso\Session;
/**
* @template T=mixed
*/
class HttpRequest extends Collection
{
/** @var Session */
public $_session; public $_session;
/** /**
* Constructor * Constructor
* Stores "request data" in GPC order. * Stores "request data" in GPC order.
@ -19,9 +24,9 @@ class HttpRequest extends Collection implements ArrayAccess
public function __construct() public function __construct()
{ {
$list = [ $list = [
'data' => $_REQUEST, 'data' => $_REQUEST,
'get' => $_GET, 'get' => $_GET,
'post' => $_POST, 'post' => $_POST,
'cookie' => $_COOKIE 'cookie' => $_COOKIE
]; ];
@ -37,37 +42,77 @@ class HttpRequest extends Collection implements ArrayAccess
parent::set('files', $data); parent::set('files', $data);
} }
/**
* @param string $key
* @return Collection
*/
function _get($key) function _get($key)
{ {
return parent::get($key); return parent::get($key);
} }
/**
* @param string $key
* @param mixed $default
* @return null|string|array
*/
function get($key, $default = null) function get($key, $default = null)
{ {
return parent::get('data')->get($key, $default); return parent::get('data')->get($key, $default);
} }
function session(Session $value = null) function getString(string $key, string $default = ''): string
{
return parent::get('data')->getString($key, $default);
}
function getInt(string $key, int $default = 0): int
{
return parent::get('data')->getInt($key, $default);
}
function getNat(string $key, int $default = 1): int
{
return parent::get('data')->getNat($key, $default);
}
function getBool(string $key, bool $default = false): bool
{
return parent::get('data')->getBool($key, $default);
}
function getArray(string $key, array $default = []): array
{
return parent::get('data')->getArray($key, $default);
}
function session(?Session $value = null): ?Session
{ {
if ($value) { if ($value) {
$this->_session = $value; $this->_session = $value;
} }
return $this->_session; return $this->_session;
} }
function set($key, $value/*: any*/) function set(string $key, mixed $value): void
{ {
return parent::get('data')->set($key, $value); parent::get('data')->set($key, $value);
} }
function export($key = 'data') /**
* @param string $key
* @return array<mixed>
*/
function export(string $key = 'data')
{ {
return parent::get($key)->export(); return parent::get($key)->export();
} }
function clear() function clear(): void
{ {
return parent::get('data')->clear(); /** @var Collection */
$collection = parent::get('data');
$collection->clear();
} }
/** /**
@ -77,7 +122,7 @@ class HttpRequest extends Collection implements ArrayAccess
* @param string $key * @param string $key
* @return mixed * @return mixed
*/ */
public function getRawData($var, $key) public function getRawData(string $var, $key)
{ {
$data = parent::get(strtolower($var)); $data = parent::get(strtolower($var));
if ($data) { if ($data) {
@ -86,6 +131,12 @@ class HttpRequest extends Collection implements ArrayAccess
return null; return null;
} }
/**
* @param string $var
* @param string $key
* @param mixed $val
* @return mixed
*/
public function setRawData($var, $key, $val) public function setRawData($var, $key, $val)
{ {
$data = parent::get(strtolower($var)); $data = parent::get(strtolower($var));
@ -94,37 +145,34 @@ class HttpRequest extends Collection implements ArrayAccess
} }
} }
public function setAction($name) /**
{ * @param string $name
*/
public function setAction($name): void
{
$this->setRawData('get', 'action', $name); $this->setRawData('get', 'action', $name);
} }
public function getAction() public function getAction(): string
{ {
$result = $this->getRawData('get', 'action'); $result = $this->getRawData('get', 'action');
return ($result) ? $result : 'index'; return ($result) ? $result : 'index';
} }
public function isAjax() public function isAjax(): bool
{ {
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'); return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');
} }
public function redirect($url) { /**
* @param string $url
*/
public function redirect($url): void {
header('location: ' . $url); header('location: ' . $url);
exit(); exit();
} }
static function getProtocol(): string {
public function offsetSet($key, $value) { return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
}
public function offsetExists($key) {
}
public function offsetUnset($key) {
}
public function offsetGet($key) {
} }
} }

View file

@ -3,8 +3,15 @@
/** /**
* Самый простой макет * Самый простой макет
*/ */
class Layout_Empty extends Filter_Filter namespace ctiso\Layout;
use ctiso\Filter\Filter,
ctiso\HttpRequest;
class Blank extends Filter
{ {
/**
* @return mixed
*/
function execute(HttpRequest $request) function execute(HttpRequest $request)
{ {
$text = $this->processor->execute($request); $text = $this->processor->execute($request);

View file

@ -4,32 +4,44 @@
* Выбор макета страницы. * Выбор макета страницы.
* Выбор оформления страницы осуществляется если было совпадение с каким либо условием * Выбор оформления страницы осуществляется если было совпадение с каким либо условием
*/ */
class Layout_Manager extends Filter_Filter namespace ctiso\Layout;
use ctiso\Filter\Filter;
use ctiso\HttpRequest;
class Manager extends Filter
{ {
// Массив условий с их макетами /**
protected $condition = array(); * Массив условий с их макетами
* @var list<array{callable, Filter}>
*/
protected $condition = [];
/** /**
* Функция которая добавляет условие для проверки параметров $_GET * Функция которая добавляет условие для проверки параметров $_GET
* @param $get array() | true Ассоциативный массив ключей и значений для $_GET * @param array|true $get Ассоциативный массив ключей и значений для $_GET
* * @param Filter $layout Макет
* @example
* addConditionGet(array('module' => 'personal'), 'personal')
* addConditionGet(array('module' => 'login'), 'login')
*/ */
public function addConditionGet($get, Filter_Filter $layout) public function addConditionGet($get, Filter $layout): void
{ {
$this->addCondition(Functions::rcurry(array($this, 'checkGet'), $get), $layout); $this->addCondition(fn(HttpRequest $request) => $this->checkGet($request, $get), $layout);
} }
/** /**
* Условие для аякс запросов. Тоже самое что и addConditionGet но еще проверяется является ли запрос ajax * Условие для аякс запросов. Тоже самое что и addConditionGet но еще проверяется является ли запрос ajax
* @param array|true $get Ассоциативный массив ключей и значений для $_GET
* @param Filter $layout Макет
*/ */
public function addConditionXHR($get, Filter_Filter $layout) public function addConditionXHR($get, Filter $layout): void
{ {
$this->addCondition(Functions::rcurry(array($this, 'checkXHR'), $get), $layout); $this->addCondition(fn(HttpRequest $request) => $this->checkXHR($request, $get), $layout);
} }
/**
* @param HttpRequest $request
* @param array|true $get
* @return bool
*/
public function checkGet($request, $get) public function checkGet($request, $get)
{ {
if (is_array($get)) { if (is_array($get)) {
@ -42,38 +54,44 @@ class Layout_Manager extends Filter_Filter
return true; return true;
} }
public function checkXHR($request, $get) /**
* @param HttpRequest $request
* @param array|true $get
* @return bool
*/
public function checkXHR($request, $get): bool
{ {
return $request->isAjax() && $this->checkGet($request, $get); return $request->isAjax() && $this->checkGet($request, $get);
} }
/** /**
* Добавляет условие в общем виде * Добавляет условие в общем виде
* @parma $get function(HttpRequest) Функция * @param callable $get Функция
* @parma $layout Layout Макет * @param Filter $layout Макет
*/ */
public function addCondition($get, Filter_Filter $layout) public function addCondition($get, Filter $layout): void
{ {
$this->condition [] = array($get, $layout); $this->condition [] = [$get, $layout];
} }
/** /**
* Выбирает и применяет макет для страницы * Выбирает и применяет макет для страницы
* @return mixed
*/ */
public function execute(HttpRequest $request) public function execute(HttpRequest $request): mixed
{ {
foreach ($this->condition as $condition) { foreach ($this->condition as $condition) {
if (call_user_func($condition[0], $request)) { if (call_user_func($condition[0], $request)) {
$layout = $condition[1]; $layout = $condition[1];
$view = $layout->execute($request); $view = $layout->execute($request);
if (is_object($view)) { if (is_object($view)) {
echo $view->render(); return $view->execute();
} else { } else {
echo $view; return $view;
} }
return null;
} }
} }
return '';
} }
} }

View file

@ -4,55 +4,75 @@
* Класс для работы с почтой * Класс для работы с почтой
* http://en.wikipedia.org/wiki/MIME * http://en.wikipedia.org/wiki/MIME
*/ */
namespace ctiso;
use ctiso\Path,
Exception;
class Mail class Mail
{ {
/** @var string */
public $_from; public $_from;
/** @var string */
public $_to; public $_to;
/** @var string */
public $_subject; public $_subject;
/** @var string */
public $content; public $content;
/** @var string */
public $copy; public $copy;
private $encoding; /** @var string */
private $encoding;
/** @var string */
private $_notify = null; private $_notify = null;
protected $attachment = array (); /** @var array */
protected $attachment = array();
/** @var string */
protected $uniqid; protected $uniqid;
/** @var string */
protected $type = "text/plain"; protected $type = "text/plain";
function __construct() { function __construct()
{
$this->setEncoding("UTF-8"); $this->setEncoding("UTF-8");
$this->uniqid = strtoupper(uniqid(time())); // Идентефикатор разделителя $this->uniqid = strtoupper(uniqid((string)time())); // Идентефикатор разделителя
} }
/** /**
* Установка отправителя * Установка отправителя
*/ */
function from($name) function from(string $name): void
{ {
// filter_var($name, FILTER_VALIDATE_EMAIL);
$this->_from = $name; $this->_from = $name;
} }
/** /**
* Установка получателя * Установка получателя
*/ */
function to($name) // recipient function to(string $name): void
{ {
$this->_to = $name; $this->_to = $name;
} }
function replyTo($name) // recipient /**
{ * @param string $name
} */
function replyTo($name): void
{}
/** /**
* Установка получателей копии * Установка получателей копии
*/ */
function copy($name) // recipient cc function copy(string $name): void
{ {
$this->copy = $name; $this->copy = $name;
} }
function notify($notify) function notify(string $notify): void
{ {
$this->_notify = $notify; $this->_notify = $notify;
} }
@ -60,59 +80,70 @@ class Mail
/** /**
* Тема письма * Тема письма
*/ */
function subject($subject) function subject(string $subject): void
{ {
$this->_subject = $subject; $this->_subject = $subject;
} }
/** /**
* Текст письма * Текст письма
*/ */
function setContent($text) function setContent(string $text): void
{ {
$this->content = $text; $this->content = $text;
} }
/** /**
* Кодировка текста в письме * Кодировка текста в письме
*/ */
function setEncoding($encoding) function setEncoding(string $encoding): void
{ {
$this->encoding = $encoding; $this->encoding = $encoding;
} }
/** /**
* Добавление вложения из файла * Добавление вложения из файла
* @param string $filename
* @param string|false $name
*/ */
function addAttachment($filename, $name = false) function addAttachment(string $filename, $name = false): void
{ {
assert(is_string($filename)); if (file_exists($filename)) {
if(file_exists($filename)) { // assert ??
$file = fopen($filename, "rb"); $file = fopen($filename, "rb");
if (is_resource($file)) { if (is_resource($file)) {
$data = fread($file, filesize($filename)); $size = filesize($filename);
$this->attachment [] = ($name) ? array($data, $name) : array($data, basename($filename)); if ($size !== false && $size > 0) {
$data = fread($file, $size);
$this->attachment[] = ($name) ? [$data, $name] : [$data, basename($filename)];
}
} }
} }
} }
function setType($type) /**
* Установка типа содержимого
* @param string $type
*/
function setType($type): void
{ {
$this->type = $type; $this->type = $type;
} }
/** /**
* Добавление вложения из строки с указанием имени файла * Добавление вложения из строки с указанием имени файла
* @param string $data
* @param string $name
*/ */
function addAttachmentRaw($data, $name) function addAttachmentRaw($data, string $name): void
{ {
assert(is_string($name)); $this->attachment[] = [$data, $name];
$this->attachment [] = array($data, $name);
} }
function quote($var, $val) /**
* @param string $var
* @param string $val
*/
function quote($var, $val): string
{ {
return ";" . PHP_EOL . "\t" . $var . "=\"" . $val . "\""; return ";" . PHP_EOL . "\t" . $var . "=\"" . $val . "\"";
} }
@ -120,44 +151,48 @@ class Mail
/** /**
* Общий формат тегов MIME * Общий формат тегов MIME
* @see http://tools.ietf.org/html/rfc2045 * @see http://tools.ietf.org/html/rfc2045
*
* @param string $name
* @param string $value
* @param array $args
*/ */
function mimeTag($name, $value, array $args = array()) function mimeTag($name, $value, array $args = []): string
{ {
assert (is_string($name)); assert(is_string($name));
assert (is_string($value)); assert(is_string($value));
return $name . ": " . $value . implode("", array_map(array($this, 'quote'), array_keys($args), array_values($args))) . PHP_EOL; return $name . ": " . $value . implode("", array_map([$this, 'quote'], array_keys($args), array_values($args))) . PHP_EOL;
} }
/** /**
* *
* @see http://tools.ietf.org/html/rfc2047 * @see http://tools.ietf.org/html/rfc2047
*/ */
function encodedWord($text, $encoding = 'B') function encodedWord(string $text, string $encoding = 'B'): string
{ {
return "=?{$this->encoding}?$encoding?".base64_encode($text)."?="; return "=?{$this->encoding}?$encoding?" . base64_encode($text) . "?=";
} }
/** /**
* Тело сообщения * Тело сообщения
*/ */
function getMessage() function getMessage(): string
{ {
$message = "--".$this->uniqid . PHP_EOL; $message = "--" . $this->uniqid . PHP_EOL;
$message .= $this->mimeTag("Content-Type", $this->type, array ('charset' => $this->encoding)); $message .= $this->mimeTag("Content-Type", $this->type, ['charset' => $this->encoding]);
$message .= $this->mimeTag("Content-Transfer-Encoding", "8bit"); $message .= $this->mimeTag("Content-Transfer-Encoding", "8bit");
$message .= PHP_EOL . $this->content . PHP_EOL . PHP_EOL; $message .= PHP_EOL . $this->content . PHP_EOL . PHP_EOL;
/* /*
* Вложения * Вложения
* http://tools.ietf.org/html/rfc2046#section-5.1.3 * http://tools.ietf.org/html/rfc2046#section-5.1.3
*/ */
foreach ($this->attachment as $value) { foreach ($this->attachment as $value) {
list($data, $name) = $value; list($data, $name) = $value;
$message .= "--" . $this->uniqid . PHP_EOL; $message .= "--" . $this->uniqid . PHP_EOL;
$message .= $this->mimeTag("Content-Type", "application/octet-stream", array ('name' => basename($name))); $message .= $this->mimeTag("Content-Type", "application/octet-stream", ['name' => basename($name)]);
$message .= $this->mimeTag("Content-Transfer-Encoding", "base64"); $message .= $this->mimeTag("Content-Transfer-Encoding", "base64");
$message .= $this->mimeTag("Content-Disposition", "attachment", array ('filename' => basename($name))); $message .= $this->mimeTag("Content-Disposition", "attachment", ['filename' => basename($name)]);
$message .= PHP_EOL . chunk_split(base64_encode($data)) . PHP_EOL; $message .= PHP_EOL . chunk_split(base64_encode($data)) . PHP_EOL;
} }
@ -167,7 +202,7 @@ class Mail
/** /**
* Заголовок сообщения * Заголовок сообщения
*/ */
function getHeader() function getHeader(): string
{ {
$head = $this->mimeTag("MIME-Version", "1.0"); $head = $this->mimeTag("MIME-Version", "1.0");
$head .= $this->mimeTag("From", $this->_from); $head .= $this->mimeTag("From", $this->_from);
@ -176,7 +211,7 @@ class Mail
if (is_string($this->_notify)) { if (is_string($this->_notify)) {
$head .= $this->mimeTag("Disposition-Notification-To", "\"" . $this->_notify . "\" <" . $this->_from . ">"); $head .= $this->mimeTag("Disposition-Notification-To", "\"" . $this->_notify . "\" <" . $this->_from . ">");
} }
$head .= $this->mimeTag("Content-Type", "multipart/mixed", array ("boundary" => $this->uniqid)); $head .= $this->mimeTag("Content-Type", "multipart/mixed", ["boundary" => $this->uniqid]);
if ($this->copy) { if ($this->copy) {
$head .= $this->mimeTag("BCC", $this->copy); $head .= $this->mimeTag("BCC", $this->copy);
} }
@ -184,7 +219,7 @@ class Mail
return $head; return $head;
} }
function getSubject() function getSubject(): string
{ {
return $this->encodedWord($this->_subject); return $this->encodedWord($this->_subject);
} }
@ -192,28 +227,29 @@ class Mail
/** /**
* Вывод строки для сохранения в формате .eml * Вывод строки для сохранения в формате .eml
*/ */
function eml() function eml(): string
{ {
return "To: " . $this->_to . PHP_EOL . "Subject: {$this->getSubject()}" . PHP_EOL . $this->getHeader() . $this->getMessage(); return "To: " . $this->_to . PHP_EOL . "Subject: {$this->getSubject()}" . PHP_EOL . $this->getHeader() . $this->getMessage();
} }
/** /**
* Отправка почты * Отправка почты
*/ */
function send() function send(): bool
{ {
$result = mail($this->_to, $this->getSubject(), $this->getMessage(), $this->getHeader()); $result = mail($this->_to, $this->getSubject(), $this->getMessage(), $this->getHeader());
if(! $result) { if (! $result) {
file_put_contents(Path::resolveFile("send.eml"), $this->eml()); file_put_contents(Path::resolveFile("send.eml"), $this->eml());
throw new Exception('Невозможно отправить почту'); throw new Exception('Невозможно отправить почту');
} }
return $result; return $result;
} }
function clear() { function clear(): void
{
foreach ($this->attachment as $key => &$value) { foreach ($this->attachment as $key => &$value) {
unset($this->attachment[$key]); unset($this->attachment[$key]);
} }
$this->attachment = array(); $this->attachment = [];
} }
} }

View file

@ -1,9 +1,15 @@
<?php <?php
namespace ctiso;
use PHPMailer\PHPMailer\PHPMailer;
class MailAlt class MailAlt
{ {
/** @var PHPMailer */
public $mailer; public $mailer;
/** @var string */
public $_notify; public $_notify;
/** @var string */
public $encoding; public $encoding;
function __construct() { function __construct() {
@ -14,62 +20,65 @@ class MailAlt
/** /**
* Установка отправителя * Установка отправителя
*/ */
function from($name) function from(string $name): void
{ {
$this->mailer->setFrom($name); $this->mailer->setFrom($name);
} }
/** /**
* Установка получателя * Установка получателя
*/ */
function to($name) // recipient function to(string $name): void // recipient
{ {
$this->mailer->addAddress($name); $this->mailer->addAddress($name);
} }
function replyTo($name) // recipient function replyTo(string $name): void // recipient
{ {
$this->mailer->AddReplyTo($name); $this->mailer->addReplyTo($name);
} }
/** /**
* Установка получателей копии * Установка получателей копии
* @param string $name
*/ */
function copy($name) // recipient cc function copy(string $name): void // recipient cc
{ {
$this->mailer->addCC($name); $this->mailer->addCC($name);
} }
function notify($notify) function notify(string $notify): void
{ {
$this->_notify = $notify; $this->_notify = $notify;
} }
/** /**
* Тема письма * Тема письма
* @param string $subject
*/ */
function subject($subject/*: string*/) function subject(string $subject): void
{ {
$this->mailer->Subject = $subject; $this->mailer->Subject = $subject;
} }
/** /**
* Текст письма * Текст письма
* @param string $text
*/ */
function setContent($text) function setContent(string $text): void
{ {
$this->mailer->Body = $text; $this->mailer->Body = $text;
} }
function setType($text) function setType(string $text): void
{ {
$this->mailer->isHTML($text == 'text/html'); $this->mailer->isHTML($text == 'text/html');
} }
/** /**
* Кодировка текста в письме * Кодировка текста в письме
*/ */
function setEncoding($encoding) function setEncoding(string $encoding): void
{ {
$this->encoding = $encoding; $this->encoding = $encoding;
} }
@ -77,20 +86,20 @@ class MailAlt
/** /**
* Добавление вложения из файла * Добавление вложения из файла
*/ */
function addAttachment($filename, $name = null) function addAttachment(string $filename, ?string $name = null): void
{ {
$this->mailer->addAttachment($filename, $name); $this->mailer->addAttachment($filename, $name);
} }
/** /**
* Отправка почты * Отправка почты
*/ */
function send() function send(): bool
{ {
return $this->mailer->send(); return $this->mailer->send();
} }
function eml() { function eml(): string {
return $this->mailer->getSentMIMEMessage(); return $this->mailer->getSentMIMEMessage();
} }
} }

12
src/Model/BaseMapper.php Normal file
View file

@ -0,0 +1,12 @@
<?php
namespace ctiso\Model;
/**
* @property \ctiso\Database $db
*/
abstract class BaseMapper {
function getAllAsOptions(): array {
return [];
}
}

View file

@ -1,30 +1,40 @@
<?php <?php
class Model_Factory namespace ctiso\Model;
{
static $shortcut = "model";
public $db;
public $_registry;
public $_shortcut;
public function __construct ($db/*: Database*/, Settings $_registry = null) use ctiso\Registry;
use ctiso\Database;
use ctiso\Role\User;
class Factory
{
/** @var Database */
public $db;
/** @var ?Registry */
public $config;
/** @var ?User */
public $user;
public function __construct(Database $db, ?Registry $config = null, ?User $user = null)
{ {
$this->db = $db; $this->db = $db;
$this->_registry = $_registry; $this->config = $config;
$this->user = $user;
} }
/** /**
* Создает модель * Создает модель
* @param string $name * @template T
* @return model * @param class-string<T> $modelName
* @return T
*/ */
public function getModel ($name) public function getModel($modelName)
{ {
$modelName = "Mapper_" . $name;
$model = new $modelName(); $model = new $modelName();
$model->db = $this->db; $model->db = $this->db;
$model->factory = $this; $model->factory = $this;
$model->_registry = $this->_registry; $model->config = $this->config;
$model->user = $this->user;
$model->setUp(); $model->setUp();
// //
return $model; return $model;

View file

@ -1,24 +1,30 @@
<?php <?php
namespace ctiso;
class Numbers class Numbers
{ {
static function roman($i) static function roman(float $i): float
{ {
return 0; return 0;
} }
static function decimal($i) static function decimal(float $i): float
{ {
return $i; return $i;
} }
static function prefix($prefix, array $array, $key = false) /**
* @param array $array
* @return array
*/
static function prefix(callable $prefix, array $array)
{ {
$result = array(); $result = [];
for ($i = 0; $i < count($array); $i++) { $count = count($array);
$result [] = call_user_func($prefix, $i + 1) . '. ' . $array[$i]; for ($i = 0; $i < $count; $i++) {
$result [] = call_user_func($prefix, $i + 1) . '. ' . $array[$i];
} }
return $result; return $result;
} }
} }

View file

@ -6,19 +6,22 @@
* *
*/ */
class Path namespace ctiso;
class Path
{ {
const SEPARATOR = "/"; const SEPARATOR = "/";
protected $path = array(); protected array $path = [];
protected $url = array(); protected array $url = [];
protected $absolute = false; protected bool $absolute = false;
public function __construct($path) /**
* @param string $path Путь (Тип указан в doccomments т.к откудато приходит null)
*/
public function __construct($path = '')
{ {
// assert(is_string($path)); $this->url = parse_url($path) ?: [];
$this->url = parse_url($path);
if (isset($this->url['path'])) { if (isset($this->url['path'])) {
$path = $this->url['path']; $path = $this->url['path'];
@ -33,18 +36,18 @@ class Path
$this->path = self::optimize($list); $this->path = self::optimize($list);
} }
} }
static function factory($path) { static function factory(string $path): Path {
return new Path($path); return new Path($path);
} }
public function getParts() public function getParts(): array
{ {
return $this->path; return $this->path;
} }
public static function normalize($pathName) public static function normalize(string $pathName): string
{ {
$path = new Path($pathName); $path = new Path($pathName);
return $path->__toString(); return $path->__toString();
@ -52,12 +55,12 @@ class Path
/** /**
* Базовое имя * Базовое имя
* @param $path * @param string $path
* @return mixed * @return string
*/ */
public static function basename($path) public static function basename($path)
{ {
$list = preg_split('#\\\\|/#s', $path); $list = preg_split('#\\\\|/#s', $path) ?: [''];
return end($list); return end($list);
} }
@ -65,16 +68,19 @@ class Path
* Возвращает расширение файла * Возвращает расширение файла
* *
* @param string $fileName Полное имя файла * @param string $fileName Полное имя файла
*
* @return string * @return string
*/ */
static function getExtension($fileName) static function getExtension($fileName)
{ {
assert(is_string($fileName) || is_null($fileName));
return pathinfo($fileName, PATHINFO_EXTENSION); return pathinfo($fileName, PATHINFO_EXTENSION);
} }
/**
* Проверяет расширение файла
* @param string $fileName Полное имя файла
* @param string|array $extension Расширение файла
* @return bool
*/
static function isType($fileName, $extension) static function isType($fileName, $extension)
{ {
if (is_array($extension)) { if (is_array($extension)) {
@ -86,17 +92,11 @@ class Path
/** /**
* Полное имя файла без расширения * Полное имя файла без расширения
*
* @param string $fileName Имя файла
*
* @return string
*/ */
static function skipExtension($fileName) static function skipExtension(string $fileName): string
{ {
assert(is_string($fileName));
$path = pathinfo($fileName); $path = pathinfo($fileName);
if ($path['dirname'] == ".") { if (!isset($path['dirname']) || $path['dirname'] === ".") {
return $path['filename']; return $path['filename'];
} else { } else {
return self::join($path['dirname'], $path['filename']); return self::join($path['dirname'], $path['filename']);
@ -107,13 +107,10 @@ class Path
* Возвращает имя файла без расширения * Возвращает имя файла без расширения
* *
* @param string $fileName Полное имя файла * @param string $fileName Полное имя файла
*
* @return string * @return string
*/ */
static function getFileName($fileName) static function getFileName(string $fileName)
{ {
assert(is_string($fileName));
return pathinfo($fileName, PATHINFO_FILENAME); return pathinfo($fileName, PATHINFO_FILENAME);
} }
@ -123,47 +120,47 @@ class Path
* Преобразует строку пути в массив * Преобразует строку пути в массив
* *
* @param string $path Путь * @param string $path Путь
* * @return list<string>
* @return array
*/ */
public static function listFromString($path) public static function listFromString(string $path): array
{ {
assert(is_string($path));
$list = preg_split('#\\\\|/#s', $path); $list = preg_split('#\\\\|/#s', $path);
return $list ?: [];
return $list;
} }
/** /**
* Преобразует относительный путь в абсолютный * Преобразует относительный путь в абсолютный
* @param array<string> $path Путь
* @return array<string>
*/ */
public static function optimize($path) // public static function optimize($path) //
{ {
$result = array(); $result = [];
foreach ($path as $n) { foreach ($path as $n) {
switch ($n) { switch ($n) {
// Может быть относительным или абсолютным путем // Может быть относительным или абсолютным путем
case "": break; case "": case ".": break;
case ".": break;
case "..": case "..":
if (count($result) > 0 && $result[count($result) - 1] != '..') { if (count($result) > 0 && $result[count($result) - 1] != '..') {
array_pop($result); break; array_pop($result); break;
} }
default: default:
array_push($result, $n); $result[] = $n;
} }
} }
return $result; return $result;
} }
// Сравнение двух путей на равентство /**
public function equal($path/*: Path*/) * Сравнение двух путей на равентство
*/
public function equal(Path $path): bool
{ {
if (count($this->path) == count($path->path)) { $count = count($this->path);
for ($i = 0; $i < count($this->path); $i++) { if ($count == count($path->path)) {
for ($i = 0; $i < $count; $i++) {
if ($this->path[$i] != $path->path[$i]) { if ($this->path[$i] != $path->path[$i]) {
return false; return false;
} }
} }
return true; return true;
@ -171,27 +168,29 @@ class Path
return false; return false;
} }
public static function makeUrl($path) /**
* Преобразует путь в строку
* @param array{ host?: string, path?: string, query?: string, fragment?: string, scheme?: string, user?: string, pass?: string, port?: int} $path Путь
*/
public static function makeUrl($path): string
{ {
$slash = (isset($path['host']) && (strlen($path['path']) > 0) && ($path['path'][0] != '/')) ? '/' : ''; $slash = (isset($path['host']) && isset($path['path']) && (strlen($path['path']) > 0) && ($path['path'][0] != '/')) ? '/' : '';
$scheme = isset($path['scheme']) ? $path['scheme'] . ':/' : '';
return (isset($path['scheme']) ? $path['scheme'] . ':/' : '') $user = isset($path['user']) ? $path['user'] . (isset($path['pass']) ? ':' . $path['pass'] : '') . '@' : '';
. (isset($path['host']) ? ('/'
. (isset($path['user']) ? $path['user'] . (isset($path['pass']) ? ':' . $path['pass'] : '') . '@' : '') $port = isset($path['port']) ? ':' . $path['port'] : '';
. $path['host'] $host = isset($path['host']) ? ('/' . $user . $path['host'] . $port) : '';
. (isset($path['port']) ? ':' . $path['port'] : '')) : '')
. $slash $query = isset($path['query']) ? '?' . $path['query'] : '';
. $path['path'] $fragment = isset($path['fragment']) ? '#' . $path['fragment'] : '';
. (isset($path['query']) ? '?' . $path['query'] : '')
. (isset($path['fragment']) ? '#' . $path['fragment'] : ''); return $scheme . $host . $slash . ($path['path'] ?? '') . $query . $fragment;
} }
/** /**
* Преобразует путь в строку * Преобразует путь в строку
*
* @return string
*/ */
public function __toString() public function __toString(): string
{ {
$result = (($this->absolute) ? '/' : '') . implode(self::SEPARATOR, $this->path); $result = (($this->absolute) ? '/' : '') . implode(self::SEPARATOR, $this->path);
$this->url['path'] = $result; $this->url['path'] = $result;
@ -201,17 +200,16 @@ class Path
/** /**
* Проверяет является ли папка родительской для другой папки * Проверяет является ли папка родительской для другой папки
* *
* @parma Path $path * @param Path $path
*
* @return boolean
*/ */
public function isParent($path/*: Path*/) public function isParent($path): bool
{ {
if (isset($this->url['host']) && isset($path->url['host']) if (isset($this->url['host']) && isset($path->url['host'])
&& ($this->url['host'] != $path->url['host'])) return false; && ($this->url['host'] != $path->url['host'])) return false;
if (count($path->path) > count($this->path)) { $count = count($this->path);
for ($i = 0; $i < count($this->path); $i++) { if (count($path->path) > $count) {
for ($i = 0; $i < $count; $i++) {
if ($path->path[$i] != $this->path[$i]) { if ($path->path[$i] != $this->path[$i]) {
return false; return false;
} }
@ -221,17 +219,16 @@ class Path
return false; return false;
} }
public static function _isParent($path1, $path2) public static function _isParent(string $path1, string $path2): bool
{ {
$path = new Path($path1); $path = new Path($path1);
return $path->isParent(new Path($path2)); return $path->isParent(new Path($path2));
} }
/** /**
* Находит путь относительно текущего путя * Находит путь относительно текущего путя
*
* @param string $name Полный путь к файлу
* *
* @param string $name Полный путь к файлу
* @return string Относительный путь к файлу * @return string Относительный путь к файлу
*/ */
public function relPath($name) public function relPath($name)
@ -246,7 +243,12 @@ class Path
return $path->__toString(); return $path->__toString();
} }
// Вычисляет относительный путь в виде строки /**
* Вычисляет относительный путь в виде строки
* @param string $rpath Путь относительно которого вычисляется относительный путь
* @param string $lpath Путь к которому вычисляется относительный путь
* @return string Относительный путь
*/
static function relative($rpath, $lpath) { static function relative($rpath, $lpath) {
// Нужно проверять диск!! // Нужно проверять диск!!
$self = new Path($rpath); $self = new Path($rpath);
@ -254,21 +256,28 @@ class Path
$self_path = $self->getParts(); $self_path = $self->getParts();
$list_path = $list->getParts(); $list_path = $list->getParts();
$result = array(); $result = [];
for ($i = 0; $i < count($list_path); $i++) { $count = count($list_path);
if (($i >= count($self_path)) || $list_path[$i] != $self_path[$i]) { for ($i = 0; $i < $count; $i++) {
break; if (($i >= count($self_path)) || $list_path[$i] != $self_path[$i]) {
break;
} }
} }
for($j = $i; $j < count($list_path); $j++) { $list_count = count($list_path);
for($j = $i; $j < $list_count; $j++) {
array_push($result, '..'); array_push($result, '..');
} }
for($j = $i; $j < count($self_path); $j++) { $self_count = count($self_path);
for($j = $i; $j < $self_count; $j++) {
array_push($result, $self_path[$j]); array_push($result, $self_path[$j]);
} }
return implode("/", $result); return implode("/", $result);
} }
/**
* @param string $path
* @return string
*/
public function append($path) public function append($path)
{ {
$base = $this->__toString(); $base = $this->__toString();
@ -278,11 +287,11 @@ class Path
/** /**
* Обьединяет строки в Path соединяя необходимым разделителем * Обьединяет строки в Path соединяя необходимым разделителем
* fixme не обрабатывает параметры урла, решение Path::join(SITE_WWW_PATH) . '?param=pampam' * fixme не обрабатывает параметры урла, решение Path::join(SITE_WWW_PATH) . '?param=pampam'
* @return string * @param string ...$args
* @return string
*/ */
static function fromJoin($_rest) { static function fromJoin(...$args) {
$args = func_get_args(); $result = [];
$result = array();
$parts0 = new Path(array_shift($args)); $parts0 = new Path(array_shift($args));
$result [] = $parts0->getParts(); $result [] = $parts0->getParts();
foreach ($args as $file) { foreach ($args as $file) {
@ -291,7 +300,7 @@ class Path
} }
// При обьединении ссылок можно обьеденить path, query, fragment // При обьединении ссылок можно обьеденить path, query, fragment
$path = implode(self::SEPARATOR, self::optimize(call_user_func_array('array_merge', $result))); $path = implode(self::SEPARATOR, self::optimize(call_user_func_array('array_merge', $result)));
$parts0->url['path'] = ($parts0->isAbsolute()) ? '/' . $path : $path; $parts0->url['path'] = ($parts0->isAbsolute()) ? '/' . $path : $path;
return $parts0; return $parts0;
} }
@ -299,32 +308,33 @@ class Path
/** /**
* Обьединяет строки в строку пути соединяя необходимым разделителем * Обьединяет строки в строку пути соединяя необходимым разделителем
* fixme не обрабатывает параметры урла, решение Path::join(SITE_WWW_PATH) . '?param=pampam' * fixme не обрабатывает параметры урла, решение Path::join(SITE_WWW_PATH) . '?param=pampam'
* @return string * @param string ...$args
* @return string
*/ */
static function join($_rest) static function join(...$args)
{ {
$args = func_get_args(); $path = call_user_func_array([self::class, "fromJoin"], $args);
$path = call_user_func_array("self::fromJoin", $args);
return self::makeUrl($path->url); return self::makeUrl($path->url);
} }
// Проверка структуры имени файла // Проверка структуры имени файла
static function checkName($name, $extension) static function checkName(string $name, string $extension): bool
{ {
return (strlen(pathinfo($name, PATHINFO_FILENAME)) > 0) && (pathinfo($name, PATHINFO_EXTENSION) == $extension); return (strlen(pathinfo($name, PATHINFO_FILENAME)) > 0) && (pathinfo($name, PATHINFO_EXTENSION) == $extension);
} }
static function isCharName($char) static function isCharName(string $char): bool
{ {
$ch = ord($char); $ch = ord($char);
return ((($ch >= ord('a')) && ($ch <= ord('z'))) || (strpos('-._', $char) !== false) || (($ch >= ord('0')) && ($ch <= ord('9')))); return ((($ch >= ord('a')) && ($ch <= ord('z'))) || (strpos('-._', $char) !== false) || (($ch >= ord('0')) && ($ch <= ord('9'))));
} }
// Проверка имени файла // Проверка имени файла
static function isName($name) { static function isName(string $name): bool
{
if (strlen(trim($name)) == 0) { if (strlen(trim($name)) == 0) {
return false; return false;
} }
for ($i = 0; $i < strlen($name); $i++) { for ($i = 0; $i < strlen($name); $i++) {
if (!self::isCharName($name[$i])) { if (!self::isCharName($name[$i])) {
return false; return false;
@ -333,7 +343,7 @@ class Path
return true; return true;
} }
public function isAbsolute() public function isAbsolute(): bool
{ {
return $this->absolute; return $this->absolute;
} }
@ -342,7 +352,6 @@ class Path
* Подбирает новое временное имя для файла * Подбирает новое временное имя для файла
* *
* @param string $dst Предпологаемое имя файла * @param string $dst Предпологаемое имя файла
*
* @return string Новое имя файла * @return string Новое имя файла
*/ */
static function resolveFile($dst) static function resolveFile($dst)
@ -359,41 +368,49 @@ class Path
} }
/** /**
* Список файлов в директории * Список файлов в директории
* *
* @param array $allow массив расширений для файлов * @param ?string[] $allow массив расширений для файлов
* @param array $ignore массив имен пааок которые не нужно обрабатывать * @param string[] $ignore массив имен пааок которые не нужно обрабатывать
* *
* @returnarray * @return string[]
*/ */
public function getContent($allow = null, $ignore = array()) public function getContent(?array $allow = null, array $ignore = [])
{ {
$ignore = array_merge(array(".", ".."), $ignore); $ignore = array_merge([".", ".."], $ignore);
return self::fileList($this->__toString(), $allow, $ignore); return self::fileList($this->__toString(), $allow, $ignore);
} }
/** /**
* Обьединяет строки в путь соединяя необходимым разделителем * Обьединяет строки в путь соединяя необходимым разделителем
*
* @param array $allow массив расширений разрешеных для файлов
* @param array $ignore массив имен пааок которые не нужно обрабатывать
* *
* @return array * @param ?string[] $allow массив расширений разрешеных для файлов
*/ * @param string[] $ignore массив имен папок которые не нужно обрабатывать
function getContentRec($allow = null, $ignore = array()) *
* @return string[]
*/
function getContentRec(?array $allow = null, array $ignore = [])
{ {
$result = array (); $result = [];
$ignore = array_merge(array (".", ".."), $ignore); $ignore = array_merge([".", ".."], $ignore);
self::fileListAll($result, $this->__toString(), $allow, $ignore); self::fileListAll($result, $this->__toString(), $allow, $ignore);
return $result; return $result;
} }
// Использовать SPL ??? /**
protected static function fileList($base, &$allow, &$ignore) * Список файлов в директории
*
* @param string $base Базовый путь
* @param ?string[] $allow массив расширений для файлов
* @param string[] $ignore массив имен папок которые не нужно обрабатывать
*
* @return string[]
*/
protected static function fileList(string $base, ?array &$allow, array &$ignore): array
{ {
if ($base == '') $base = '.'; if ($base == '') $base = '.';
$result = array (); $result = [];
$handle = opendir($base); $handle = opendir($base);
if (is_resource($handle)) { if (is_resource($handle)) {
while (true) { while (true) {
@ -402,7 +419,7 @@ class Path
if (! in_array($file, $ignore)) { if (! in_array($file, $ignore)) {
$isDir = is_dir(Path::join($base, $file)); $isDir = is_dir(Path::join($base, $file));
if ($isDir || ($allow == null) || in_array(self::getExtension($file), $allow)) { if ($isDir || ($allow == null) || in_array(self::getExtension($file), $allow)) {
$result[] = $file; $result[] = $file;
} }
} }
continue; continue;
@ -414,8 +431,8 @@ class Path
// При обьединении ссылок можно обьеденить path, query, fragment // При обьединении ссылок можно обьеденить path, query, fragment
return $result; return $result;
} }
protected static function fileListAll(&$result, $base, &$allow, &$ignore) protected static function fileListAll(array &$result, string $base, ?array &$allow, array &$ignore): void
{ {
$files = self::fileList($base, $allow, $ignore); $files = self::fileList($base, $allow, $ignore);
foreach ($files as $name) { foreach ($files as $name) {
@ -435,7 +452,7 @@ class Path
* *
* @return void * @return void
*/ */
static function prepare($dst, $filename = true) static function prepare(string $dst, bool $filename = true)
{ {
if ($filename) { if ($filename) {
$path_dst = pathinfo($dst, PATHINFO_DIRNAME); $path_dst = pathinfo($dst, PATHINFO_DIRNAME);
@ -449,29 +466,29 @@ class Path
/** /**
* Обновить относительную ссылку при переносе файла * Обновить относительную ссылку при переносе файла
* *
* @param String $relativePath - относительная ссылка до переноса * @param string $relativePath - относительная ссылка до переноса
* @param String $srcFile - абсолютный путь к папке содержащей файл со ссылкой до переноса * @param string $srcFile - абсолютный путь к папке содержащей файл со ссылкой до переноса
* @param String $dstFile - абсолютный путь к папке содержащей файл со ссылкой после переноса * @param string $dstFile - абсолютный путь к папке содержащей файл со ссылкой после переноса
* *
* @return String * @return string
*/ */
static function updateRelativePathOnFileMove($relativePath, $srcFile, $dstFile) { static function updateRelativePathOnFileMove(string $relativePath, string $srcFile, string $dstFile) {
$srcToDst = self::relative($srcFile, $dstFile); $srcToDst = self::relative($srcFile, $dstFile);
return self::normalize(self::join($srcToDst, $relativePath)); return self::normalize(self::join($srcToDst, $relativePath));
} }
/** /**
* Обновить относительную ссылку в файле при переносе папки * Обновить относительную ссылку в файле при переносе папки
* *
* @param String $relativePath - относительная ссылка до переноса * @param string $relativePath относительная ссылка до переноса
* @param String $fileDir - абсолютный путь к директории файла содержащего ссылку до переноса * @param string $fileDir абсолютный путь к директории файла содержащего ссылку до переноса
* @param String $srcDir - абсолютный путь к переносимой директории до переноса * @param string $srcDir абсолютный путь к переносимой директории до переноса
* @param String $dstDir - абсолютный путь к переносимой директории после переноса * @param string $dstDir абсолютный путь к переносимой директории после переноса
* *
* @return * @return string
*/ */
static function updateRelativePathOnDirectoryMove($relativePath, $fileDir, $srcDir, $dstDir) { static function updateRelativePathOnDirectoryMove(string $relativePath, string $fileDir, string $srcDir, string $dstDir) {
$relativePath = self::normalize($relativePath); $relativePath = self::normalize($relativePath);
$fileDir = self::normalize($fileDir); $fileDir = self::normalize($fileDir);
$srcDir = self::normalize($srcDir); $srcDir = self::normalize($srcDir);

View file

@ -3,80 +3,117 @@
/** /**
* Преобразование типов !!! Пересмотреть использование классов!! * Преобразование типов !!! Пересмотреть использование классов!!
* Класс преобразования типа значения поля класса в тип поля таблицы * Класс преобразования типа значения поля класса в тип поля таблицы
* @package system
*/ */
namespace ctiso;
class Primitive { class Primitive {
// varchar /**
public static function to_varchar($value) * @param mixed $value
*/
public static function to_varchar($value): string
{ {
return ((string) $value); return ((string) $value);
} }
/**
* @param mixed $value
* @return mixed
*/
public static function from_varchar($value) public static function from_varchar($value)
{ {
return $value; return $value;
} }
// int /**
public static function to_bool($value) * @param mixed $value
*/
public static function to_bool($value): bool
{ {
return filter_var($value, FILTER_VALIDATE_BOOLEAN);//(int)((bool) $value); return filter_var($value, FILTER_VALIDATE_BOOLEAN);
} }
public static function from_bool($value) /**
* Преобразование значения в булевое значение
* @param string $value
* @return bool
*/
public static function from_bool($value): bool
{ {
return ((bool) $value); return ((bool) $value);
} }
// int /**
public static function to_int($value) * Преобразование значения в целое число
* @param string $value
* @return int
*/
public static function to_int($value): int
{ {
return ((int) $value); return ((int) $value);
} }
public static function from_int($value) /**
* @param mixed $value
*/
public static function from_int($value): string
{ {
return ((string) $value); return ((string) $value);
} }
// date /**
public static function to_date($value) * Преобразование даты dd/mm/yy в unix timestamp
* @param string $value
* @return int
*/
public static function to_date($value): int
{ {
$result = 0; $result = 0;
$tmp = explode("/", $value, 3); $tmp = explode("/", $value ?? '', 3);
if (!empty($tmp)) {
if (count($tmp) != 3) return $result;
$year = intval($tmp[2]); if (count($tmp) != 3) {
$month = intval($tmp[1]); return $result;
$day = intval($tmp[0]); }
if ($month != 0 && $day != 0 && $year != 0) { $year = (int)$tmp[2];
if (checkdate($month, $day, $year)) { $month = (int)$tmp[1];
return mktime(0, 0, 0, $month, $day, $year); $day = (int)$tmp[0];
} else {
return 0; if ($month != 0 && $day != 0 && $year != 0) {
} if (checkdate($month, $day, $year)) {
return mktime(0, 0, 0, $month, $day, $year) ?: 0;
} else {
return 0;
} }
} }
return $result; return $result;
} }
public static function to_datetime($value) /**
* Преобразование даты ISO 8601 в unix timestamp
* @param string $value
* @return int
*/
public static function to_datetime($value): int
{ {
$result = 0; $result = 0;
$tmp = array(); $tmp = [];
if (preg_match('/(\d+)-(\d+)-(\d+)T(\d+):(\d+)Z/', $value, $tmp)) { if (preg_match('/(\d+)-(\d+)-(\d+)T(\d+):(\d+)Z/', $value, $tmp)) {
if (checkdate($tmp[2], $tmp[3], $tmp[1])) { if (checkdate((int)$tmp[2], (int)$tmp[3], (int)$tmp[1])) {
$result = mktime($tmp[4], $tmp[5], 0, $tmp[2], $tmp[3], $tmp[1]); $result = mktime((int)$tmp[4], (int)$tmp[5], 0, (int)$tmp[2], (int)$tmp[3], (int)$tmp[1]) ?: 0;
} }
} }
return $result; return $result;
} }
public static function from_date($value) /**
* Преобразование даты в формат dd/mm/yyyy
* @param int $value
* @return string
*/
public static function from_date($value): string
{ {
if ($value > 0) { if ($value > 0) {
return date("d/m/Y", $value); return date("d/m/Y", $value);
@ -84,7 +121,12 @@ class Primitive {
return ''; return '';
} }
public static function from_datetime($value) /**
* Преобразование даты в формат ISO 8601
* @param int $value
* @return string
*/
public static function from_datetime($value): string
{ {
if ($value > 0) { if ($value > 0) {
return date("Y-m-d\TH:i\Z", $value); return date("Y-m-d\TH:i\Z", $value);
@ -93,24 +135,45 @@ class Primitive {
} }
// secure /**
* @deprecated
* @template T
* @param T $value
* @return T
*/
public static function to_secure($value) public static function to_secure($value)
{ {
// Значение приабразуется во время сохранения в базе данных // Значение приабразуется во время сохранения в базе данных
return $value; return $value;
} }
/**
* @deprecated
* @template T
* @param T $value
* @return T
*/
public static function from_secure($value) public static function from_secure($value)
{ {
return $value; return $value;
} }
// array /**
public static function to_array($value) * Преобразование значения в массив
* @param mixed $value
* @return array
*/
public static function to_array($value)
{ {
return (is_array($value)) ? $value : array(); return (is_array($value)) ? $value : [];
} }
/**
* @deprecated
* @template T
* @param T $value
* @return T
*/
public static function from_array($value) public static function from_array($value)
{ {
return $value; return $value;

68
src/Process.php Normal file
View file

@ -0,0 +1,68 @@
<?php
namespace ctiso;
/**
* str_getcsv
* @param string $input
* @param string $delimiter
* @param string $enclosure
* @param string $escape
* @return array|false
*/
function str_getcsv($input, $delimiter = ",", $enclosure = '"', $escape = "\\")
{
$fiveMBs = 1024;
$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
$data = [];
if (is_resource($fp)) {
fputs($fp, $input);
rewind($fp);
$data = fgetcsv($fp, 1000, $delimiter, $enclosure);
fclose($fp);
}
return $data;
}
/**
* process_exists
* @param int $pid
* @return bool
*/
function process_exists($pid)
{
if (PHP_OS == 'WINNT') {
$content = shell_exec("tasklist.exe /NH /FO CSV");
if (!$content) {
return false;
}
$processes = explode("\n", $content);
foreach ($processes as $process) {
if ($process != "") {
$csv = str_getcsv($process);
if ($csv && $pid == $csv[1]) return true;
}
}
return false;
} else {
return file_exists("/proc/$pid");
}
}
/**
* create_single_proces
* @param string $fpid
* @param callable $fn
* @return int
*/
function create_single_process($fpid, $fn)
{
if (file_exists($fpid)) {
if (process_exists((int)file_get_contents($fpid))) {
return 1;
}
}
call_user_func($fn);
return 0;
}

View file

@ -1,40 +1,62 @@
<?php <?php
///<reference path="Settings.php" /> namespace ctiso;
use ctiso\File,
Exception;
/** class Registry {
* http://www.patternsforphp.com/wiki/Registry /** @var array<string, array{path: string, data: mixed}> */
* http://www.patternsforphp.com/wiki/Singleton private array $namespace = [];
* http://www.phppatterns.com/docs/design/the_registry?s=registry
*/
class Registry extends Settings function importFile(string $namespace, ?string $filePath = null): void {
{ $data = json_decode(File::getContents($filePath), true);
static $instance = null; $data['_file'] = $filePath;
$this->namespace[$namespace] = [
/** 'path' => $filePath,
*/ 'data' => $data
static public function getInstance() ];
{ }
if (self::$instance == null) {
self::$instance = new Registry(); function importArray(string $namespace, array $data = []): void {
if (isset($this->namespace[$namespace])) {
$data = array_merge($this->namespace[$namespace]['data'], $data);
} }
return self::$instance; $this->namespace[$namespace] = [
'path' => null,
'data' => $data
];
} }
/** /**
* Список модулей * @param string $ns
* @param string $key
* @return mixed
* @throws Exception
*/ */
public function getModules() public function get(string $ns, string $key) {
{ if (isset($this->namespace[$ns]['data'][$key])) {
return array_keys($this->data); return $this->namespace[$ns]['data'][$key];
}
throw new Exception('Unknown key ' . $ns . '::' . $key);
} }
/** /**
* Проверка наличия модуля * @param string $ns
* @param string $key
* @return mixed|null
*/ */
public function hasModule($name) public function getOpt(string $ns, string $key) {
{ if (isset($this->namespace[$ns]['data'][$key])) {
return isset($this->data[$name]); return $this->namespace[$ns]['data'][$key];
}
return null;
}
public function has(string $ns, string $key): bool {
return isset($this->namespace[$ns]['data'][$key]);
}
function set(string $ns, string $key, mixed $value): void {
$this->namespace[$ns]['data'][$key] = $value;
} }
} }

128
src/Role/User.php Normal file
View file

@ -0,0 +1,128 @@
<?php
namespace ctiso\Role;
use ctiso\Database;
use ctiso\Database\Statement;
use ctiso\Database\PDOStatement;
// Класс должен быть в библиотеке приложения
class User implements UserInterface
{
const LIFE_TIME = 1800; // = 30min * 60sec;
public string $fullname;
public string $name;
/** @var string */
public $access;
public string $password;
/** @var int */
public $id;
public Database $db;
public array $groups;
function __construct(Database $db, array $groups) {
$this->db = $db;
$this->groups = $groups;
}
public function setDB(Database $db): void {
$this->db = $db;
}
public function getName(): string {
return $this->name;
}
/**
* @return bool
*/
function isLogged() {
return \ctiso\Filter\Authorization::isLogged();
}
public function getUserByQuery(Statement $stmt): ?PDOStatement
{
$result = $stmt->executeQuery();
if ($result->next()) {
$this->access = $this->groups[$result->getString('access')];
$this->name = $result->getString('login');
$this->id = $result->getInt('id_user');
$this->password = $result->getString('password');
$this->fullname = implode(' ', [
$result->getString('surname'),
$result->getString('firstname'),
$result->getString('patronymic')]);
return $result;
}
return null;
}
/**
* @param PDOStatement $result
* @return string
*/
function getUserPassword($result) {
return $result->get('password');
}
public function getUserByLogin(string $login): ?PDOStatement
{
$stmt = $this->db->prepareStatement("SELECT * FROM users WHERE login = ?");
$stmt->setString(1, $login);
$result = $this->getUserByQuery($stmt);
if ($result) {
$time = time();
$id = $this->id;
$this->db->executeQuery(
"UPDATE users SET lasttime = :time WHERE id_user = :id",
['time' => $time, 'id' => $id]); // Время входа
}
return $result;
}
public function getUserById(int $id): ?PDOStatement
{
$stmt = $this->db->prepareStatement("SELECT * FROM users WHERE id_user = ?");
$stmt->setInt(1, $_SESSION ['access']);
$result = $this->getUserByQuery($stmt);
if ($result) {
$lasttime = $result->getInt('lasttime');
$time = time();
if ($time - $lasttime > self::LIFE_TIME) return null; // Вышло время сессии
$id = $this->id;
}
return $result;
}
/**
* @param string $random
* @param PDOStatement $result
* @return PDOStatement|bool
*/
function setSID(string $random, $result)
{
return $this->db->executeQuery("UPDATE users SET sid = :sid, trie_count = 0 WHERE id_user = :user", [
'user' => $result->getInt('id_user'),
'sid' => $random
]);
}
function resetTries(string $login): void {
$this->db->executeQuery(
"UPDATE users SET trie_count = :count WHERE login = :login",
['count' => 0, 'login' => $login]
);
}
function updateTries(string $login): void {
$user = $this->db->fetchOneArray("SELECT id_user, trie_count FROM users WHERE login = :login", ['login' => $login]);
if ($user === false) {
return;
}
$this->db->executeQuery(
"UPDATE users SET trie_time = :cur_time, trie_count = :count WHERE id_user = :id_user",
['cur_time' => time(), 'count' => $user['trie_count']+1, 'id_user' => $user['id_user']]
);
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace ctiso\Role;
use ctiso\Database\Statement;
use ctiso\Database\PDOStatement;
interface UserInterface {
function getUserByQuery(Statement $stmt): ?PDOStatement;
function getUserByLogin(string $login): ?PDOStatement;
function getUserById(int $id): ?PDOStatement;
function getName(): string;
/**
* @param string $random
* @param PDOStatement $result
* @return PDOStatement|bool
*/
function setSID(string $random, $result);
}

View file

@ -1,7 +1,9 @@
<?php <?php
namespace ctiso;
class Security { class Security {
static function generatePassword($length = 9, $strength = 0) { static function generatePassword(int $length = 9, int $strength = 0): string {
$vowels = 'aeuy'; $vowels = 'aeuy';
$consonants = 'bdghjmnpqrstvz'; $consonants = 'bdghjmnpqrstvz';
if ($strength & 1) { if ($strength & 1) {
@ -16,7 +18,7 @@ class Security {
if ($strength & 8) { if ($strength & 8) {
$consonants .= '@#$%'; $consonants .= '@#$%';
} }
$password = ''; $password = '';
$alt = time() % 2; $alt = time() % 2;
for ($i = 0; $i < $length; $i++) { for ($i = 0; $i < $length; $i++) {

View file

@ -1,8 +1,10 @@
<?php <?php
class Session namespace ctiso;
class Session
{ {
function get($key) function get(string $key): mixed
{ {
if (isset($_SESSION[$key])) { if (isset($_SESSION[$key])) {
return $_SESSION[$key]; return $_SESSION[$key];
@ -10,26 +12,27 @@ class Session
return null; return null;
} }
function set($key, $value) function set(string|array $key, mixed $value): void
{ {
if (is_array($key)) { if (is_array($key)) {
$_SESSION[strtolower(get_class($key[0]))][$key[1]] = $value; $className = get_class($key[0]);
$_SESSION[strtolower($className ?: '')][$key[1]] = $value;
} else { } else {
$_SESSION[$key] = $value; $_SESSION[$key] = $value;
} }
} }
function clean($key) function clean(string $key): void
{ {
unset($_SESSION[$key]); unset($_SESSION[$key]);
} }
function start() function start(): void
{ {
@session_start(); @session_start();
} }
function stop() function stop(): void
{ {
session_destroy(); session_destroy();
} }

View file

@ -1,25 +1,40 @@
<?php <?php
namespace ctiso;
use ctiso\File,
Exception;
/** /**
* Класс реестра * Класс реестра
* Реестр организован как ассоциативный многомерный массив * Реестр организован как ассоциативный многомерный массив
* array( 'name1' => parameters1, 'name2' => parameters1, ... ) * array( 'name1' => parameters1, 'name2' => parameters1, ... )
* *
* name1, name2 ... - Имена модулей * name1, name2 ... - Имена модулей
* parameters1, parameters1 - Массивы с параметрами модуля * parameters1, parameters1 - Массивы с параметрами модуля
* Имя необходимо чтобы потом легко было удалить ненужные ветки дерева * Имя необходимо чтобы потом легко было удалить ненужные ветки дерева
*/ */
class Settings extends Collection class Settings
{ {
/** @var array */
public $data = [];
/** @var string */
protected $file; protected $file;
/** @var string */
protected $format = 'php'; protected $format = 'php';
/** @var bool */
protected $is_read = false;
/**
* Конструктор
* @param string $file Путь к файлу
* @param 'php'|'json'|false $format Формат файла
*/
public function __construct ($file = null, $format = false) public function __construct ($file = null, $format = false)
{ {
$fileFormat = ['theme' => 'json']; $fileFormat = ['theme' => 'json'];
$extname = pathinfo($file, PATHINFO_EXTENSION); $extname = pathinfo($file, PATHINFO_EXTENSION);
$this->format = $format ? $format : Arr::get($fileFormat, $extname, $extname); $this->format = $format ?: Arr::get($fileFormat, $extname, $extname);
$this->file = $file; $this->file = $file;
} }
@ -27,43 +42,48 @@ class Settings extends Collection
* Чтение настроек из файла * Чтение настроек из файла
* @return Boolean * @return Boolean
*/ */
public function read() public function read(): bool
{ {
if (!file_exists ($this->file)) { if (!file_exists ($this->file)) {
$this->is_read = true;
return false; return false;
} }
// Не include_once т.к читать настройки можно несколько раз // Не include_once т.к читать настройки можно несколько раз
$settings = array(); $settings = [];
if ($this->format == 'json') { if ($this->format == 'json') {
$settings = json_decode(File::getContents($this->file), true); $settings = json_decode(File::getContents($this->file), true);
} else { } else {
include ($this->file); $settings = include ($this->file);
} }
if (!is_array($settings)) { if (!is_array($settings)) {
throw new Exception($this->file); throw new Exception('no data in ' . $this->file);
} }
return $this->import($settings);
$this->is_read = true;
$this->data = $settings;
return true;
} }
/** /**
* Запись ключа в реестр (Реестр это многомерный массив) * Запись ключа в реестр (Реестр это многомерный массив)
* @param array $key Путь к значению ключа
* @param mixed $value Значение
* @return void
*/ */
public function writeKey(array $key, $value) public function writeKey(array $key, $value)
{ {
// assert(count($key) >= 1);
$data = &$this->data; $data = &$this->data;
while (count($key) > 1) { while (count($key) > 1) {
$name = array_shift($key); $name = array_shift($key);
$data = &$data[$name]; $data = &$data[$name];
} }
// assert(count($key) == 1);
$name = array_shift($key); $name = array_shift($key);
if (is_array($value)) { if (is_array($value)) {
if (!isset($data[$name])) { if (!isset($data[$name])) {
$data[$name] = array(); $data[$name] = [];
} }
$this->merge($data[$name], $value); $this->merge($data[$name], $value);
} else { } else {
@ -73,12 +93,14 @@ class Settings extends Collection
/** /**
* Обновляет массив в соответствии со значением * Обновляет массив в соответствии со значением
* @param array $data Массив
* @param mixed $value Значение
*/ */
protected function merge(array &$data, $value) protected function merge(array &$data, $value): void
{ {
foreach ($value as $key => $subvalue) { foreach ($value as $key => $subvalue) {
if (is_array($subvalue)) { if (is_array($subvalue)) {
if (! isset($data[$key])) $data[$key] = array(); if (! isset($data[$key])) $data[$key] = [];
$this->merge($data[$key], $subvalue); $this->merge($data[$key], $subvalue);
} else { } else {
$data[$key] = $subvalue; $data[$key] = $subvalue;
@ -87,17 +109,23 @@ class Settings extends Collection
} }
/** /**
* Чтение ключа из реестра * Чтение ключа из реестра
* @param $args Путь к значению ключа * @param array $key Путь к значению ключа
* @return mixed
*/ */
public function readKey(array $key) public function readKey(array $key)
{ {
return $this->readKeyData($key, $this->data); return $this->readKeyData($key, $this->data);
} }
/**
* Чтение ключа из реестра
* @param array $key Путь к значению ключа
* @param array $data
* @return mixed
*/
protected function readKeyData(array $key, $data) protected function readKeyData(array $key, $data)
{ {
// assert(count($key) >= 1);
while (count($key) > 1) { while (count($key) > 1) {
$name = array_shift($key); $name = array_shift($key);
if (isset($data[$name])) { if (isset($data[$name])) {
@ -105,9 +133,8 @@ class Settings extends Collection
} else { } else {
return null; return null;
} }
} }
// assert(count($key) == 1);
$name = array_shift($key); $name = array_shift($key);
if (isset($data[$name])) { if (isset($data[$name])) {
return $data[$name]; return $data[$name];
@ -118,12 +145,12 @@ class Settings extends Collection
/** /**
* Чтение ключа из реестра (Собирает все ключи с определенным значением во всех модулях) * Чтение ключа из реестра (Собирает все ключи с определенным значением во всех модулях)
* @param $key Путь к значению ключа внутри модуля * @param mixed $key Путь к значению ключа внутри модуля
* @return array
*/ */
public function readKeyList($_rest) public function readKeyList(...$key)
{ {
$key = func_get_args(); $result = [];
$result = array();
foreach ($this->data as $name => $value) { foreach ($this->data as $name => $value) {
$output = $this->readKeyData($key, $value); $output = $this->readKeyData($key, $value);
if ($output) { if ($output) {
@ -133,13 +160,17 @@ class Settings extends Collection
return $result; return $result;
} }
public function removeKey($name) /**
* Удаление ключа из реестра
* @param string $name Имя ключа
*/
public function removeKey($name): void
{ {
unset($this->data[$name]); unset($this->data[$name]);
} }
public function removeNode(array $key) public function removeNode(array $key): void
{ {
$data = &$this->data; $data = &$this->data;
while (count($key) > 1) { while (count($key) > 1) {
@ -150,26 +181,80 @@ class Settings extends Collection
unset($data[$name]); unset($data[$name]);
} }
public function getOwner()
{
return array_keys($this->data);
}
/** /**
* Запись настроек в файл (Может переименовать в store) * Запись настроек в файл (Может переименовать в store)
* *
* @param File $file * @param string $file
*
* @return void * @return void
*/ */
public function write($file = null) public function write($file = null)
{ {
if (!$this->is_read) {
throw new Exception('read settings before write');
}
if ($this->format == 'json') { if ($this->format == 'json') {
$result = json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); $result = json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} else { } else {
$result = var_export($this->data, true); $result = var_export($this->data, true);
$result = "<?php\n\$settings = ".$result.";\n?>"; $result = "<?php\nreturn ".$result.";\n?>";
} }
file_put_contents (($file) ? $file : $this->file, $result); file_put_contents(($file) ? $file : $this->file, $result);
}
/**
* Установка значения ключа
* @param string $key Ключ
* @param mixed $value Значение
*/
public function set($key, $value): void {
$this->data[$key] = $value;
}
/**
* Получение значения ключа
* @param string $key Ключ
* @param mixed $default Значение по умолчанию
* @return mixed
*/
public function get($key, $default = null)
{
return isset($this->data[$key]) && $this->data[$key] != '' ? $this->data[$key] : $default;
}
/**
* Получение всех данных
* @return array
*/
function export() {
return $this->data;
}
/**
* Импорт данных
* @param array $data Данные
* @return void
*/
function import($data) {
$this->data = $data;
}
/**
* Список модулей/ключей
* @return array
*/
public function getKeys()
{
return array_keys($this->data);
}
/**
* Проверка наличия ключа
* @param string $name Ключ
* @return bool
*/
public function hasKey($name)
{
return isset($this->data[$name]);
} }
} }

View file

@ -6,58 +6,104 @@
* $setup->set('target', 'dst'); * $setup->set('target', 'dst');
* $setup->executeActions('install'); * $setup->executeActions('install');
* </code> * </code>
*/ */
class Setup namespace ctiso;
{ use ctiso\Tools\SQLStatementExtractor;
protected $actions = array(); use ctiso\Path;
public $context = array(); use ctiso\File;
protected $file; use SimpleXMLElement;
protected $action;
protected $node;
protected $stack = array();
public $zip;
public $target; class FakeZipArchive {
public $source; /** @var string */
public $base;
public function __construct($file) function open(string $path): void {
{ $this->base = $path;
$this->file = $file;
$this->node = simplexml_load_file($file);
$this->target = '';
$this->source = '';
$this->zip = new ZipArchive();
$this->zip->open(strtr($file, array('.xml' => '.zip')));
array_push($this->stack, $this->node);
$this->registerAction('copy', array($this, 'copyFile'));
$this->registerAction('make-directory', array($this, 'makeDirectory'));
$this->registerAction('make-link', array($this, 'makeLink'));
$this->registerAction('include', array($this, 'includeFile'));
$this->registerAction('when', array($this, 'testWhen'));
} }
/** /**
* Регистрация новых действия для установки * Возвращает содержимое файла
* @param string $file
* @return string
*/ */
public function registerAction($name, $action) function getFromName($file) {
return File::getContents(Path::join($this->base, $file));
}
}
class Setup
{
/** @var array */
protected $actions = [];
/** @var array */
public $context = [];
/** @var string */
protected $file;
/** @var string */
protected $action;
/** @var SimpleXMLElement */
protected $node;
/** @var array */
protected $stack = [];
/** @var FakeZipArchive */
public $zip;
/** @var string */
public $target;
/** @var string */
public $source;
/**
* @param string $file
*/
public function __construct($file)
{
$this->file = $file;
$node = simplexml_load_file($file);
if ($node === false) {
throw new \RuntimeException("Can't load file $file");
}
$this->node = $node;
$this->target = '';
$this->source = '';
$this->zip = new FakeZipArchive();
$this->zip->open(dirname($this->file) . '/' . $this->node->attributes()['dir']);
array_push($this->stack, $this->node);
$this->registerAction('copy', [$this, 'copyFile']);
$this->registerAction('make-directory', [$this, 'makeDirectory']);
$this->registerAction('make-link', [$this, 'makeLink']);
$this->registerAction('include', [$this, 'includeFile']);
$this->registerAction('when', [$this, 'testWhen']);
}
/**
* Регистрация новых действия для установки
* @param string $name
* @param callable $action
*/
public function registerAction(string $name, $action): void
{ {
$this->actions[$name] = $action; $this->actions[$name] = $action;
} }
/** /**
* Установка переменных для шаблона * Установка переменных для шаблона
* @param string $name
* @param mixed $value
*/ */
public function set($name, $value) public function set(string $name, $value): void
{ {
$this->context[$name] = $value; $this->context[$name] = $value;
} }
function replaceFn($matches) { /**
* @return string
*/
function replaceFn(array $matches) {
if (isset($this->context[$matches[2]])) { if (isset($this->context[$matches[2]])) {
$v = $this->context[$matches[2]]; $v = $this->context[$matches[2]];
} else { } else {
@ -70,14 +116,14 @@ class Setup
return $v; return $v;
} }
public function fileContent($file, array $tpl) public function fileContent(string $file, array $tpl): string
{ {
$result = $this->zip->getFromName($file); $result = $this->zip->getFromName($file);
$result = preg_replace_callback('/\{\{\s*(\*?)(\w+)\s*\}\}/', array($this, 'replaceFn'), $result); $result = preg_replace_callback('/\{\{\s*(\*?)(\w+)\s*\}\}/', [$this, 'replaceFn'], $result);
return $result; return $result;
} }
function callAction($name, array $attributes) function callAction(string $name, array $attributes): void
{ {
if(isset($this->actions[$name])) { if(isset($this->actions[$name])) {
call_user_func_array($this->actions[$name], $attributes); call_user_func_array($this->actions[$name], $attributes);
@ -86,6 +132,8 @@ class Setup
/** /**
* Заменяет переменные на их значения в строке * Заменяет переменные на их значения в строке
* @param array<string> $match массив совпадения
* @return string
*/ */
function replaceVariable(array $match) function replaceVariable(array $match)
{ {
@ -97,48 +145,55 @@ class Setup
/** /**
* Для всех аттрибутов заменяет переменные на их значения * Для всех аттрибутов заменяет переменные на их значения
* @param SimpleXMLElement $attributes аттрибуты
* @return array<string, string>
*/ */
function resolve($attributes) function resolve(SimpleXMLElement $attributes): array
{ {
$result = array(); $result = [];
foreach ($attributes as $key => $value) { foreach ($attributes as $key => $value) {
$result [$key] = preg_replace_callback("/\\\${(\w+)}/", array($this, 'replaceVariable'), $value); $result[$key] = preg_replace_callback("/\\\${(\w+)}/", $this->replaceVariable(...), $value);
} }
return $result; return $result;
} }
/** /**
* Выполняет список действий если для действия не указан аттрибут when то оно выполняется всегда * Выполняет список действий если для действия не указан аттрибут when то оно выполняется всегда
* *
* @param $action специальное действие * @param string $action специальное действие
* @return void
*/ */
function executeActions($action = "install") function executeActions($action = "install")
{ {
$this->action = $action; $this->action = $action;
if ($this->stack[count($this->stack) - 1] === false) { if ($this->stack[count($this->stack) - 1] === false) {
return; return;
} }
$item/*: SimpleXMLElement*/ = $this->stack[count($this->stack) - 1]; /** @var \SimpleXMLElement */
$item = $this->stack[count($this->stack) - 1];
$root = $item->children(); $root = $item->children();
foreach ($root as $node) foreach ($root as $node)
{ {
$attributes = $node->attributes(); $attributes = $node->attributes();
array_push($this->stack, $node); array_push($this->stack, $node);
$this->callAction($node->getName(), array($this->resolve($attributes))); $this->callAction($node->getName(), [$this->resolve($attributes)]);
array_pop($this->stack); array_pop($this->stack);
} }
} }
/** /**
* Копирования файла * Копирования файла
* @param preserve Не переписывать файл если он существует * preserve - Не переписывать файл если он существует
* @param template Файл является шаблоном подставить параметры до копирования * template Файл является шаблоном подставить параметры до копирования
* @param src Исходный файл * src Исходный файл
* @param dst Новый файл * dst Новый файл
*
* @param array{preserve?: string, template: string, src: string, dst: string} $attributes
* @return void
*/ */
public function copyFile(array $attributes) public function copyFile(array $attributes)
{ {
$path = $this->targetPath($attributes['dst']); $path = $this->targetPath($attributes['dst']);
if (!(file_exists($path) && isset($attributes['preserve']))) { if (!(file_exists($path) && isset($attributes['preserve']))) {
@ -148,9 +203,9 @@ class Setup
/** /**
* Создает символическую ссылку на папку/файл * Создает символическую ссылку на папку/файл
* @param dst Имя папки * @param array{target: string, link: string} $attributes
*/ */
public function makeLink(array $attributes) public function makeLink(array $attributes): void
{ {
if (function_exists('symlink')) { if (function_exists('symlink')) {
symlink($attributes['target'], $attributes['link']); symlink($attributes['target'], $attributes['link']);
@ -158,35 +213,37 @@ class Setup
} }
/** /**
* Подключение файла установки * Подключение файла установки
* @param file Имя подключаемого файла * @param array{file: string} $attributes Имя подключаемого файла
*/ */
public function includeFile(array $attributes) public function includeFile(array $attributes): void
{ {
$file = basename($this->file) . "/" . $attributes['file']; $file = basename($this->file) . "/" . $attributes['file'];
$setup = new Setup($file); $setup = new Setup($file);
$setup->context = $this->context; $setup->context = $this->context;
$setup->executeActions(); $setup->executeActions();
} }
function targetPath($s) { function targetPath(string $s): string {
return $this->target . '/' . $s; return $this->target . '/' . $s;
} }
/** /**
* Создает новую папку * Создает новую папку
* @param dst Имя папки * dst Имя папки
*
* @param array{dst:string} $attributes
*/ */
public function makeDirectory(array $attributes) public function makeDirectory(array $attributes): void
{ {
$path = $this->targetPath($attributes['dst']); $path = $this->targetPath($attributes['dst']);
if (!file_exists($path)) { if (!file_exists($path)) {
mkdir($path); mkdir($path);
} }
} }
function testWhen(array $attributes) function testWhen(array $attributes): void
{ {
if (!isset($attributes['test']) || $attributes['test'] == $this->action) { if (!isset($attributes['test']) || $attributes['test'] == $this->action) {
$this->executeActions($this->action); $this->executeActions($this->action);
@ -194,19 +251,26 @@ class Setup
} }
/** /**
* Выполнение Списка SQL команд * Выполнение Списка SQL команд из ZIP файла
*/ * @param Database $conn
function batchSQLZip($conn/*: Database*/, $file) * @param string $file
*/
function batchSQLZip($conn, $file): void
{ {
$stmtList = Tools_SQLStatementExtractor::extract($this->zip->getFromName($file)); $stmtList = SQLStatementExtractor::extract($this->zip->getFromName($file));
foreach ($stmtList as $stmt) { foreach ($stmtList as $stmt) {
$conn->executeQuery ($stmt); $conn->executeQuery ($stmt);
} }
} }
static function batchSQL($conn/*: Database*/, $file) /**
* Выполнение Списка SQL команд
* @param Database $conn
* @param string $file
*/
static function batchSQL($conn, $file): void
{ {
$stmtList = Tools_SQLStatementExtractor::extractFile($file); $stmtList = SQLStatementExtractor::extractFile($file);
foreach ($stmtList as $stmt) { foreach ($stmtList as $stmt) {
$conn->executeQuery ($stmt); $conn->executeQuery ($stmt);
} }

View file

@ -1,66 +0,0 @@
<?php
/**
* Класс для короткого доступа к файлам / папкам
*/
class Shortcut
{
static $instance = null;
public $variables = array();
public $list = array();
// Singleton pattern
static public function getInstance()
{
if (self::$instance == null) {
self::$instance = new Shortcut();
}
return self::$instance;
}
/**
* Добавляет ярлык с именем $prefix
* Путь может содержать переменные
*/
public function addUrl($prefix, $path)
{
$this->list[$prefix] = $path;
}
/**
*
*/
public function addVar($name, $value)
{
$this->variables['$' . $name] = $value;
}
/**
* Возвращает путь по имени ярлыка
*/
static function getUrl($prefix, $name = null, $name1 = null)
{
$shortcut = self::getInstance();
$names = $shortcut->variables;
if ($name) {
$names['$name'] = $name;
}
if ($name1) {
$names['$name1'] = $name1;
}
if (isset($shortcut->list[$prefix])) {
return strtr($shortcut->list[$prefix], $names);
}
return null;
}
static function expand($path)
{
$shortcut = self::getInstance();
$names = $shortcut->variables;
return strtr($path, $names);
}
}

View file

@ -1,19 +1,24 @@
<?php <?php
class SortRecord namespace ctiso;
{
public $key;
public $mode;
public $order;
function __construct($key, $mode, $order) class SortRecord
{ {
public string $key;
public int $order;
function __construct(string $key, bool $order)
{
$this->key = $key; $this->key = $key;
$this->order = ((boolean)($order) === false) ? 1 : -1; $this->order = ((boolean)($order) === false) ? 1 : -1;
$this->mode = $mode;
} }
function compare($a, $b) /**
* @template T
* @param array<string, T> $a
* @param array<string, T> $b
*/
function compare(array $a, array $b): int
{ {
if($a[$this->key] == $b[$this->key]) { if($a[$this->key] == $b[$this->key]) {
return 0; return 0;
@ -21,7 +26,7 @@ class SortRecord
return ($a[$this->key] > $b[$this->key]) ? $this->order : -$this->order; return ($a[$this->key] > $b[$this->key]) ? $this->order : -$this->order;
} }
function compareKeys($a, $b) function compareKeys(object $a, object $b): int
{ {
if($a->{$this->key} == $b->{$this->key}) { if($a->{$this->key} == $b->{$this->key}) {
return 0; return 0;
@ -29,23 +34,23 @@ class SortRecord
return ($a->{$this->key} > $b->{$this->key}) ? $this->order : -$this->order; return ($a->{$this->key} > $b->{$this->key}) ? $this->order : -$this->order;
} }
function sort(&$list) function sort(array &$list): bool
{ {
return usort($list, array($this, 'compare')); return usort($list, [$this, 'compare']);
} }
function sortKeys(&$list) function sortKeys(array &$list): bool
{ {
return usort($list, array($this, 'compareKeys')); return usort($list, [$this, 'compareKeys']);
} }
function group(&$list, $key, $types) function group(array &$list, string $key, array $types): void
{ {
$groups = array(); $groups = [];
foreach ($types as $name) { foreach ($types as $name) {
$groups[$name] = array(); $groups[$name] = [];
} }
$groups['_any_'] = array(); $groups['_any_'] = [];
foreach ($list as $item) { foreach ($list as $item) {
if (isset($groups[$item[$key]])) { if (isset($groups[$item[$key]])) {
$groups[$item[$key]][] = $item; $groups[$item[$key]][] = $item;
@ -53,10 +58,10 @@ class SortRecord
$groups['_any_'][] = $item; $groups['_any_'][] = $item;
} }
} }
$result = array(); $result = [];
foreach ($groups as $value) { foreach ($groups as $value) {
$result = array_merge($result, $value); $result = array_merge($result, $value);
} }
$list = $result; $list = $result;
} }
} }

40
src/TableTree.php Normal file
View file

@ -0,0 +1,40 @@
<?php
/**
* Преобразование дерева из модели Plain в массив массивов (Adjacency List)
*/
namespace ctiso;
use ctiso\Functions;
class TableTree {
/**
* Обходит таблицу как дерево
* $fn ($name, $index, $rows, $cc)
* $name Ключ уровня
* $index Значение ключа уровня
* $rows Все столбцы текущго уровня
* $cc Столбцы более низкого уровня
*
* @param array $level Уровни вложенности
* @param array $table Таблица
* @param callable $fn Функция которая применяется к каждой ветке дерева
* @return array
*/
static function walk($level, $table, $fn) {
if (empty ($level)) {
return $table;
}
$name = array_shift ($level);
$keys = Functions::key_unique_values($name, $table);
$data = [];
foreach ($keys as $index) {
list($rows, $table) = Functions::partition (Functions::lcurry(['\ctiso\Functions', '__index'], $index, $name), $table);
// $rows = array_filter ($table, lcurry('__index', intval($index), $name));
//$rows = array_filter ($table, create_function ('$x', 'return __index ('.intval($index).', \''.$name.'\', $x);'));
$data[$index] = call_user_func ($fn, $name, $index, $rows, self::walk ($level, $rows, $fn));
}
return $data;
}
}

90
src/Tales.php Normal file
View file

@ -0,0 +1,90 @@
<?php
/**
* Расширения для PHPTAL для отображения времени и даты
*/
namespace ctiso;
use PHPTAL_Php_TalesInternal;
use ctiso\Controller\SiteInterface;
use ctiso\Controller\Component;
use ctiso\HttpRequest;
use PHPTAL_Tales;
use PHPTAL_TalesRegistry;
class Tales_DateTime implements PHPTAL_Tales
{
static public function date(string $expression, bool $nothrow = false): string
{
return "ctiso\\Tales::phptal_date(".PHPTAL_Php_TalesInternal::path($expression).")";
}
static public function time(string $expression, bool $nothrow = false): string
{
return "ctiso\\Tales::phptal_time(".PHPTAL_Php_TalesInternal::path($expression).")";
}
}
class Tales_Component implements PHPTAL_Tales
{
static public function component(string $expression, bool $nothrow = false): string
{
$s = PHPTAL_Php_TalesInternal::string($expression);
return "ctiso\\Tales::phptal_component(" . $s . ")";
}
}
class Tales_Assets implements PHPTAL_Tales
{
static public function assets(string $expression, bool $nothrow = false): string
{
$s = PHPTAL_Php_TalesInternal::string($expression);
return "ctiso\\Tales::phptal_asset(" . $s . ")";
}
}
class Tales {
/** @var ?SiteInterface */
static $site;
static function phptal_date (int $e): string {
return date("d.m.Y", $e);
}
static function phptal_time (int $e): string {
return date("H:i", $e);
}
static function phptal_asset(string $s): string {
self::$site->addStyleSheet($s);
return "";
}
/**
* Функция подключения компонента
* @param string $expression
* @return string
*/
static function phptal_component($expression): string {
$begin = floatval(microtime(true));
/** @var Component */
$component = self::$site->loadComponent($expression);
$req = new HttpRequest();
$result = $component->execute($req);
echo "<!-- ", $expression, ", ", round(floatval(microtime(true)) - $begin, 4), "sec -->";
return $result;
}
static function register(?SiteInterface $site): void {
self::$site = $site;
/* Регистрация нового префикса для подключения компонента */
$tales = PHPTAL_TalesRegistry::getInstance();
$tales->registerPrefix('component', [\ctiso\Tales_Component::class, 'component']);
$tales->registerPrefix('date', [\ctiso\Tales_DateTime::class, 'date']);
$tales->registerPrefix('time', [\ctiso\Tales_DateTime::class, 'time']);
$tales->registerPrefix('assets', [\ctiso\Tales_Assets::class, 'assets']);
}
}

View file

@ -1,86 +1,130 @@
<?php <?php
class Tools_Drawing namespace ctiso\Tools;
use GdImage;
class Drawing
{ {
const ALIGN_LEFT = "left"; const ALIGN_LEFT = "left";
const ALIGN_TOP = "top"; const ALIGN_TOP = "top";
const ALIGN_BOTTOM = "bottom"; const ALIGN_BOTTOM = "bottom";
const ALIGN_CENTER = "center"; const ALIGN_CENTER = "center";
const ALIGN_RIGHT = "right"; const ALIGN_RIGHT = "right";
static function drawrectnagle(&$image, $left, $top, $width, $height, $rgb) /**
* @param GdImage $image
* @param int $left
* @param int $top
* @param int $width
* @param int $height
* @param list<int<0, 255>> $rgb
*/
static function drawRectangle(GdImage &$image, int $left, int $top, int $width, int $height, array $rgb): void
{ {
$color = imagecolorallocate($image, $rgb[0], $rgb[1], $rgb[2]); $color = imagecolorallocate($image, $rgb[0], $rgb[1], $rgb[2]);
if ($color === false) {
throw new \RuntimeException("Can't allocate color");
}
$right = $left + $width; $right = $left + $width;
$bottom = $top + $height; $bottom = $top + $height;
imageline($image, $left, $top, $right, $top, $color); imageline($image, $left, $top, $right, $top, $color);
imageline($image, $right,$top, $right, $bottom, $color); imageline($image, $right, $top, $right, $bottom, $color);
imageline($image, $left, $bottom, $right, $bottom, $color); imageline($image, $left, $bottom, $right, $bottom, $color);
imageline($image, $left, $top, $left, $bottom, $color); imageline($image, $left, $top, $left, $bottom, $color);
} }
/** /**
* http://ru2.php.net/imagettftext * http://ru2.php.net/imagettftext
*
* @param GdImage $image
* @param int $size
* @param float $angle
* @param int $left
* @param int $top
* @param int $color
* @param string $font
* @param string $text
* @param int $max_width
* @param int $max_height
* @param string $align
* @param string $valign
*/ */
static function imagettftextbox(&$image, $size, $angle, $left, $top, $color, $font, $text, static function imagettftextbox(
$max_width, $max_height, $align, $valign) GdImage &$image,
{ int $size,
// echo $left,"\n", $top, "\n"; float $angle,
// echo $max_width,"\n", $max_height, "\n"; $left,
// self::drawrectnagle($image, $left, $top, $max_width, $max_height, array(0xFF,0,0)); $top,
$color,
$font,
$text,
$max_width,
$max_height,
$align,
$valign
): float {
// echo $left,"\n", $top, "\n";
// echo $max_width,"\n", $max_height, "\n";
// self::drawrectnagle($image, $left, $top, $max_width, $max_height, array(0xFF,0,0));
$text_lines = explode("\n", $text); // Supports manual line breaks! $text_lines = explode("\n", $text); // Supports manual line breaks!
$lines = array(); $lines = [];
$line_widths = array(); $line_widths = [];
$largest_line_height = 0; $largest_line_height = 0;
foreach ($text_lines as $block) { foreach ($text_lines as $block) {
$current_line = ''; // Reset current line $current_line = ''; // Reset current line
$words = explode(' ', $block); // Split the text into an array of single words $words = explode(' ', $block); // Split the text into an array of single words
$first_word = true; $first_word = true;
$last_width = 0; $last_width = 0;
for ($i = 0; $i < count($words); $i++) { $count = count($words);
$item = '';
for ($i = 0; $i < $count; $i++) {
$item = $words[$i]; $item = $words[$i];
$dimensions = imagettfbbox($size, $angle, $font, $current_line . ($first_word ? '' : ' ') . $item); $dimensions = imagettfbbox($size, $angle, $font, $current_line . ($first_word ? '' : ' ') . $item);
if ($dimensions === false) {
continue;
}
$line_width = $dimensions[2] - $dimensions[0]; $line_width = $dimensions[2] - $dimensions[0];
$line_height = $dimensions[1] - $dimensions[7]; $line_height = $dimensions[1] - $dimensions[7];
$largest_line_height = max($line_height, $largest_line_height); $largest_line_height = max($line_height, $largest_line_height);
if ($line_width > $max_width && !$first_word) { if ($line_width > $max_width && !$first_word) {
$lines[] = $current_line; $lines[] = $current_line;
$line_widths[] = $last_width ? $last_width : $line_width; $line_widths[] = $last_width ?: $line_width;
$current_line = $item; $current_line = $item;
} else { } else {
$current_line .= ($first_word ? '' : ' ') . $item; $current_line .= ($first_word ? '' : ' ') . $item;
} }
if ($i == count($words) - 1) { if ($i == count($words) - 1) {
$lines[] = $current_line; $lines[] = $current_line;
$line_widths[] = $line_width; $line_widths[] = $line_width;
} }
$last_width = $line_width; $last_width = $line_width;
$first_word = false; $first_word = false;
} }
if ($current_line) { if ($current_line) {
$current_line = $item; $current_line = $item;
} }
} }
// vertical align // vertical align
$top_offset = 0; $top_offset = 0;
if ($valign == self::ALIGN_CENTER) { if ($valign == self::ALIGN_CENTER) {
$top_offset = ($max_height - $largest_line_height * count($lines)) / 2; $top_offset = ($max_height - $largest_line_height * count($lines)) / 2;
} elseif ($valign == self::ALIGN_BOTTOM) { } elseif ($valign == self::ALIGN_BOTTOM) {
$top_offset = $max_height - $largest_line_height * count($lines); $top_offset = $max_height - $largest_line_height * count($lines);
} }
$top += $largest_line_height + $top_offset; $top += $largest_line_height + $top_offset;
$i = 0; $i = 0;
foreach ($lines as $line) { foreach ($lines as $line) {
// horizontal align // horizontal align
@ -90,28 +134,23 @@ class Tools_Drawing
} elseif ($align == self::ALIGN_RIGHT) { } elseif ($align == self::ALIGN_RIGHT) {
$left_offset = ($max_width - $line_widths[$i]); $left_offset = ($max_width - $line_widths[$i]);
} }
imagettftext($image, $size, $angle, $left + $left_offset, $top + ($largest_line_height * $i), $color, $font, $line); imagettftext($image, $size, $angle, $left + $left_offset, $top + ($largest_line_height * $i), $color, $font, $line);
$i++; $i++;
} }
return $largest_line_height * count($lines); return $largest_line_height * count($lines);
} }
function imagettftextSp(GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $font, string $text, int $spacing = 0): void
function imagettftextSp($image, $size, $angle, $x, $y, $color, $font, $text, $spacing = 0) {
{ if ($spacing == 0) {
if ($spacing == 0)
{
imagettftext($image, $size, $angle, $x, $y, $color, $font, $text); imagettftext($image, $size, $angle, $x, $y, $color, $font, $text);
} } else {
else
{
$temp_x = $x; $temp_x = $x;
for ($i = 0; $i < mb_strlen($text); $i++) for ($i = 0; $i < mb_strlen($text); $i++) {
{
$bbox = imagettftext($image, $size, $angle, $temp_x, $y, $color, $font, $text[$i]); $bbox = imagettftext($image, $size, $angle, $temp_x, $y, $color, $font, $text[$i]);
$temp_x += $spacing + ($bbox[2] - $bbox[0]); $temp_x += $spacing + ($bbox !== false ? ($bbox[2] - $bbox[0]) : 0);
} }
} }
} }

View file

@ -1,8 +1,16 @@
<?php <?php
class Tools_Image namespace ctiso\Tools;
use GdImage;
class Image
{ {
static function load($uri) /**
* @param string $uri
* @return GdImage|false
*/
static function load($uri): GdImage|false
{ {
$e = strtolower(pathinfo($uri, PATHINFO_EXTENSION)); $e = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
switch ($e) { switch ($e) {
@ -10,24 +18,32 @@ class Tools_Image
case 'jpeg': case 'jpg': return imagecreatefromjpeg($uri); case 'jpeg': case 'jpg': return imagecreatefromjpeg($uri);
case 'gif': return imagecreatefromgif($uri); case 'gif': return imagecreatefromgif($uri);
} }
return false;
} }
static function fit($image, $prewidth, $preheight, $force = true) static function fit(GdImage $image, int $prewidth, int $preheight, bool $force = true): GdImage|false
{ {
$width = imagesx($image); $width = imagesx($image);
$height = imagesy($image); $height = imagesy($image);
$percent = min($prewidth / $width, $preheight / $height);
if ($percent > 1 && !$force) $percent = 1;
$new_width = $width * $percent;
$new_height = $height * $percent;
$percent = min($prewidth / $width, $preheight / $height);
if ($percent > 1 && !$force) {
$percent = 1;
}
$new_width = max(1, (int)($width * $percent));
$new_height = max(1, (int)($height * $percent));
$image_p = imagecreatetruecolor($new_width, $new_height); $image_p = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
return $image_p; return $image_p;
} }
static function save($image, $uri) /**
* @param GdImage $image
* @param string $uri
* @return bool
*/
static function save($image, $uri): bool
{ {
$e = strtolower(pathinfo($uri, PATHINFO_EXTENSION)); $e = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
switch ($e) { switch ($e) {
@ -35,5 +51,6 @@ class Tools_Image
case 'png': imagepng($image, $uri); break; case 'png': imagepng($image, $uri); break;
case 'gif': imagegif($image, $uri); break; case 'gif': imagegif($image, $uri); break;
} }
return false;
} }
} }

View file

@ -18,7 +18,7 @@
* and is licensed under the LGPL. For more information please see * and is licensed under the LGPL. For more information please see
* <http://creole.phpdb.org>. * <http://creole.phpdb.org>.
*/ */
/** /**
* Static class for extracting SQL statements from a string or file. * Static class for extracting SQL statements from a string or file.
* *
@ -26,139 +26,157 @@
* @version $Revision: 1.5 $ * @version $Revision: 1.5 $
* @package creole.util.sql * @package creole.util.sql
*/ */
class Tools_SQLStatementExtractor {
namespace ctiso\Tools;
use Exception;
class SQLStatementExtractor
{
/** @var string */
protected static $delimiter = ';'; protected static $delimiter = ';';
/** /**
* Get SQL statements from file. * Get SQL statements from file.
* *
* @param string $filename Path to file to read. * @param string $filename Path to file to read.
* @return array SQL statements * @return array SQL statements
*/ */
public static function extractFile($filename) { public static function extractFile($filename)
{
$buffer = file_get_contents($filename); $buffer = file_get_contents($filename);
if ($buffer !== false) { if ($buffer !== false) {
return self::extractStatements(self::getLines($buffer)); return self::extractStatements(self::getLines($buffer));
} }
throw new Exception("Unable to read file: " . $filename); throw new Exception("Unable to read file: " . $filename);
} }
/** /**
* Extract statements from string. * Extract statements from string.
* *
* @param string $txt * @param string $buffer
* @return array * @return array
*/ */
public static function extract($buffer) { public static function extract($buffer)
{
return self::extractStatements(self::getLines($buffer)); return self::extractStatements(self::getLines($buffer));
} }
/** /**
* Extract SQL statements from array of lines. * Extract SQL statements from array of lines.
* *
* @param array $lines Lines of the read-in file. * @param string[] $lines Lines of the read-in file.
* @return string * @return string[] SQL statements
*/ */
protected static function extractStatements($lines) { protected static function extractStatements($lines)
{
$statements = array();
$statements = [];
$sql = ""; $sql = "";
foreach($lines as $line) {
$line = trim($line);
if (self::startsWith("//", $line) ||
self::startsWith("--", $line) ||
self::startsWith("#", $line)) {
continue;
}
if (strlen($line) > 4 && strtoupper(substr($line,0, 4)) == "REM ") {
continue;
}
$sql .= " " . $line; foreach ($lines as $line) {
$sql = trim($sql); $line = trim($line);
// SQL defines "--" as a comment to EOL if (
// and in Oracle it may contain a hint self::startsWith("//", $line) ||
// so we cannot just remove it, instead we must end it self::startsWith("--", $line) ||
if (strpos($line, "--") !== false) { self::startsWith("#", $line)
$sql .= "\n"; ) {
} continue;
if (self::endsWith(self::$delimiter, $sql)) {
$statements[] = self::substring($sql, 0, strlen($sql)-1 - strlen(self::$delimiter));
$sql = "";
}
} }
return $statements;
if (strlen($line) > 4 && strtoupper(substr($line, 0, 4)) == "REM ") {
continue;
}
$sql .= " " . $line;
$sql = trim($sql);
// SQL defines "--" as a comment to EOL
// and in Oracle it may contain a hint
// so we cannot just remove it, instead we must end it
if (strpos($line, "--") !== false) {
$sql .= "\n";
}
if (self::endsWith(self::$delimiter, $sql)) {
$statements[] = self::substring($sql, 0, strlen($sql) - 1 - strlen(self::$delimiter));
$sql = "";
}
}
return $statements;
} }
// //
// Some string helper methods // Some string helper methods
// //
/** /**
* Tests if a string starts with a given string. * Tests if a string starts with a given string.
* @param string $check The substring to check. * @param string $check The substring to check.
* @param string $string The string to check in (haystack). * @param string $string The string to check in (haystack).
* @return boolean True if $string starts with $check, or they are equal, or $check is empty. * @return boolean True if $string starts with $check, or they are equal, or $check is empty.
*/ */
protected static function startsWith($check, $string) { protected static function startsWith($check, $string)
{
if ($check === "" || $check === $string) { if ($check === "" || $check === $string) {
return true; return true;
} else { } else {
return (strpos($string, $check) === 0); return (strpos($string, $check) === 0);
} }
} }
/** /**
* Tests if a string ends with a given string. * Tests if a string ends with a given string.
* @param string $check The substring to check. * @param string $check The substring to check.
* @param string $string The string to check in (haystack). * @param string $string The string to check in (haystack).
* @return boolean True if $string ends with $check, or they are equal, or $check is empty. * @return boolean True if $string ends with $check, or they are equal, or $check is empty.
*/ */
protected static function endsWith($check/*: string*/, $string) { protected static function endsWith(string $check, string $string)
{
if ($check === "" || $check === $string) { if ($check === "" || $check === $string) {
return true; return true;
} else { } else {
return (strpos(strrev($string), strrev($check)) === 0); return (strpos(strrev($string), strrev($check)) === 0);
} }
} }
/** /**
* a natural way of getting a subtring, php's circular string buffer and strange * a natural way of getting a subtring, php's circular string buffer and strange
* return values suck if you want to program strict as of C or friends * return values suck if you want to program strict as of C or friends
* @param string $string The string to get the substring from.
* @param int $startpos The start position of the substring.
* @param int $endpos The end position of the substring.
* @return string The substring.
*/ */
protected static function substring($string, $startpos, $endpos = -1) { protected static function substring(string $string, int $startpos, int $endpos = -1)
{
$len = strlen($string); $len = strlen($string);
$endpos = (int) (($endpos === -1) ? $len-1 : $endpos); $endpos = (int) (($endpos === -1) ? $len - 1 : $endpos);
if ($startpos > $len-1 || $startpos < 0) { if ($startpos > $len - 1 || $startpos < 0) {
trigger_error("substring(), Startindex out of bounds must be 0<n<$len", E_USER_ERROR); trigger_error("substring(), Startindex out of bounds must be 0<n<$len", E_USER_ERROR);
} }
if ($endpos > $len-1 || $endpos < $startpos) { if ($endpos > $len - 1 || $endpos < $startpos) {
trigger_error("substring(), Endindex out of bounds must be $startpos<n<".($len-1), E_USER_ERROR); trigger_error("substring(), Endindex out of bounds must be $startpos<n<" . ($len - 1), E_USER_ERROR);
} }
if ($startpos === $endpos) { if ($startpos === $endpos) {
return (string) $string[$startpos]; return (string) $string[$startpos];
} else { } else {
$len = $endpos-$startpos; $len = $endpos - $startpos;
} }
return substr($string, $startpos, $len+1); return substr($string, $startpos, $len + 1);
} }
/** /**
* Convert string buffer into array of lines. * Convert string buffer into array of lines.
* *
* @param string $filename * @param string $buffer
* @return array string[] lines of file. * @return string[] lines of file.
*/ */
protected static function getLines($buffer) { protected static function getLines(string $buffer): array
$lines = preg_split("/\r?\n|\r/", $buffer); {
return $lines; $lines = preg_split("/\r?\n|\r/", $buffer);
return $lines === false ? [] : $lines;
} }
}
}

View file

@ -1,110 +0,0 @@
<?php
class Tools_String {
// from creole
static function strToArray($str) {
$str = substr($str, 1, -1); // remove { }
$res = array();
$subarr = array();
$in_subarr = 0;
$toks = explode(',', $str);
foreach($toks as $tok) {
if ($in_subarr > 0) { // already in sub-array?
$subarr[$in_subarr][] = $tok;
if ('}' === substr($tok, -1, 1)) { // check to see if we just added last component
$res[] = static::strToArray(implode(',', $subarr[$in_subarr]));
$in_subarr--;
}
} elseif ($tok[0] === '{') { // we're inside a new sub-array
if ('}' !== substr($tok, -1, 1)) {
$in_subarr++;
// if sub-array has more than one element
$subarr[$in_subarr] = array();
$subarr[$in_subarr][] = $tok;
} else {
$res[] = static::strToArray($tok);
}
} else { // not sub-array
$val = trim($tok, '"'); // remove " (surrounding strings)
// perform type castng here?
$res[] = $val;
}
}
return $res;
}
//Нормализация строк на русском
static function normalizeRussian($str) {
$result = preg_replace('/\s+/',' ', $str);
if (is_string($result)) {
$result = trim($result); //Замена длинных пробелов на одинарные, пробелы по краям
$result = mb_strtolower($result);
$result = preg_replace('/ё/','е', $str); //е на ё
}
return $result;
}
//Проверка равенства двух строк на русском языке.
static function equalRussianCheck($str1,$str2) {
return self::normalizeRussian($str1) == self::normalizeRussian($str2);
}
/**
* Попадает ли строка в список вариантов
* input: $str="foo1" $variants="foo1|foo2|foo3"
* output: true
* input: $str="foo" $variants="foo1|foo2|foo3"
* output: false
*/
static function compare_string_to_variants($str, $variants){
$variants_array = explode('|', $variants);
$founded = false;
foreach ($variants_array as $variant) {
$founded = $founded || self::equalRussianCheck($variant, $str);
}
return $founded;
}
static function mb_str_split($str) {
return preg_split('~~u', $str, null, PREG_SPLIT_NO_EMPTY);
}
static function mb_strtr($str, $from, $to) {
return str_replace(self::mb_str_split($from), self::mb_str_split($to), $str);
}
static function encodestring($st) {
$st = self::mb_strtr($st,"абвгдеёзийклмнопрстуфхъыэ !+()", "abvgdeeziyklmnoprstufh_ie_____");
$st = self::mb_strtr($st,"АБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЭ", "ABVGDEEZIYKLMNOPRSTUFH_IE");
$st = strtr($st, array(
" " => '_',
"." => '_',
"," => '_',
"?" => '_',
"\"" => '_',
"'" => '_',
"/" => '_',
"\\" => '_',
"%" => '_',
"#" => '_',
"*" => '_',
"ж"=>"zh", "ц"=>"ts", "ч"=>"ch", "ш"=>"sh",
"щ"=>"shch","ь"=>"", "ю"=>"yu", "я"=>"ya",
"Ж"=>"ZH", "Ц"=>"TS", "Ч"=>"CH", "Ш"=>"SH",
"Щ"=>"SHCH","Ь"=>"", "Ю"=>"YU", "Я"=>"YA",
"Й"=>"i", "й"=>"ie", "ё"=>"Ye",
""=>"N"
));
return strtolower($st);
}
static function validate_encoded_string($st) {
$enc_st = self::encodestring($st);
return preg_match('/^[\w_-]+(\.[\w_-]+)?$/', $enc_st);
}
}

168
src/Tools/StringUtil.php Normal file
View file

@ -0,0 +1,168 @@
<?php
namespace ctiso\Tools;
class StringUtil
{
/**
* Преобразует строку в массив
* @param string $str
* @return array
*/
static function strToArray(string $str): array
{
$str = substr($str, 1, -1); // remove { }
$res = [];
$subarr = [];
$in_subarr = 0;
$toks = explode(',', $str);
foreach ($toks as $tok) {
if ($in_subarr > 0) { // already in sub-array?
$subarr[$in_subarr][] = $tok;
if ('}' === substr($tok, -1, 1)) { // check to see if we just added last component
$res[] = static::strToArray(implode(',', $subarr[$in_subarr]));
$in_subarr--;
}
} elseif ($tok[0] === '{') { // we're inside a new sub-array
if ('}' !== substr($tok, -1, 1)) {
$in_subarr++;
// if sub-array has more than one element
$subarr[$in_subarr] = [];
$subarr[$in_subarr][] = $tok;
} else {
$res[] = static::strToArray($tok);
}
} else { // not sub-array
$val = trim($tok, '"'); // remove " (surrounding strings)
// perform type castng here?
$res[] = $val;
}
}
return $res;
}
/**
* Нормализация строк на русском
* @param string $str
* @return string
*/
static function normalizeRussian(string $str): string
{
$result = preg_replace('/\s+/', ' ', $str);
if (is_string($result)) {
$result = trim($result); //Замена длинных пробелов на одинарные, пробелы по краям
$result = mb_strtolower($result);
$result = preg_replace('/ё/', 'е', $str); //е на ё
}
return $result;
}
/**
* Проверка равенства двух строк на русском языке.
* @param string $str1
* @param string $str2
* @return bool
*/
static function equalRussianCheck($str1, $str2): bool
{
return self::normalizeRussian($str1) == self::normalizeRussian($str2);
}
/**
* Попадает ли строка в список вариантов
* input: $str="foo1" $variants="foo1|foo2|foo3"
* output: true
* input: $str="foo" $variants="foo1|foo2|foo3"
* output: false
*
* @param string $str
* @param string $variants
* @return bool
*/
static function compare_string_to_variants($str, string $variants)
{
$variants_array = explode('|', $variants);
$founded = false;
foreach ($variants_array as $variant) {
$founded = $founded || self::equalRussianCheck($variant, $str);
}
return $founded;
}
/**
* Разбивает строку на массив символов
* @param string $str
* @return array
*/
static function mb_str_split(string $str): array
{
return preg_split('~~u', $str, -1, PREG_SPLIT_NO_EMPTY) ?: [];
}
/**
* Заменяет символы в строке на символы из другой строки
* @param string $str
* @param string $from
* @param string $to
* @return string
*/
static function mb_strtr($str, $from, $to)
{
return str_replace(self::mb_str_split($from), self::mb_str_split($to), $str);
}
static function encodestring(string $st): string
{
$st = self::mb_strtr($st, "абвгдеёзийклмнопрстуфхъыэ !+()", "abvgdeeziyklmnoprstufh_ie_____");
$st = self::mb_strtr($st, "АБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЭ", "ABVGDEEZIYKLMNOPRSTUFH_IE");
$st = strtr($st, [
" " => '_',
"." => '_',
"," => '_',
"?" => '_',
"\"" => '_',
"'" => '_',
"/" => '_',
"\\" => '_',
"%" => '_',
"#" => '_',
"*" => '_',
"ж" => "zh",
"ц" => "ts",
"ч" => "ch",
"ш" => "sh",
"щ" => "shch",
"ь" => "",
"ю" => "yu",
"я" => "ya",
"Ж" => "ZH",
"Ц" => "TS",
"Ч" => "CH",
"Ш" => "SH",
"Щ" => "SHCH",
"Ь" => "",
"Ю" => "YU",
"Я" => "YA",
"Й" => "i",
"й" => "ie",
"ё" => "Ye",
"" => "N"
]);
return strtolower($st);
}
/**
* Проверяет, является ли строка кодированной
* @param string $st
* @return int|false
*/
static function validate_encoded_string(string $st): int|false
{
$enc_st = self::encodestring($st);
return preg_match('/^[\w_-]+(\.[\w_-]+)?$/', $enc_st);
}
}

View file

@ -3,10 +3,18 @@
/** /**
* Формат для композиции изображений * Формат для композиции изображений
*/ */
class Tools_TemplateImage
namespace ctiso\Tools;
use ctiso\Tools\Drawing;
use GdImage;
class TemplateImage
{ {
static $listfiles = array('jpg' => 'jpeg', 'gif' => 'gif', 'png' => 'png', 'bmp' => 'wbmp'); /** @var array<string, string> */
static $listfonts = array( static array $listfiles = array('jpg' => 'jpeg', 'gif' => 'gif', 'png' => 'png', 'bmp' => 'wbmp');
/** @var array<string, string> */
static array $listfonts = array(
'georgia' => 'georgia.ttf', 'georgia' => 'georgia.ttf',
'georgiabd' => 'georgiab.ttf', 'georgiabd' => 'georgiab.ttf',
'georgiaz' => 'georgiaz.ttf', 'georgiaz' => 'georgiaz.ttf',
@ -21,27 +29,29 @@ class Tools_TemplateImage
'' => 'arial.ttf', '' => 'arial.ttf',
'dejavu' => 'DejaVuCondensedSerif.ttf', 'dejavu' => 'DejaVuCondensedSerif.ttf',
'dejavubd' => 'DejaVuCondensedSerifBold.ttf', 'dejavubd' => 'DejaVuCondensedSerifBold.ttf',
'dejavuz' =>'DejaVuCondensedSerifBoldItalic.ttf', 'dejavuz' => 'DejaVuCondensedSerifBoldItalic.ttf',
'dejavui' => 'DejaVuCondensedSerifItalic.ttf', 'dejavui' => 'DejaVuCondensedSerifItalic.ttf',
'miriad' => 'MyriadPro-Cond.ttf', 'miriad' => 'MyriadPro-Cond.ttf',
'miriadbd' => 'MyriadPro-BoldCond.ttf' 'miriadbd' => 'MyriadPro-BoldCond.ttf'
); );
/** @var string */
protected $src; protected $src;
protected $context = array(); protected array $context = [];
protected $data = array(); protected array $data = [];
protected $base = "c:\\windows\\fonts\\"; protected string $base = "c:\\windows\\fonts\\";
protected $image; protected GdImage $image;
protected $prepare = true; /** @var bool */
protected $_prepare = true;
/** @var bool */
public $debug = false; public $debug = false;
public $filename; public string $resource;
public $resource; public string $filename;
function __construct ($template = false) function __construct(?array $template = null)
{ {
// assert(is_string($src)); if ($template) {
if($template) {
$this->data = $template; $this->data = $template;
} }
} }
@ -49,132 +59,166 @@ class Tools_TemplateImage
/** /**
* Путь к изображению * Путь к изображению
*/ */
function resourcePath($path) function resourcePath(string $path): void
{ {
assert(is_string($path));
$this->resource = $path; $this->resource = $path;
} }
/** /**
* Путь у шрифтам * Путь у шрифтам
*/ */
function fontPath($path) function fontPath(string $path): void
{ {
assert(is_string($path));
$this->base = $path; $this->base = $path;
} }
function set($name, $value) /**
* @param string $name
* @param mixed $value
*/
function set(string $name, $value): void
{ {
assert(is_string($name)); $this->context['[' . $name . ']'] = $value;
$this->context['['.$name.']'] = $this->encode($value);
} }
function setImage($name) function setImage(string $name): void
{ {
$this->filename = $name; $this->filename = $name;
$this->image = $this->imagefromfile($name); $this->image = $this->imagefromfile($name);
} }
function setEmptyImage($width, $height) /**
* Создает пустое изображение
* @param int<1, max> $width
* @param int<1, max> $height
*/
function setEmptyImage($width, $height): void
{ {
$this->image = imagecreatetruecolor($width, $height); $this->image = imagecreatetruecolor($width, $height);
} }
/** /**
* Создает изображение из файла * Создает изображение из файла
* @param string $file
* @return GdImage|null
*/ */
function imagefromfile($file) function imagefromfile(string $file)
{ {
assert(is_string($file));
$suffix = pathinfo($file, PATHINFO_EXTENSION); $suffix = pathinfo($file, PATHINFO_EXTENSION);
if (array_key_exists($suffix, self::$listfiles)) { if (array_key_exists($suffix, self::$listfiles)) {
return call_user_func('imagecreatefrom' . self::$listfiles[$suffix], $file); $imageFn = 'imagecreatefrom' . self::$listfiles[$suffix];
if (!is_callable($imageFn)) {
return null;
}
return call_user_func($imageFn, $file);
} }
return null; return null;
} }
function getFontFile($name) function getFontFile(string $name): string
{ {
assert(is_string($name)); if (array_key_exists(strtolower($name), self::$listfonts)) {
if(array_key_exists(strtolower($name), self::$listfonts)) {
return $this->base . self::$listfonts[$name]; return $this->base . self::$listfonts[$name];
} }
return $this->base . 'arial.ttf'; return $this->base . 'arial.ttf';
} }
function fontSuffix($style) function fontSuffix(array $style): string
{ {
if($style[0] && $style[1]) return "z"; if ($style[0] && $style[1]) return "z";
if($style[0]) return "bd"; if ($style[0]) return "bd";
if($style[1]) return "i"; if ($style[1]) return "i";
return ""; return "";
} }
function imageText($text, $value/*: stdClass*/) /**
* @param string $text
* @param object{
* fontFamily: string,
* fontSize: int,
* fontStyle: array{string, string},
* color: string,
* align: array,
* valign: array,
* left: int,
* top: int,
* width: int,
* height: int
* } $value
*/
function imageText(string $text, object $value): void
{ {
assert(is_string($text));
$text = strtr($text, $this->context); $text = strtr($text, $this->context);
$size = $value->fontSize; $size = $value->fontSize;
$fontfile = $this->getFontFile($value->fontFamily . $this->fontSuffix($value->fontStyle)); $fontfile = $this->getFontFile($value->fontFamily . $this->fontSuffix($value->fontStyle));
$color = intval(substr($value->color, 1), 16); $color = intval(substr($value->color, 1), 16);
if ($value->align[0]) { if ($value->align[0]) {
$align = Tools_Drawing::ALIGN_LEFT; $align = Drawing::ALIGN_LEFT;
} elseif ($value->align[2]) { } elseif ($value->align[2]) {
$align = Tools_Drawing::ALIGN_RIGHT; $align = Drawing::ALIGN_RIGHT;
} else { } else {
$align = Tools_Drawing::ALIGN_CENTER; $align = Drawing::ALIGN_CENTER;
} }
if ($value->valign[0]) { if ($value->valign[0]) {
$valign = Tools_Drawing::ALIGN_TOP; $valign = Drawing::ALIGN_TOP;
} elseif ($value->valign[1]) { } elseif ($value->valign[1]) {
$valign = Tools_Drawing::ALIGN_CENTER; $valign = Drawing::ALIGN_CENTER;
} else { } else {
$valign = Tools_Drawing::ALIGN_BOTTOM; $valign = Drawing::ALIGN_BOTTOM;
} }
Tools_Drawing::imagettftextbox($this->image, $size, 0, $value->left, $value->top, $color, $fontfile, $text, Drawing::imagettftextbox(
$value->width, $value->height, $this->image,
$align, $valign); $size,
0,
$value->left,
$value->top,
$color,
$fontfile,
$text,
$value->width,
$value->height,
$align,
$valign
);
} }
/** /**
* Перекодировка текста * Перекодировка текста
* @deprecated Можно заменить encode($x) -> $x
*/ */
function encode($text) function encode(string $text): string
{ {
assert(is_string($text)); return $text;
return $text; //iconv("WINDOWS-1251", "UTF-8", $text);
} }
function setSize($new_width, $new_height) /**
* @param int<1,max> $new_width
* @param ?int<1,max> $new_height
*/
function setSize(int $new_width, ?int $new_height = null): void
{ {
$width = imagesx($this->image); $width = imagesx($this->image);
$height = imagesy($this->image); $height = imagesy($this->image);
if($new_height == false) { if ($new_height == null) {
$new_height = ceil($height * $new_width / $width); $new_height = max(1, (int)ceil($height * $new_width / $width));
} }
// Resample // Resample
$image_p = imagecreatetruecolor($new_width, $new_height); $image_p = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($image_p, $this->image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); imagecopyresampled($image_p, $this->image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
// imagecopyresized($image_p, $this->image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); // imagecopyresized($image_p, $this->image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
$this->image = $image_p; $this->image = $image_p;
} }
function prepare() { function prepare(): void
if($this->prepare) { {
$this->prepare = false; if ($this->_prepare) {
$this->_prepare = false;
foreach ($this->data as $value) { foreach ($this->data as $value) {
$this->imageText($value->text, $value); // break; $this->imageText($value->text, $value); // break;
} }
@ -184,10 +228,8 @@ class Tools_TemplateImage
/** /**
* Генерирует изображение из шаблона * Генерирует изображение из шаблона
*/ */
function render($file = null) function render(?string $file = null): string|bool
{ {
assert(is_string($file) || is_null($file));
$this->prepare(); $this->prepare();
if ($file == null) { if ($file == null) {

View file

@ -1,12 +1,21 @@
<?php <?php
class UTF8 { namespace ctiso;
static function str_split($str, $split_length = 1) {
$split_length = (int) $split_length; class UTF8
{
$matches = array(); /**
preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches); * @param string $str
* @param int $split_length
return $matches[0]; * @return list<string>
*/
static function str_split(string $str, int $split_length = 1): array
{
$split_length = (int) $split_length;
$matches = [];
preg_match_all('/.{' . $split_length . '}|[^\x00]{1,' . $split_length . '}$/us', $str, $matches);
return $matches[0];
} }
} }

31
src/Url.php Normal file
View file

@ -0,0 +1,31 @@
<?php
namespace ctiso;
class Url {
/** @var array<string, string> */
public array $parts = [];
public ?Url $parent;
/**
* @param Url|null $parent
*/
function setParent($parent): void {
$this->parent = $parent;
}
/**
* @param string[] $parts
*/
function setQuery(array $parts): void {
$this->parts = $parts;
}
function addQueryParam(string $key, ?string $value): void {
$this->parts[$key] = $value;
}
function toString(): string {
return '?' . http_build_query(array_merge($this->parts, $this->parent ? $this->parent->parts : []));
}
}

View file

@ -1,15 +1,16 @@
<?php <?php
/**
* UserMessageException.php
*
*/
/** /**
* Исключение с понятным пользователю сообщением, которое имеет смысл ему показать. * Исключение с понятным пользователю сообщением, которое имеет смысл ему показать.
* @see Controller_Front * @see Controller_Front
*/ */
class UserMessageException extends Exception { namespace ctiso;
class UserMessageException extends \Exception {
/** @var string */
public $userMessage; public $userMessage;
/**
* @param string $message
*/
public function __construct($message) { public function __construct($message) {
parent::__construct($message); parent::__construct($message);
$this->userMessage = $message; $this->userMessage = $message;

View file

@ -1,45 +0,0 @@
<?php
abstract class Validator_Rule_Abstract
{
public $field;
protected $errorMsg;
protected $ctx;
public function __construct($field, $errorMsg = null)
{
$this->field = $field;
$this->errorMsg = $errorMsg;
}
public function setName($field)
{
$this->field = $field;
return $this;
}
public function setErrorMsg($errorMsg)
{
$this->errorMsg = $errorMsg;
return $this;
}
public function getErrorMsg()
{
return $this->errorMsg;
}
public function isValid(Collection $container, $status = null)
{
return true;
}
function skipEmpty() {
return true;
}
public function setContext($ctx)
{
$this->ctx = $ctx;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace ctiso\Validator\Rule;
use ctiso\Collection;
abstract class AbstractRule
{
public string $field;
protected ?string $errorMsg;
/** @var RuleContext */
protected $ctx;
public function __construct(string $field, ?string $errorMsg = null)
{
$this->field = $field;
$this->errorMsg = $errorMsg;
}
public function setName(string $field): self
{
$this->field = $field;
return $this;
}
public function setErrorMsg(?string $errorMsg): self
{
$this->errorMsg = $errorMsg;
return $this;
}
public function getErrorMsg(): string
{
return $this->errorMsg;
}
/**
* @param Collection $container
* @param bool|null $status
* @return bool
*/
public function isValid(Collection $container, $status = null): bool
{
return true;
}
function skipEmpty(): bool {
return true;
}
/**
* @param RuleContext $ctx
*/
public function setContext($ctx): void
{
$this->ctx = $ctx;
}
}

View file

@ -3,14 +3,19 @@
/** /**
* Проверка на число * Проверка на число
*/ */
class Validator_Rule_Alpha extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
use ctiso\Validator\Rule\AbstractRule;
use ctiso\Collection;
class Alpha extends AbstractRule
{ {
public function getErrorMsg() public function getErrorMsg(): string
{ {
return "Поле должно содержать только буквы"; return "Поле должно содержать только буквы";
} }
public function isValid(Collection $container, $status = null) public function isValid(Collection $container, $status = null): bool
{ {
return ctype_alpha($container->get($this->field)); return ctype_alpha($container->get($this->field));
} }

View file

@ -3,14 +3,17 @@
/** /**
* Проверка формата электронной почты * Проверка формата электронной почты
*/ */
class Validator_Rule_Code extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
use ctiso\Validator\Rule\AbstractRule,
ctiso\Collection;
class Code extends AbstractRule
{ {
public function getErrorMsg() public function getErrorMsg(): string {
{
return "Неправильно указан персональный код"; return "Неправильно указан персональный код";
} }
function checkCode($code) { function checkCode(array $code): bool {
foreach($code as $c) { foreach($code as $c) {
if (empty($c)) { if (empty($c)) {
return false; return false;
@ -19,37 +22,38 @@ class Validator_Rule_Code extends Validator_Rule_Abstract
return true; return true;
} }
public function isValid(Collection $container, $status = null) public function isValid(Collection $container, $status = null): bool
{ {
if ($status == 'update') return true; if ($status == 'update') return true;
$name = $this->field; $name = $this->field;
if (is_array($_POST[$name . '_code_genre'])) { if (is_array($_POST[$name . '_code_genre'])) {
for($n = 0; $n < count($_POST[$name . '_code_genre']); $n++) { $count = count($_POST[$name . '_code_genre']);
$code = array( for($n = 0; $n < $count; $n++) {
$_POST[$name . '_code_genre'][$n], $code = [
$_POST[$name . '_code_f'][$n], $_POST[$name . '_code_genre'][$n],
$_POST[$name . '_code_i'][$n], $_POST[$name . '_code_f'][$n],
$_POST[$name . '_code_o'][$n], $_POST[$name . '_code_i'][$n],
$_POST[$name . '_code_year'][$n], $_POST[$name . '_code_o'][$n],
$_POST[$name . '_code_month'][$n], $_POST[$name . '_code_year'][$n],
$_POST[$name . '_code_day'][$n] $_POST[$name . '_code_month'][$n],
); $_POST[$name . '_code_day'][$n]
];
if (!$this->checkCode($code)) { if (!$this->checkCode($code)) {
return false; return false;
} }
} }
return true; return true;
} else { } else {
$code = array( $code = [
$_POST[$name . '_code_genre'], $_POST[$name . '_code_genre'],
$_POST[$name . '_code_f'], $_POST[$name . '_code_f'],
$_POST[$name . '_code_i'], $_POST[$name . '_code_i'],
$_POST[$name . '_code_o'], $_POST[$name . '_code_o'],
$_POST[$name . '_code_year'], $_POST[$name . '_code_year'],
$_POST[$name . '_code_month'], $_POST[$name . '_code_month'],
$_POST[$name . '_code_day'] $_POST[$name . '_code_day']
); ];
return $this->checkCode($code); return $this->checkCode($code);
} }

View file

@ -3,30 +3,38 @@
/** /**
* Проверка формата даты * Проверка формата даты
*/ */
class Validator_Rule_Count extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
{ use ctiso\Validator\Rule\AbstractRule,
public $size = 1; ctiso\Collection;
public $max = false;
public function getErrorMsg() class Count extends AbstractRule
{ {
public int $size = 1;
public ?int $max = null;
public function getErrorMsg(): string
{
return "Количество записей должно быть не менне {$this->size} и не более {$this->max}"; return "Количество записей должно быть не менне {$this->size} и не более {$this->max}";
} }
function not_empty($s) { /**
* @param string $s
* @return bool
*/
function notEmpty($s): bool {
return $s != ""; return $s != "";
} }
public function isValid(Collection $container, $status = null) public function isValid(Collection $container, $status = null): bool
{ {
if (!$this->max) { if (!$this->max) {
$this->max = $this->size; $this->max = $this->size;
} }
$count = count(array_filter(array_map('trim', $count = count(array_filter(array_map('trim',
explode(";", $container->get($this->field))), array($this, 'not_empty'))); explode(";", $container->get($this->field))), [$this, 'notEmpty']));
return $count >= $this->size && $count <= $this->max; return $count >= $this->size && $count <= ((int)$this->max);
} }
} }

View file

@ -3,21 +3,23 @@
/** /**
* Проверка формата даты * Проверка формата даты
*/ */
class Validator_Rule_Date extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
{ use ctiso\Validator\Rule\AbstractRule,
private $split = "\\/"; ctiso\Collection;
public function getErrorMsg() class Date extends AbstractRule
{ {
public function getErrorMsg(): string
{
return "Неверный формат даты"; return "Неверный формат даты";
} }
public function isValid(Collection $container, $status = null) public function isValid(Collection $container, $status = null): bool
{ {
$pattern = "/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})$/"; $pattern = "/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})$/";
$matches = []; $matches = [];
return (preg_match($pattern, $container->get($this->field), $matches) return (preg_match($pattern, $container->get($this->field), $matches)
&& checkdate($matches[2], $matches[1], $matches[3])); && checkdate((int)$matches[2], (int)$matches[1], (int)$matches[3]));
} }
} }

View file

@ -3,23 +3,21 @@
/** /**
* Проверка формата электронной почты * Проверка формата электронной почты
*/ */
class Validator_Rule_Email extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
use ctiso\Validator\Rule\AbstractRule,
ctiso\Collection;
class Email extends AbstractRule
{ {
public function getErrorMsg() public function getErrorMsg(): string
{ {
return "Неверный формат электронной почты"; return "Неверный формат электронной почты";
} }
public function isValid(Collection $container, $status = null) public function isValid(Collection $container, $status = null): bool
{ {
$user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\|\{\}~\']+';
$doIsValid = '(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]\.?)+';
$ipv4 = '[0-9]{1,3}(\.[0-9]{1,3}){3}';
$ipv6 = '[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7}';
$emails = explode(",", $container->get($this->field)); $emails = explode(",", $container->get($this->field));
foreach ($emails as $email) { foreach ($emails as $email) {
// if (! preg_match("/^$user@($doIsValid|(\[($ipv4|$ipv6)\]))$/", $email)) return false;
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) return false; if (! filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
} }

View file

@ -3,22 +3,20 @@
/** /**
* Проверка формата электронной почты * Проверка формата электронной почты
*/ */
class Validator_Rule_EmailList extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
use ctiso\Validator\Rule\AbstractRule,
ctiso\Collection;
class EmailList extends AbstractRule
{ {
public function getErrorMsg() public function getErrorMsg(): string
{ {
return "Неверный формат электронной почты"; return "Неверный формат электронной почты";
} }
public function isValid(Collection $container, $status = null) { public function isValid(Collection $container, $status = null): bool {
$user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\|\{\}~\']+';
$doIsValid = '(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]\.?)+';
$ipv4 = '[0-9]{1,3}(\.[0-9]{1,3}){3}';
$ipv6 = '[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7}';
$emails = $container->get($this->field); $emails = $container->get($this->field);
foreach ($emails as $email) { foreach ($emails as $email) {
// if (! preg_match("/^$user@($doIsValid|(\[($ipv4|$ipv6)\]))$/", $email)) return false;
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) return false; if (! filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
} }
return true; return true;

View file

@ -1,13 +1,18 @@
<?php <?php
class Validator_Rule_FileName extends Validator_Rule_Abstract { namespace ctiso\Validator\Rule;
use ctiso\Validator\Rule\AbstractRule,
ctiso\Collection,
ctiso\Path;
public function getErrorMsg() class FileName extends AbstractRule {
public function getErrorMsg(): string
{ {
return 'Название файла может содержать только символы латиницы в нижнем регистре и цифры'; return 'Название файла может содержать только символы латиницы в нижнем регистре и цифры';
} }
public function isValid(Collection $container, $status = null) public function isValid(Collection $container, $status = null): bool
{ {
return Path::isName($container->get($this->field)); return Path::isName($container->get($this->field));
} }

View file

@ -3,44 +3,48 @@
/** /**
* Проверка формата времени * Проверка формата времени
*/ */
class Validator_Rule_IsFile extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
{ use ctiso\Validator\Rule\AbstractRule,
private $type = array(); ctiso\Collection;
private $maxsize = 1024;
function skipEmpty() { class IsFile extends AbstractRule
{
private array $type = [];
private int $maxsize = 1024;
function skipEmpty(): bool {
return false; return false;
} }
function setSize($size) { function setSize(int $size): void {
$this->maxsize = $size; $this->maxsize = $size;
} }
function setType(array $type) { function setType(array $type): void {
$this->type = $type; $this->type = $type;
} }
public function isValid(Collection $container, $status = null) public function isValid(Collection $container, $status = null): bool
{ {
if (!isset($_FILES[$this->field]) || $_FILES[$this->field]['error'] == UPLOAD_ERR_NO_FILE) { if (!isset($_FILES[$this->field]) || $_FILES[$this->field]['error'] == UPLOAD_ERR_NO_FILE) {
$this->setErrorMsg('Файл не загружен'); $this->setErrorMsg('Файл не загружен');
return false; return false;
} }
if ($_FILES[$this->field]['error'] == UPLOAD_ERR_INI_SIZE) { if ($_FILES[$this->field]['error'] == UPLOAD_ERR_INI_SIZE) {
$this->setErrorMsg('Превышен размер файла'); $this->setErrorMsg('Превышен размер файла');
return false; return false;
} }
$tmp = $_FILES[$this->field]; $tmp = $_FILES[$this->field];
if (!in_array($tmp['type'], $this->type)) { if (!in_array($tmp['type'], $this->type)) {
$this->setErrorMsg('Неверный формат файла'); $this->setErrorMsg('Неверный формат файла');
return false; return false;
} }
if (((int)$tmp['size']) > $this->maxsize*1024) { if (((int)$tmp['size']) > $this->maxsize*1024) {
$this->setErrorMsg('Неверный размер файла'); $this->setErrorMsg('Неверный размер файла');
return false; return false;
} }
return true; return true;

View file

@ -3,16 +3,22 @@
/** /**
* Проверка на равентство двух полей * Проверка на равентство двух полей
*/ */
class Validator_Rule_Match extends Validator_Rule_Abstract namespace ctiso\Validator\Rule;
use ctiso\Validator\Rule\AbstractRule,
ctiso\Collection;
class MatchRule extends AbstractRule
{ {
/** @var string */
public $same; public $same;
public function getErrorMsg() public function getErrorMsg(): string
{ {
return "Поля не совпадают"; return "Поля не совпадают";
} }
public function isValid(Collection $container, $status = null) { public function isValid(Collection $container, $status = null): bool
{
return (strcmp($container->get($this->field), $container->get($this->same)) == 0); return (strcmp($container->get($this->field), $container->get($this->same)) == 0);
} }
} }

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