Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
highloadblocktable.php
1<?php
10
11use Bitrix\Main,
15
16Main\Localization\Loc::loadLanguageFile(__FILE__);
17
36class HighloadBlockTable extends Entity\DataManager
37{
38 private const ENTITY_ID_PREFIX = 'HLBLOCK_';
39
40 private const ENTITY_ID_MASK = '/^HLBLOCK_(\d+)$/';
41
45 public static function getTableName()
46 {
47 return 'b_hlblock_entity';
48 }
49
50 public static function getObjectClass()
51 {
52 return HighloadBlock::class;
53 }
54
55 public static function getMap()
56 {
57 IncludeModuleLangFile(__FILE__);
58
59 $sqlHelper = Application::getConnection()->getSqlHelper();
60
62 $fieldsMap = array(
63 'ID' => array(
64 'data_type' => 'integer',
65 'primary' => true,
66 'autocomplete' => true
67 ),
68 'NAME' => array(
69 'data_type' => 'string',
70 'required' => true,
71 'validation' => array(__CLASS__, 'validateName')
72 ),
73 'TABLE_NAME' => array(
74 'data_type' => 'string',
75 'required' => true,
76 'validation' => array(__CLASS__, 'validateTableName')
77 ),
78 'FIELDS_COUNT' => array(
79 'data_type' => 'integer',
80 'expression' => array(
81 '(SELECT COUNT(ID) FROM b_user_field WHERE b_user_field.ENTITY_ID = '.
82 $sqlHelper->getConcatFunction("'".self::ENTITY_ID_PREFIX."'", $sqlHelper->castToChar('%s')).')',
83 'ID'
84 )
85 ),
86 'LANG' => new Entity\ReferenceField(
87 'LANG',
88 'Bitrix\Highloadblock\HighloadBlockLangTable',
89 array('=this.ID' => 'ref.ID', 'ref.LID' => new Main\DB\SqlExpression('?', LANGUAGE_ID))
90 ),
91 );
92
93 return $fieldsMap;
94 }
95
102 public static function add(array $data)
103 {
104 $result = parent::add($data);
105
106 if (!$result->isSuccess(true))
107 {
108 return $result;
109 }
110
111 // create table in db
112 $connection = Application::getConnection();
113 $dbtype = $connection->getType();
114 $sqlHelper = $connection->getSqlHelper();
115
116 if ($dbtype == 'mysql')
117 {
118 $connection->query('
119 CREATE TABLE '.$sqlHelper->quote($data['TABLE_NAME']).' (ID int(11) unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY (ID))
120 ');
121 }
122 elseif ($dbtype == 'pgsql')
123 {
124 $connection->query('
125 CREATE TABLE '.$sqlHelper->quote($data['TABLE_NAME']).' (ID serial NOT NULL, PRIMARY KEY (ID))
126 ');
127 }
128 elseif ($dbtype == 'mssql')
129 {
130 $connection->query('
131 CREATE TABLE '.$sqlHelper->quote($data['TABLE_NAME']).' (ID int NOT NULL IDENTITY (1, 1),
132 CONSTRAINT '.$data['TABLE_NAME'].'_ibpk_1 PRIMARY KEY (ID))
133 ');
134 }
135 elseif ($dbtype == 'oracle')
136 {
137 $connection->query('
138 CREATE TABLE '.$sqlHelper->quote($data['TABLE_NAME']).' (ID number(11) NOT NULL, PRIMARY KEY (ID))
139 ');
140
141 $connection->query('
142 CREATE SEQUENCE sq_'.$data['TABLE_NAME'].'
143 ');
144
145 $connection->query('
146 CREATE OR REPLACE TRIGGER '.$data['TABLE_NAME'].'_insert
147 BEFORE INSERT
148 ON '.$sqlHelper->quote($data['TABLE_NAME']).'
149 FOR EACH ROW
150 BEGIN
151 IF :NEW.ID IS NULL THEN
152 SELECT sq_'.$data['TABLE_NAME'].'.NEXTVAL INTO :NEW.ID FROM dual;
153 END IF;
154 END;
155 ');
156 }
157 else
158 {
159 throw new Main\SystemException('Unknown DB type');
160 }
161
162 return $result;
163 }
164
171 public static function update($primary, array $data)
172 {
173 global $USER_FIELD_MANAGER;
174
175 // get old data
176 $oldData = static::getByPrimary($primary)->fetch();
177
178 // update row
179 $result = parent::update($primary, $data);
180
181 if (!$result->isSuccess(true))
182 {
183 return $result;
184 }
185
186 // rename table in db
187 if (isset($data['TABLE_NAME']) && $data['TABLE_NAME'] !== $oldData['TABLE_NAME'])
188 {
189 $connection = Application::getConnection();
190 $sqlHelper = $connection->getSqlHelper();
191 $connection->renameTable($oldData['TABLE_NAME'], $data['TABLE_NAME']);
192
193 if ($connection instanceof MssqlConnection)
194 {
195 // rename constraint
196 $connection->query(sprintf(
197 "EXEC sp_rename %s, %s, 'OBJECT'",
198 $sqlHelper->quote($oldData['TABLE_NAME'].'_ibpk_1'),
199 $sqlHelper->quote($data['TABLE_NAME'].'_ibpk_1')
200 ));
201 }
202
203 // rename also uf multiple tables and its constraints, sequences, and triggers
205 foreach ($USER_FIELD_MANAGER->getUserFields(static::compileEntityId($oldData['ID'])) as $field)
206 {
207 if ($field['MULTIPLE'] == 'Y')
208 {
209 $oldUtmTableName = static::getMultipleValueTableName($oldData, $field);
210 $newUtmTableName = static::getMultipleValueTableName($data, $field);
211
212 $connection->renameTable($oldUtmTableName, $newUtmTableName);
213 }
214 }
215 }
216
217 return $result;
218 }
219
225 public static function delete($primary)
226 {
227 global $USER_FIELD_MANAGER;
228
229 // get old data
230 $hlblock = static::getByPrimary($primary)->fetch();
231
232 // remove row
233 $result = parent::delete($primary);
234
235 if (!$result->isSuccess(true))
236 {
237 return $result;
238 }
239
240 // get file fields
241 $file_fields = array();
243 $fields = $USER_FIELD_MANAGER->getUserFields(static::compileEntityId($hlblock['ID']));
244
245 foreach ($fields as $name => $field)
246 {
247 if ($field['USER_TYPE']['BASE_TYPE'] === 'file')
248 {
249 $file_fields[] = $name;
250 }
251 }
252
253 // delete files
254 if (!empty($file_fields))
255 {
256 $oldEntity = static::compileEntity($hlblock);
257
258 $query = new Entity\Query($oldEntity);
259
260 // select file ids
261 $query->setSelect($file_fields);
262
263 // if they are not empty
264 $filter = array('LOGIC' => 'OR');
265
266 foreach ($file_fields as $file_field)
267 {
268 $filter['!'.$file_field] = false;
269 }
270
271 $query->setFilter($filter);
272
273 // go
274 $iterator = $query->exec();
275
276 while ($row = $iterator->fetch())
277 {
278 foreach ($file_fields as $file_field)
279 {
280 if (!empty($row[$file_field]))
281 {
282 if (is_array($row[$file_field]))
283 {
284 foreach ($row[$file_field] as $value)
285 {
286 \CFile::delete($value);
287 }
288 }
289 else
290 {
291 \CFile::delete($row[$file_field]);
292 }
293 }
294 }
295 }
296 unset($row, $iterator);
297 }
298
299 $connection = Application::getConnection();
300
301 foreach ($fields as $field)
302 {
303 // delete from uf registry
304 if ($field['USER_TYPE']['BASE_TYPE'] === 'enum')
305 {
306 $enumField = new \CUserFieldEnum;
307 $enumField->DeleteFieldEnum($field['ID']);
308 }
309
310 $connection->query("DELETE FROM b_user_field_lang WHERE USER_FIELD_ID = ".$field['ID']);
311 $connection->query("DELETE FROM b_user_field WHERE ID = ".$field['ID']);
312
313 // if multiple - drop utm table
314 if ($field['MULTIPLE'] == 'Y')
315 {
316 $utmTableName = static::getMultipleValueTableName($hlblock, $field);
317 $connection->dropTable($utmTableName);
318 }
319 }
320
321 // clear uf cache
322 $managedCache = Application::getInstance()->getManagedCache();
323 if(CACHED_b_user_field !== false)
324 {
325 $managedCache->cleanDir("b_user_field");
326 }
327
328 // clear langs
329 $res = HighloadBlockLangTable::getList(array(
330 'filter' => array('ID' => $primary)
331 ));
332 while ($row = $res->fetch())
333 {
334 HighloadBlockLangTable::delete([
335 'ID' => $row['ID'],
336 'LID' => $row['LID'],
337 ]);
338 }
339
340 // clear rights
341 $res = HighloadBlockRightsTable::getList(array(
342 'filter' => array('HL_ID' => $primary)
343 ));
344 while ($row = $res->fetch())
345 {
346 HighloadBlockRightsTable::delete($row['ID']);
347 }
348
349 // drop hl table
350 $connection->dropTable($hlblock['TABLE_NAME']);
351
352 return $result;
353 }
354
359 public static function resolveHighloadblock($hlblock)
360 {
361 if (!is_array($hlblock))
362 {
363 if (is_int($hlblock) || is_numeric(mb_substr($hlblock, 0, 1)))
364 {
365 // we have an id
366 $hlblock = HighloadBlockTable::getById($hlblock)->fetch();
367 }
368 elseif (is_string($hlblock) && $hlblock !== '')
369 {
370 // we have a name
371 $hlblock = HighloadBlockTable::query()->addSelect('*')->where('NAME', $hlblock)->exec()->fetch();
372 }
373 else
374 {
375 $hlblock = null;
376 }
377 }
378 if (empty($hlblock))
379 return null;
380
381 if (!isset($hlblock['ID']))
382 return null;
383 if (!isset($hlblock['NAME']) || !preg_match('/^[a-z0-9_]+$/i', $hlblock['NAME']))
384 return null;
385 if (empty($hlblock['TABLE_NAME']))
386 return null;
387
388 return $hlblock;
389 }
390
397 public static function compileEntity($hlblock)
398 {
399 global $USER_FIELD_MANAGER;
400
401 $rawBlock = $hlblock;
402 $hlblock = static::resolveHighloadblock($hlblock);
403 if (empty($hlblock))
404 {
405 throw new Main\SystemException(sprintf(
406 'Invalid highloadblock description `%s`.', mydump($rawBlock)
407 ));
408 }
409 unset($rawBlock);
410
411 // generate entity & data manager
412 $fieldsMap = array();
413
414 // add ID
415 $fieldsMap['ID'] = array(
416 'data_type' => 'integer',
417 'primary' => true,
418 'autocomplete' => true
419 );
420
421 // build datamanager class
422 $entity_name = $hlblock['NAME'];
423 $entity_data_class = $hlblock['NAME'].'Table';
424
425 if (class_exists($entity_data_class))
426 {
427 // rebuild if it's already exists
428 Entity\Base::destroy($entity_data_class);
429 }
430 else
431 {
432 $entity_table_name = $hlblock['TABLE_NAME'];
433
434 // make with an empty map
435 $eval = '
436 class '.$entity_data_class.' extends '.__NAMESPACE__.'\DataManager
437 {
438 public static function getTableName()
439 {
440 return '.var_export($entity_table_name, true).';
441 }
442
443 public static function getMap()
444 {
445 return '.var_export($fieldsMap, true).';
446 }
447
448 public static function getHighloadBlock()
449 {
450 return '.var_export($hlblock, true).';
451 }
452 }
453 ';
454
455 eval($eval);
456 }
457
458 // then configure and attach fields
460 $entity = $entity_data_class::getEntity();
461
463 $uFields = $USER_FIELD_MANAGER->getUserFields(static::compileEntityId($hlblock['ID']));
464
465 foreach ($uFields as $uField)
466 {
467 if ($uField['MULTIPLE'] == 'N')
468 {
469 // just add single field
470 $params = array(
471 'required' => $uField['MANDATORY'] == 'Y'
472 );
473 $field = $USER_FIELD_MANAGER->getEntityField($uField, $uField['FIELD_NAME'], $params);
474 $entity->addField($field);
475 foreach ($USER_FIELD_MANAGER->getEntityReferences($uField, $field) as $reference)
476 {
477 $entity->addField($reference);
478 }
479 }
480 else
481 {
482 // build utm entity
483 static::compileUtmEntity($entity, $uField);
484 }
485 }
486
487 return Entity\Base::getInstance($entity_name);
488 }
489
494 public static function compileEntityId($id)
495 {
496 return self::ENTITY_ID_PREFIX.$id;
497 }
498
499 public static function OnBeforeUserTypeAdd($field)
500 {
501 if (preg_match(self::ENTITY_ID_MASK, $field['ENTITY_ID'], $matches))
502 {
503 if (mb_substr($field['FIELD_NAME'], -4) == '_REF')
504 {
509 global $APPLICATION;
510
511 $APPLICATION->ThrowException(
512 Main\Localization\Loc::getMessage('HIGHLOADBLOCK_HIGHLOAD_BLOCK_ENTITY_FIELD_NAME_REF_RESERVED')
513 );
514
515 return false;
516 }
517 else
518 {
519 return array('PROVIDE_STORAGE' => false);
520 }
521 }
522
523 return true;
524 }
525
526 public static function onAfterUserTypeAdd($field)
527 {
528 global $APPLICATION, $USER_FIELD_MANAGER;
529
530 if (preg_match(self::ENTITY_ID_MASK, $field['ENTITY_ID'], $matches))
531 {
532 $field['USER_TYPE'] = $USER_FIELD_MANAGER->getUserType($field['USER_TYPE_ID']);
533
534 // get entity info
535 $hlblock_id = $matches[1];
536 $hlblock = HighloadBlockTable::getById($hlblock_id)->fetch();
537
538 if (empty($hlblock))
539 {
540 $APPLICATION->throwException(sprintf(
541 'Entity "'.static::compileEntityId('%s').'" wasn\'t found.', $hlblock_id
542 ));
543
544 return false;
545 }
546
547 // get usertype info
548 $sql_column_type = $USER_FIELD_MANAGER->getUtsDBColumnType($field);
549
550 // create field in db
551 $connection = Application::getConnection();
552 $sqlHelper = $connection->getSqlHelper();
553
554 $connection->query(sprintf(
555 'ALTER TABLE %s ADD %s %s',
556 $sqlHelper->quote($hlblock['TABLE_NAME']), $sqlHelper->quote($field['FIELD_NAME']), $sql_column_type
557 ));
558
559 if ($field['MULTIPLE'] == 'Y')
560 {
561 // create table for this relation
562 $hlentity = static::compileEntity($hlblock);
563 $utmEntity = Entity\Base::getInstance(HighloadBlockTable::getUtmEntityClassName($hlentity, $field));
564
565 $utmEntity->createDbTable();
566
567 // add indexes
568 $connection->query(sprintf(
569 'CREATE INDEX %s ON %s (%s)',
570 $sqlHelper->quote('IX_UTM_HL'.$hlblock['ID'].'_'.$field['ID'].'_ID'),
571 $sqlHelper->quote($utmEntity->getDBTableName()),
572 $sqlHelper->quote('ID')
573 ));
574
575 $connection->query(sprintf(
576 'CREATE INDEX %s ON %s (%s)',
577 $sqlHelper->quote('IX_UTM_HL'.$hlblock['ID'].'_'.$field['ID'].'_VALUE'),
578 $sqlHelper->quote($utmEntity->getDBTableName()),
579 $sqlHelper->quote('VALUE')
580 ));
581 }
582
583 return array('PROVIDE_STORAGE' => false);
584 }
585
586 return true;
587 }
588
589 public static function OnBeforeUserTypeDelete($field)
590 {
591 global $USER_FIELD_MANAGER;
592
593 if (preg_match(self::ENTITY_ID_MASK, $field['ENTITY_ID'], $matches))
594 {
595 // get entity info
596 $hlblock_id = $matches[1];
597 $hlblock = HighloadBlockTable::getById($hlblock_id)->fetch();
598
599 if (empty($hlblock))
600 {
601 // non-existent or zombie. let it go
602 return array('PROVIDE_STORAGE' => false);
603 }
604
606 $fieldType = $USER_FIELD_MANAGER->getUserType($field["USER_TYPE_ID"]);
607
608 if ($fieldType['BASE_TYPE'] == 'file')
609 {
610 // if it was file field, then delete all files
611 $entity = static::compileEntity($hlblock);
612
614 $dataClass = $entity->getDataClass();
615
616 $rows = $dataClass::getList(array('select' => array($field['FIELD_NAME'])));
617
618 while ($oldData = $rows->fetch())
619 {
620 if (empty($oldData[$field['FIELD_NAME']]))
621 {
622 continue;
623 }
624
625 if(is_array($oldData[$field['FIELD_NAME']]))
626 {
627 foreach($oldData[$field['FIELD_NAME']] as $value)
628 {
629 \CFile::delete($value);
630 }
631 }
632 else
633 {
634 \CFile::delete($oldData[$field['FIELD_NAME']]);
635 }
636 }
637 }
638
639 // drop db column
640 $connection = Application::getConnection();
641 $connection->dropColumn($hlblock['TABLE_NAME'], $field['FIELD_NAME']);
642
643 // if multiple - drop utm table
644 if ($field['MULTIPLE'] == 'Y')
645 {
646 $utmTableName = static::getMultipleValueTableName($hlblock, $field);
647 $connection->dropTable($utmTableName);
648 }
649
650 return array('PROVIDE_STORAGE' => false);
651 }
652
653 return true;
654 }
655
656 protected static function compileUtmEntity(Entity\Base $hlentity, $userfield)
657 {
658 global $USER_FIELD_MANAGER;
659
660 // build utm entity
662 $hlDataClass = $hlentity->getDataClass();
663 $hlblock = $hlDataClass::getHighloadBlock();
664
665 $utmClassName = static::getUtmEntityClassName($hlentity, $userfield);
666 $utmTableName = static::getMultipleValueTableName($hlblock, $userfield);
667
668 if (class_exists($utmClassName.'Table'))
669 {
670 // rebuild if it already exists
671 Entity\Base::destroy($utmClassName.'Table');
672 $utmEntity = Entity\Base::getInstance($utmClassName);
673 }
674 else
675 {
676 // create entity from scratch
677 $utmEntity = Entity\Base::compileEntity($utmClassName, array(), array(
678 'table_name' => $utmTableName,
679 'namespace' => $hlentity->getNamespace()
680 ));
681 }
682
683 // main fields
684 $utmValueField = $USER_FIELD_MANAGER->getEntityField($userfield, 'VALUE');
685
686 $utmEntityFields = array(
687 new Entity\IntegerField('ID'),
688 $utmValueField
689 );
690
691 // references
692 $references = $USER_FIELD_MANAGER->getEntityReferences($userfield, $utmValueField);
693
694 foreach ($references as $reference)
695 {
696 $utmEntityFields[] = $reference;
697 }
698
699 foreach ($utmEntityFields as $field)
700 {
701 $utmEntity->addField($field);
702 }
703
704 // add original entity reference
705 $referenceField = new Entity\ReferenceField(
706 'OBJECT',
707 $hlentity,
708 array('=this.ID' => 'ref.ID')
709 );
710
711 $utmEntity->addField($referenceField);
712
713 // add short alias for back-reference
714 $aliasField = new Entity\ExpressionField(
715 $userfield['FIELD_NAME'].'_SINGLE',
716 '%s',
717 $utmEntity->getFullName().':'.'OBJECT.VALUE',
718 array(
719 'data_type' => get_class($utmEntity->getField('VALUE')),
720 'required' => $userfield['MANDATORY'] == 'Y'
721 )
722 );
723
724 $hlentity->addField($aliasField);
725
726 // add aliases to references
727 /*foreach ($references as $reference)
728 {
729 // todo after #44924 is resolved
730 // actually no. to make it work expression should support linking to references
731 }*/
732
733 // add serialized cache-field
734 $cacheField = new Main\ORM\Fields\ArrayField($userfield['FIELD_NAME'], array(
735 'required' => $userfield['MANDATORY'] == 'Y'
736 ));
737
738 Main\UserFieldTable::setMultipleFieldSerialization($cacheField, $userfield);
739
740 $hlentity->addField($cacheField);
741
742 return $utmEntity;
743 }
744
745 public static function getUtmEntityClassName(Entity\Base $hlentity, $userfield)
746 {
747 return $hlentity->getName().'Utm'.Main\Text\StringHelper::snake2camel($userfield['FIELD_NAME']);
748 }
749
750 public static function getMultipleValueTableName($hlblock, $userfield)
751 {
752 return $hlblock['TABLE_NAME'].'_'.mb_strtolower($userfield['FIELD_NAME']);
753 }
754
755 public static function validateName()
756 {
757 return array(
758 new Entity\Validator\Unique,
759 new Entity\Validator\Length(
760 null,
761 100,
762 array('MAX' => GetMessage('HIGHLOADBLOCK_HIGHLOAD_BLOCK_ENTITY_NAME_FIELD_LENGTH_INVALID'))
763 ),
764 new Entity\Validator\RegExp(
765 '/^[A-Z][A-Za-z0-9]*$/',
766 GetMessage('HIGHLOADBLOCK_HIGHLOAD_BLOCK_ENTITY_NAME_FIELD_REGEXP_INVALID')
767 ),
768 new Entity\Validator\RegExp(
769 '/(?<!Table)$/i',
770 GetMessage('HIGHLOADBLOCK_HIGHLOAD_BLOCK_ENTITY_NAME_FIELD_TABLE_POSTFIX_INVALID')
771 )
772 );
773 }
774
775 public static function validateTableName()
776 {
777 return array(
778 new Entity\Validator\Unique,
779 new Entity\Validator\Length(
780 null,
781 64,
782 array('MAX' => GetMessage('HIGHLOADBLOCK_HIGHLOAD_BLOCK_ENTITY_TABLE_NAME_FIELD_LENGTH_INVALID'))
783 ),
784 new Entity\Validator\RegExp(
785 '/^[a-z0-9_]+$/',
786 GetMessage('HIGHLOADBLOCK_HIGHLOAD_BLOCK_ENTITY_TABLE_NAME_FIELD_REGEXP_INVALID')
787 ),
788 array(__CLASS__, 'validateTableExisting')
789 );
790 }
791
792 public static function validateTableExisting($value, $primary, array $row, Entity\Field $field)
793 {
794 $checkName = null;
795
796 if (empty($primary))
797 {
798 // new row
799 $checkName = $value;
800 }
801 else
802 {
803 // update row
804 $oldData = static::getByPrimary($primary)->fetch();
805
806 if ($value != $oldData['TABLE_NAME'])
807 {
808 // table name has been changed for existing row
809 $checkName = $value;
810 }
811 }
812
813 if (!empty($checkName))
814 {
815 if (Application::getConnection()->isTableExists($checkName))
816 {
817 return GetMessage('HIGHLOADBLOCK_HIGHLOAD_BLOCK_ENTITY_TABLE_NAME_ALREADY_EXISTS',
818 array('#TABLE_NAME#' => $value)
819 );
820 }
821 }
822
823 return true;
824 }
825}
static getUtmEntityClassName(Entity\Base $hlentity, $userfield)
static validateTableExisting($value, $primary, array $row, Entity\Field $field)
static getMultipleValueTableName($hlblock, $userfield)
static getConnection($name="")
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29