1<?php 2 3/* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */ 4 5/** 6 * Helper class to create new object types (object_data, RBAC) 7 * 8 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com> 9 * $Id: class.ilObjFolderGUI.php 25134 2010-08-13 14:22:11Z smeyer $ 10 * 11 * @ingroup ServicesMigration 12 */ 13class ilDBUpdateNewObjectType 14{ 15 const RBAC_OP_EDIT_PERMISSIONS = 1; 16 const RBAC_OP_VISIBLE = 2; 17 const RBAC_OP_READ = 3; 18 const RBAC_OP_WRITE = 4; 19 const RBAC_OP_DELETE = 6; 20 const RBAC_OP_COPY = 99; 21 22 protected static $initialPermissionDefinition = [ 23 'role' => [ 24 'User' => [ 25 'id' => 4, 26 'ignore_for_authoring_objects' => true, 27 'object' => [ 28 self::RBAC_OP_VISIBLE, 29 self::RBAC_OP_READ, 30 ] 31 ] 32 ], 33 'rolt' => [ 34 'il_crs_admin' => [ 35 'object' => [ 36 self::RBAC_OP_VISIBLE, 37 self::RBAC_OP_READ, 38 self::RBAC_OP_WRITE, 39 self::RBAC_OP_DELETE, 40 self::RBAC_OP_COPY, 41 self::RBAC_OP_EDIT_PERMISSIONS, 42 ], 43 'lp' => true, 44 'create' => [ 45 'crs', 46 'grp', 47 'fold', 48 ] 49 ], 50 'il_crs_tutor' => [ 51 'object' => [ 52 self::RBAC_OP_VISIBLE, 53 self::RBAC_OP_READ, 54 self::RBAC_OP_WRITE, 55 self::RBAC_OP_COPY, 56 ], 57 'create' => [ 58 'crs', 59 'fold', 60 ] 61 ], 62 'il_crs_member' => [ 63 'ignore_for_authoring_objects' => true, 64 'object' => [ 65 self::RBAC_OP_VISIBLE, 66 self::RBAC_OP_READ, 67 ] 68 ], 69 'il_grp_admin' => [ 70 'object' => [ 71 self::RBAC_OP_VISIBLE, 72 self::RBAC_OP_READ, 73 self::RBAC_OP_WRITE, 74 self::RBAC_OP_DELETE, 75 self::RBAC_OP_COPY, 76 self::RBAC_OP_EDIT_PERMISSIONS, 77 ], 78 'lp' => true, 79 'create' => [ 80 'grp', 81 'fold', 82 ] 83 ], 84 'il_grp_member' => [ 85 'ignore_for_authoring_objects' => true, 86 'object' => [ 87 self::RBAC_OP_VISIBLE, 88 self::RBAC_OP_READ, 89 ] 90 ], 91 'Author' => [ 92 'object' => [ 93 self::RBAC_OP_VISIBLE, 94 self::RBAC_OP_READ, 95 self::RBAC_OP_WRITE, 96 self::RBAC_OP_DELETE, 97 self::RBAC_OP_COPY, 98 self::RBAC_OP_EDIT_PERMISSIONS, 99 ], 100 'lp' => true, 101 'create' => [ 102 'cat', 103 'crs', 104 'grp', 105 'fold', 106 ] 107 ], 108 'Local Administrator' => [ 109 'object' => [ 110 self::RBAC_OP_VISIBLE, 111 self::RBAC_OP_DELETE, 112 self::RBAC_OP_EDIT_PERMISSIONS, 113 ], 114 'create' => [ 115 'cat', 116 ] 117 ], 118 ] 119 ]; 120 121 /** 122 * Add new type to object data 123 * 124 * @param string $a_type_id 125 * @param string $a_type_title 126 * @return int insert id 127 */ 128 public static function addNewType($a_type_id, $a_type_title) 129 { 130 global $ilDB; 131 132 // check if it already exists 133 $type_id = self::getObjectTypeId($a_type_id); 134 if ($type_id) { 135 return $type_id; 136 } 137 138 $type_id = $ilDB->nextId('object_data'); 139 140 $fields = array( 141 'obj_id' => array('integer', $type_id), 142 'type' => array('text', 'typ'), 143 'title' => array('text', $a_type_id), 144 'description' => array('text', $a_type_title), 145 'owner' => array('integer', -1), 146 'create_date' => array('timestamp', ilUtil::now()), 147 'last_update' => array('timestamp', ilUtil::now()) 148 ); 149 $ilDB->insert('object_data', $fields); 150 151 return $type_id; 152 } 153 154 /** 155 * Add RBAC operations for type 156 * 157 * @param int $a_type_id 158 * @param array $a_operations 159 */ 160 public static function addRBACOperations($a_type_id, array $a_operations) 161 { 162 foreach ($a_operations as $ops_id) { 163 if (self::isValidRBACOperation($ops_id)) { 164 if ($ops_id == self::RBAC_OP_COPY) { 165 $ops_id = self::getCustomRBACOperationId('copy'); 166 } 167 168 self::addRBACOperation($a_type_id, $ops_id); 169 } 170 } 171 } 172 173 /** 174 * Add RBAC operation 175 * 176 * @param int $a_type_id 177 * @param int $a_ops_id 178 * @return bool 179 */ 180 public static function addRBACOperation($a_type_id, $a_ops_id) 181 { 182 global $ilDB; 183 184 // check if it already exists 185 $set = $ilDB->query('SELECT * FROM rbac_ta' . 186 ' WHERE typ_id = ' . $ilDB->quote($a_type_id, 'integer') . 187 ' AND ops_id = ' . $ilDB->quote($a_ops_id, 'integer')); 188 if ($ilDB->numRows($set)) { 189 return false; 190 } 191 192 $fields = array( 193 'typ_id' => array('integer', $a_type_id), 194 'ops_id' => array('integer', $a_ops_id) 195 ); 196 $ilDB->insert('rbac_ta', $fields); 197 return true; 198 } 199 200 /** 201 * Check if rbac operation exists 202 * 203 * @param int $a_type_id type id 204 * @param int $a_ops_id operation id 205 * @return bool 206 */ 207 public static function isRBACOperation($a_type_id, $a_ops_id) 208 { 209 global $ilDB; 210 211 // check if it already exists 212 $set = $ilDB->query('SELECT * FROM rbac_ta' . 213 ' WHERE typ_id = ' . $ilDB->quote($a_type_id, 'integer') . 214 ' AND ops_id = ' . $ilDB->quote($a_ops_id, 'integer')); 215 if ($ilDB->numRows($set)) { 216 return true; 217 } 218 return false; 219 } 220 221 /** 222 * Delete rbac operation 223 * 224 * @param int $a_type 225 * @param int $a_ops_id 226 */ 227 public static function deleteRBACOperation($a_type, $a_ops_id) 228 { 229 global $ilDB; 230 231 if (!$a_type || !$a_ops_id) { 232 return; 233 } 234 235 $type_id = self::getObjectTypeId($a_type); 236 if (!$type_id) { 237 return; 238 } 239 240 $query = 'DELETE FROM rbac_ta WHERE ' . 241 'typ_id = ' . $ilDB->quote($type_id, 'integer') . ' AND ' . 242 'ops_id = ' . $ilDB->quote($a_ops_id, 'integer'); 243 $GLOBALS['ilLog']->write(__METHOD__ . ': ' . $query); 244 $ilDB->manipulate($query); 245 246 self::deleteRBACTemplateOperation($a_type, $a_ops_id); 247 } 248 249 /** 250 * Delete operation for type in templates 251 * 252 * @param string $a_type 253 * @param int $a_ops_id 254 */ 255 public static function deleteRBACTemplateOperation($a_type, $a_ops_id) 256 { 257 global $ilDB; 258 259 if (!$a_type || !$a_ops_id) { 260 return; 261 } 262 263 $query = 'DELETE FROM rbac_templates WHERE ' . 264 'type = ' . $ilDB->quote($a_type, 'text') . ' AND ' . 265 'ops_id = ' . $ilDB->quote($a_ops_id, 'integer'); 266 $GLOBALS['ilLog']->write(__METHOD__ . ': ' . $query); 267 $ilDB->manipulate($query); 268 } 269 270 /** 271 * Check if given RBAC operation id is valid 272 * 273 * @param int $a_ops_id 274 * @return bool 275 */ 276 protected static function isValidRBACOperation($a_ops_id) 277 { 278 $valid = array( 279 self::RBAC_OP_EDIT_PERMISSIONS, 280 self::RBAC_OP_VISIBLE, 281 self::RBAC_OP_READ, 282 self::RBAC_OP_WRITE, 283 self::RBAC_OP_DELETE, 284 self::RBAC_OP_COPY 285 ); 286 if (in_array($a_ops_id, $valid)) { 287 return true; 288 } 289 return false; 290 } 291 292 /** 293 * Get id of RBAC operation 294 * 295 * @param string $a_operation 296 * @return int 297 */ 298 public static function getCustomRBACOperationId($a_operation) 299 { 300 global $ilDB; 301 302 $sql = 'SELECT ops_id' . 303 ' FROM rbac_operations' . 304 ' WHERE operation = ' . $ilDB->quote($a_operation, 'text'); 305 $res = $ilDB->query($sql); 306 $row = $ilDB->fetchAssoc($res); 307 return $row['ops_id']; 308 } 309 310 /** 311 * Add custom RBAC operation 312 * 313 * @param string $a_id 314 * @param string $a_title 315 * @param string $a_class 316 * @param string $a_pos 317 * @return int ops_id 318 */ 319 public static function addCustomRBACOperation($a_id, $a_title, $a_class, $a_pos) 320 { 321 global $ilDB; 322 323 // check if it already exists 324 $ops_id = self::getCustomRBACOperationId($a_id); 325 if ($ops_id) { 326 return $ops_id; 327 } 328 329 if (!in_array($a_class, array('create', 'object', 'general'))) { 330 return; 331 } 332 if ($a_class == 'create') { 333 $a_pos = 9999; 334 } 335 336 $ops_id = $ilDB->nextId('rbac_operations'); 337 338 $fields = array( 339 'ops_id' => array('integer', $ops_id), 340 'operation' => array('text', $a_id), 341 'description' => array('text', $a_title), 342 'class' => array('text', $a_class), 343 'op_order' => array('integer', $a_pos), 344 ); 345 $ilDB->insert('rbac_operations', $fields); 346 347 return $ops_id; 348 } 349 350 /** 351 * Get id for object data type entry 352 * 353 * @param string $a_type 354 * @return int 355 */ 356 public static function getObjectTypeId($a_type) 357 { 358 global $ilDB; 359 360 $sql = 'SELECT obj_id FROM object_data' . 361 ' WHERE type = ' . $ilDB->quote('typ', 'text') . 362 ' AND title = ' . $ilDB->quote($a_type, 'text'); 363 $res = $ilDB->query($sql); 364 $row = $ilDB->fetchAssoc($res); 365 return $row['obj_id']; 366 } 367 368 /** 369 * Add create RBAC operations for parent object types 370 * 371 * @param string $a_id 372 * @param string $a_title 373 * @param array $a_parent_types 374 */ 375 public static function addRBACCreate($a_id, $a_title, array $a_parent_types) 376 { 377 $ops_id = self::addCustomRBACOperation($a_id, $a_title, 'create', 9999); 378 379 foreach ($a_parent_types as $type) { 380 $type_id = self::getObjectTypeId($type); 381 if ($type_id) { 382 self::addRBACOperation($type_id, $ops_id); 383 } 384 } 385 } 386 387 /** 388 * Change order of operations 389 * 390 * @param string $a_operation 391 * @param int $a_pos 392 */ 393 public static function updateOperationOrder($a_operation, $a_pos) 394 { 395 global $ilDB; 396 397 $ilDB->update( 398 'rbac_operations', 399 array('op_order' => array('integer', $a_pos)), 400 array('operation' => array('text', $a_operation)) 401 ); 402 } 403 404 /** 405 * Create new admin object node 406 * 407 * @param string $a_id 408 * @param string $a_title 409 */ 410 public static function addAdminNode($a_obj_type, $a_title) 411 { 412 global $ilDB, $tree; 413 414 if (self::getObjectTypeId($a_obj_type)) { 415 return; 416 } 417 418 $obj_type_id = self::addNewType($a_obj_type, $a_title); 419 420 $obj_id = $ilDB->nextId('object_data'); 421 $ilDB->manipulate("INSERT INTO object_data " . 422 "(obj_id, type, title, description, owner, create_date, last_update) VALUES (" . 423 $ilDB->quote($obj_id, "integer") . "," . 424 $ilDB->quote($a_obj_type, "text") . "," . 425 $ilDB->quote($a_title, "text") . "," . 426 $ilDB->quote($a_title, "text") . "," . 427 $ilDB->quote(-1, "integer") . "," . 428 $ilDB->now() . "," . 429 $ilDB->now() . 430 ")"); 431 432 $ref_id = $ilDB->nextId('object_reference'); 433 $ilDB->manipulate("INSERT INTO object_reference " . 434 "(obj_id, ref_id) VALUES (" . 435 $ilDB->quote($obj_id, "integer") . "," . 436 $ilDB->quote($ref_id, "integer") . 437 ")"); 438 439 // put in tree 440 require_once("Services/Tree/classes/class.ilTree.php"); 441 $tree = new ilTree(ROOT_FOLDER_ID); 442 $tree->insertNode($ref_id, SYSTEM_FOLDER_ID); 443 444 $rbac_ops = array( 445 self::RBAC_OP_EDIT_PERMISSIONS, 446 self::RBAC_OP_VISIBLE, 447 self::RBAC_OP_READ, 448 self::RBAC_OP_WRITE 449 ); 450 self::addRBACOperations($obj_type_id, $rbac_ops); 451 } 452 453 /** 454 * Clone RBAC-settings between operations 455 * 456 * @param string $a_obj_type 457 * @param int $a_source_op_id 458 * @param int $a_target_op_id 459 */ 460 public static function cloneOperation($a_obj_type, $a_source_op_id, $a_target_op_id) 461 { 462 global $ilDB; 463 464 // rbac_pa 465 $sql = "SELECT rpa.*" . 466 " FROM rbac_pa rpa" . 467 " JOIN object_reference ref ON (ref.ref_id = rpa.ref_id)" . 468 " JOIN object_data od ON (od.obj_id = ref.obj_id AND od.type = " . $ilDB->quote($a_obj_type) . ")" . 469 // see ilUtil::_getObjectsByOperations() 470 " WHERE (" . $ilDB->like("ops_id", "text", "%i:" . $a_source_op_id . "%") . 471 " OR " . $ilDB->like("ops_id", "text", "%:\"" . $a_source_op_id . "\";%") . ")"; 472 $set = $ilDB->query($sql); 473 while ($row = $ilDB->fetchAssoc($set)) { 474 $ops = unserialize($row["ops_id"]); 475 // the query above could match by array KEY, we need extra checks 476 if (in_array($a_source_op_id, $ops) && !in_array($a_target_op_id, $ops)) { 477 $ops[] = $a_target_op_id; 478 479 $ilDB->manipulate("UPDATE rbac_pa" . 480 " SET ops_id = " . $ilDB->quote(serialize($ops), "text") . 481 " WHERE rol_id = " . $ilDB->quote($row["rol_id"], "integer") . 482 " AND ref_id = " . $ilDB->quote($row["ref_id"], "integer")); 483 } 484 } 485 486 // rbac_templates 487 $tmp = array(); 488 $sql = "SELECT rol_id, parent, ops_id" . 489 " FROM rbac_templates" . 490 " WHERE type = " . $ilDB->quote($a_obj_type, "text") . 491 " AND (ops_id = " . $ilDB->quote($a_source_op_id, "integer") . 492 " OR ops_id = " . $ilDB->quote($a_target_op_id) . ")"; 493 $set = $ilDB->query($sql); 494 while ($row = $ilDB->fetchAssoc($set)) { 495 $tmp[$row["rol_id"]][$row["parent"]][] = $row["ops_id"]; 496 } 497 498 foreach ($tmp as $role_id => $parents) { 499 foreach ($parents as $parent_id => $ops_ids) { 500 // only if the target op is missing 501 if (sizeof($ops_ids) < 2 && in_array($a_source_op_id, $ops_ids)) { 502 $ilDB->manipulate("INSERT INTO rbac_templates" . 503 " (rol_id, type, ops_id, parent)" . 504 " VALUES " . 505 "(" . $ilDB->quote($role_id, "integer") . 506 "," . $ilDB->quote($a_obj_type, "text") . 507 "," . $ilDB->quote($a_target_op_id, "integer") . 508 "," . $ilDB->quote($parent_id, "integer") . 509 ")"); 510 } 511 } 512 } 513 } 514 515 /** 516 * Migrate varchar column to text/clob 517 * 518 * @param string $a_table_name 519 * @param string $a_column_name 520 * @return bool 521 */ 522 public static function varchar2text($a_table_name, $a_column_name) 523 { 524 global $ilDB; 525 526 $tmp_column_name = $a_column_name . "_tmp_clob"; 527 528 if (!$ilDB->tableColumnExists($a_table_name, $a_column_name) || 529 $ilDB->tableColumnExists($a_table_name, $tmp_column_name)) { 530 return false; 531 } 532 533 // oracle does not support ALTER TABLE varchar2 to CLOB 534 535 $ilAtomQuery = $ilDB->buildAtomQuery(); 536 $ilAtomQuery->addTableLock($a_table_name); 537 538 $ilAtomQuery->addQueryCallable( 539 function (ilDBInterface $ilDB) use ($a_table_name, $a_column_name, $tmp_column_name) { 540 $def = array( 541 'type' => 'clob', 542 'notnull' => false 543 ); 544 $ilDB->addTableColumn($a_table_name, $tmp_column_name, $def); 545 546 $ilDB->manipulate('UPDATE ' . $a_table_name . ' SET ' . $tmp_column_name . ' = ' . $a_column_name); 547 548 $ilDB->dropTableColumn($a_table_name, $a_column_name); 549 550 $ilDB->renameTableColumn($a_table_name, $tmp_column_name, $a_column_name); 551 } 552 ); 553 554 $ilAtomQuery->run(); 555 556 return true; 557 } 558 559 /** 560 * Add new RBAC template 561 * 562 * @param string $a_obj_type 563 * @param string $a_id 564 * @param string $a_description 565 * @param int|array $a_op_ids 566 */ 567 public static function addRBACTemplate($a_obj_type, $a_id, $a_description, $a_op_ids) 568 { 569 global $ilDB; 570 571 $new_tpl_id = $ilDB->nextId('object_data'); 572 573 $ilDB->manipulateF( 574 "INSERT INTO object_data (obj_id, type, title, description," . 575 " owner, create_date, last_update) VALUES (%s, %s, %s, %s, %s, %s, %s)", 576 array("integer", "text", "text", "text", "integer", "timestamp", "timestamp"), 577 array($new_tpl_id, "rolt", $a_id, $a_description, -1, ilUtil::now(), ilUtil::now()) 578 ); 579 580 $ilDB->manipulateF( 581 "INSERT INTO rbac_fa (rol_id, parent, assign, protected)" . 582 " VALUES (%s, %s, %s, %s)", 583 array("integer", "integer", "text", "text"), 584 array($new_tpl_id, 8, "n", "n") 585 ); 586 587 if ($a_op_ids) { 588 if (!is_array($a_op_ids)) { 589 $a_op_ids = array($a_op_ids); 590 } 591 foreach ($a_op_ids as $op_id) { 592 $ilDB->manipulateF( 593 "INSERT INTO rbac_templates (rol_id, type, ops_id, parent)" . 594 " VALUES (%s, %s, %s, %s)", 595 array("integer", "text", "integer", "integer"), 596 array($new_tpl_id, $a_obj_type, $op_id, 8) 597 ); 598 } 599 } 600 } 601 602 public static function setRolePermission(int $a_rol_id, string $a_type, array $a_ops, int $a_ref_id) 603 { 604 global $DIC; 605 606 $ilDB = $DIC['ilDB']; 607 608 foreach ($a_ops as $ops_id) { 609 if ($ops_id == self::RBAC_OP_COPY) { 610 $ops_id = self::getCustomRBACOperationId('copy'); 611 } 612 613 $ilDB->replace( 614 'rbac_templates', 615 [ 616 'rol_id' => ['integer', $a_rol_id], 617 'type' => ['text', $a_type], 618 'ops_id' => ['integer', $ops_id], 619 'parent' => ['integer', $a_ref_id] 620 ], 621 [] 622 ); 623 } 624 } 625 626 627 /** 628 * This method will apply the 'Initial Permissions Guideline' when introducing new object types. 629 * This method does not apply permissions to existing obejcts in the ILIAS repository ('change existing objects'). 630 * @param string $objectType 631 * @param bool $hasLearningProgress A boolean flag whether or not the object type supports learning progress 632 * @param bool $usedForAuthoring A boolean flag to tell whether or not the object type is mainly used for authoring 633 * @see https://www.ilias.de/docu/goto_docu_wiki_wpage_2273_1357.html 634 */ 635 public static function applyInitialPermissionGuideline(string $objectType, bool $hasLearningProgress = false, bool $usedForAuthoring = false) 636 { 637 global $DIC; 638 639 $ilDB = $DIC['ilDB']; 640 641 $objectTypeId = self::getObjectTypeId($objectType); 642 if (!$objectTypeId) { 643 die("Something went wrong, there MUST be valid id for object_type " . $objectType); 644 } 645 646 $objectCreateOperationId = ilDBUpdateNewObjectType::getCustomRBACOperationId('create_' . $objectType); 647 if (!$objectCreateOperationId) { 648 die("Something went wrong, missing CREATE operation id for object type " . $objectType); 649 } 650 651 $globalRoleFolderId = 8; // Maybe there is another way to determine this id 652 653 $learningProgressPermissions = []; 654 if ($hasLearningProgress) { 655 $learningProgressPermissions = array_filter([ 656 self::getCustomRBACOperationId('read_learning_progress'), 657 self::getCustomRBACOperationId('edit_learning_progress'), 658 ]); 659 } 660 661 foreach (self::$initialPermissionDefinition as $roleType => $roles) { 662 foreach ($roles as $roleTitle => $definition) { 663 if ( 664 true === $usedForAuthoring && 665 array_key_exists('ignore_for_authoring_objects', $definition) && 666 true === $definition['ignore_for_authoring_objects'] 667 ) { 668 continue; 669 } 670 671 if (array_key_exists('id', $definition) && is_numeric($definition['id'])) { 672 // According to JF (2018-07-02), some roles have to be selected by if, not by title 673 $query = "SELECT obj_id FROM object_data WHERE type = %s AND obj_id = %s"; 674 $queryTypes = ['text', 'integer']; 675 $queryValues = [$roleType, $definition['id']]; 676 } else { 677 $query = "SELECT obj_id FROM object_data WHERE type = %s AND title = %s"; 678 $queryTypes = ['text', 'text']; 679 $queryValues = [$roleType, $roleTitle]; 680 } 681 682 $res = $ilDB->queryF($query, $queryTypes, $queryValues); 683 if (1 == $ilDB->numRows($res)) { 684 $row = $ilDB->fetchAssoc($res); 685 $roleId = (int) $row['obj_id']; 686 687 $operationIds = []; 688 689 if (array_key_exists('object', $definition) && is_array($definition['object'])) { 690 $operationIds = array_merge($operationIds, (array) $definition['object']); 691 } 692 693 if (array_key_exists('lp', $definition) && true === $definition['lp']) { 694 $operationIds = array_merge($operationIds, $learningProgressPermissions); 695 } 696 697 self::setRolePermission( 698 $roleId, 699 $objectType, 700 array_filter(array_map('intval', $operationIds)), 701 $globalRoleFolderId 702 ); 703 704 if (array_key_exists('create', $definition) && is_array($definition['create'])) { 705 foreach ($definition['create'] as $containerObjectType) { 706 self::setRolePermission( 707 $roleId, 708 $containerObjectType, 709 [ 710 $objectCreateOperationId 711 ], 712 $globalRoleFolderId 713 ); 714 } 715 } 716 } 717 } 718 } 719 } 720} 721