Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 /** 17 * External learning plans webservice API tests. 18 * 19 * @package tool_lp 20 * @copyright 2015 Damyon Wiese 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23 24 defined('MOODLE_INTERNAL') || die(); 25 26 global $CFG; 27 28 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 29 30 use core_competency\api; 31 use tool_lp\external; 32 use core_competency\invalid_persistent_exception; 33 use core_competency\plan; 34 use core_competency\related_competency; 35 use core_competency\user_competency; 36 use core_competency\user_competency_plan; 37 use core_competency\plan_competency; 38 use core_competency\template; 39 use core_competency\template_competency; 40 use core_competency\course_competency_settings; 41 42 /** 43 * External learning plans webservice API tests. 44 * 45 * @package tool_lp 46 * @copyright 2015 Damyon Wiese 47 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 48 */ 49 class tool_lp_external_testcase extends externallib_advanced_testcase { 50 51 /** @var stdClass $creator User with enough permissions to create insystem context. */ 52 protected $creator = null; 53 54 /** @var stdClass $catcreator User with enough permissions to create incategory context. */ 55 protected $catcreator = null; 56 57 /** @var stdClass $category Category */ 58 protected $category = null; 59 60 /** @var stdClass $category Category */ 61 protected $othercategory = null; 62 63 /** @var stdClass $user User with enough permissions to view insystem context */ 64 protected $user = null; 65 66 /** @var stdClass $catuser User with enough permissions to view incategory context */ 67 protected $catuser = null; 68 69 /** @var int Creator role id */ 70 protected $creatorrole = null; 71 72 /** @var int User role id */ 73 protected $userrole = null; 74 75 /** 76 * Setup function- we will create a course and add an assign instance to it. 77 */ 78 protected function setUp(): void { 79 global $DB, $CFG; 80 81 $this->resetAfterTest(true); 82 83 // Create some users. 84 $creator = $this->getDataGenerator()->create_user(); 85 $user = $this->getDataGenerator()->create_user(); 86 $catuser = $this->getDataGenerator()->create_user(); 87 $catcreator = $this->getDataGenerator()->create_user(); 88 $category = $this->getDataGenerator()->create_category(); 89 $othercategory = $this->getDataGenerator()->create_category(); 90 $syscontext = context_system::instance(); 91 $catcontext = context_coursecat::instance($category->id); 92 93 // Fetching default authenticated user role. 94 $authrole = $DB->get_record('role', array('id' => $CFG->defaultuserroleid)); 95 96 // Reset all default authenticated users permissions. 97 unassign_capability('moodle/competency:competencygrade', $authrole->id); 98 unassign_capability('moodle/competency:competencymanage', $authrole->id); 99 unassign_capability('moodle/competency:competencyview', $authrole->id); 100 unassign_capability('moodle/competency:planmanage', $authrole->id); 101 unassign_capability('moodle/competency:planmanagedraft', $authrole->id); 102 unassign_capability('moodle/competency:planmanageown', $authrole->id); 103 unassign_capability('moodle/competency:planview', $authrole->id); 104 unassign_capability('moodle/competency:planviewdraft', $authrole->id); 105 unassign_capability('moodle/competency:planviewown', $authrole->id); 106 unassign_capability('moodle/competency:planviewowndraft', $authrole->id); 107 unassign_capability('moodle/competency:templatemanage', $authrole->id); 108 unassign_capability('moodle/competency:templateview', $authrole->id); 109 unassign_capability('moodle/cohort:manage', $authrole->id); 110 unassign_capability('moodle/competency:coursecompetencyconfigure', $authrole->id); 111 112 // Creating specific roles. 113 $this->creatorrole = create_role('Creator role', 'lpcreatorrole', 'learning plan creator role description'); 114 $this->userrole = create_role('User role', 'lpuserrole', 'learning plan user role description'); 115 116 assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $this->creatorrole, $syscontext->id); 117 assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id); 118 assign_capability('moodle/competency:planmanage', CAP_ALLOW, $this->creatorrole, $syscontext->id); 119 assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $this->creatorrole, $syscontext->id); 120 assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->creatorrole, $syscontext->id); 121 assign_capability('moodle/competency:planview', CAP_ALLOW, $this->creatorrole, $syscontext->id); 122 assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $this->creatorrole, $syscontext->id); 123 assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $this->creatorrole, $syscontext->id); 124 assign_capability('moodle/competency:competencygrade', CAP_ALLOW, $this->creatorrole, $syscontext->id); 125 assign_capability('moodle/cohort:manage', CAP_ALLOW, $this->creatorrole, $syscontext->id); 126 127 assign_capability('moodle/competency:competencyview', CAP_ALLOW, $this->userrole, $syscontext->id); 128 assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $syscontext->id); 129 assign_capability('moodle/competency:planviewown', CAP_ALLOW, $this->userrole, $syscontext->id); 130 assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $this->userrole, $syscontext->id); 131 132 role_assign($this->creatorrole, $creator->id, $syscontext->id); 133 role_assign($this->creatorrole, $catcreator->id, $catcontext->id); 134 role_assign($this->userrole, $user->id, $syscontext->id); 135 role_assign($this->userrole, $catuser->id, $catcontext->id); 136 137 $this->creator = $creator; 138 $this->catcreator = $catcreator; 139 $this->user = $user; 140 $this->catuser = $catuser; 141 $this->category = $category; 142 $this->othercategory = $othercategory; 143 144 accesslib_clear_all_caches_for_unit_testing(); 145 } 146 147 public function test_search_users_by_capability() { 148 global $CFG; 149 $this->resetAfterTest(true); 150 151 $dg = $this->getDataGenerator(); 152 $ux = $dg->create_user(); 153 $u1 = $dg->create_user(array('idnumber' => 'Cats', 'firstname' => 'Bob', 'lastname' => 'Dyyylan', 154 'email' => 'bobbyyy@dyyylan.com', 'phone1' => '123456', 'phone2' => '78910', 'department' => 'Marketing', 155 'institution' => 'HQ')); 156 157 // First we search with no capability assigned. 158 $this->setUser($ux); 159 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 160 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 161 $this->assertCount(0, $result['users']); 162 $this->assertEquals(0, $result['count']); 163 164 // Now we assign a different capability. 165 $usercontext = context_user::instance($u1->id); 166 $systemcontext = context_system::instance(); 167 $customrole = $this->assignUserCapability('moodle/competency:planview', $usercontext->id); 168 169 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 170 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 171 $this->assertCount(0, $result['users']); 172 $this->assertEquals(0, $result['count']); 173 174 // Now we assign a matching capability in the same role. 175 $usercontext = context_user::instance($u1->id); 176 $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $customrole); 177 178 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 179 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 180 $this->assertCount(1, $result['users']); 181 $this->assertEquals(1, $result['count']); 182 183 // Now assign another role with the same capability (test duplicates). 184 role_assign($this->creatorrole, $ux->id, $usercontext->id); 185 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 186 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 187 $this->assertCount(1, $result['users']); 188 $this->assertEquals(1, $result['count']); 189 190 // Now lets try a different user with only the role at system level. 191 $ux2 = $dg->create_user(); 192 role_assign($this->creatorrole, $ux2->id, $systemcontext->id); 193 $this->setUser($ux2); 194 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 195 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 196 $this->assertCount(1, $result['users']); 197 $this->assertEquals(1, $result['count']); 198 199 // Now lets try a different user with only the role at user level. 200 $ux3 = $dg->create_user(); 201 role_assign($this->creatorrole, $ux3->id, $usercontext->id); 202 $this->setUser($ux3); 203 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 204 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 205 $this->assertCount(1, $result['users']); 206 $this->assertEquals(1, $result['count']); 207 208 // Switch back. 209 $this->setUser($ux); 210 211 // Now add a prevent override (will change nothing because we still have an ALLOW). 212 assign_capability('moodle/competency:planmanage', CAP_PREVENT, $customrole, $usercontext->id); 213 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 214 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 215 $this->assertCount(1, $result['users']); 216 $this->assertEquals(1, $result['count']); 217 218 // Now change to a prohibit override (should prevent access). 219 assign_capability('moodle/competency:planmanage', CAP_PROHIBIT, $customrole, $usercontext->id); 220 $result = external::search_users('yyylan', 'moodle/competency:planmanage'); 221 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 222 $this->assertCount(1, $result['users']); 223 $this->assertEquals(1, $result['count']); 224 225 } 226 227 /** 228 * Ensures that overrides, as well as system permissions, are respected. 229 */ 230 public function test_search_users_by_capability_the_comeback() { 231 $this->resetAfterTest(); 232 $dg = $this->getDataGenerator(); 233 234 $master = $dg->create_user(); 235 $manager = $dg->create_user(); 236 $slave1 = $dg->create_user(array('lastname' => 'MOODLER')); 237 $slave2 = $dg->create_user(array('lastname' => 'MOODLER')); 238 $slave3 = $dg->create_user(array('lastname' => 'MOODLER')); 239 240 $syscontext = context_system::instance(); 241 $slave1context = context_user::instance($slave1->id); 242 $slave2context = context_user::instance($slave2->id); 243 $slave3context = context_user::instance($slave3->id); 244 245 // Creating a role giving the site config. 246 $roleid = $dg->create_role(); 247 assign_capability('moodle/site:config', CAP_ALLOW, $roleid, $syscontext->id, true); 248 249 // Create a role override for slave 2. 250 assign_capability('moodle/site:config', CAP_PROHIBIT, $roleid, $slave2context->id, true); 251 252 // Assigning the role. 253 // Master -> System context. 254 // Manager -> User context. 255 role_assign($roleid, $master->id, $syscontext); 256 role_assign($roleid, $manager->id, $slave1context); 257 258 // Flush accesslib. 259 accesslib_clear_all_caches_for_unit_testing(); 260 261 // Confirm. 262 // Master has system permissions. 263 $this->setUser($master); 264 $this->assertTrue(has_capability('moodle/site:config', $syscontext)); 265 $this->assertTrue(has_capability('moodle/site:config', $slave1context)); 266 $this->assertFalse(has_capability('moodle/site:config', $slave2context)); 267 $this->assertTrue(has_capability('moodle/site:config', $slave3context)); 268 269 // Manager only has permissions in slave 1. 270 $this->setUser($manager); 271 $this->assertFalse(has_capability('moodle/site:config', $syscontext)); 272 $this->assertTrue(has_capability('moodle/site:config', $slave1context)); 273 $this->assertFalse(has_capability('moodle/site:config', $slave2context)); 274 $this->assertFalse(has_capability('moodle/site:config', $slave3context)); 275 276 // Now do the test. 277 $this->setUser($master); 278 $result = external::search_users('MOODLER', 'moodle/site:config'); 279 $this->assertCount(2, $result['users']); 280 $this->assertEquals(2, $result['count']); 281 $this->assertArrayHasKey($slave1->id, $result['users']); 282 $this->assertArrayHasKey($slave3->id, $result['users']); 283 284 $this->setUser($manager); 285 $result = external::search_users('MOODLER', 'moodle/site:config'); 286 $this->assertCount(1, $result['users']); 287 $this->assertEquals(1, $result['count']); 288 $this->assertArrayHasKey($slave1->id, $result['users']); 289 } 290 291 public function test_search_users() { 292 global $CFG; 293 $this->resetAfterTest(true); 294 295 $dg = $this->getDataGenerator(); 296 $ux = $dg->create_user(); 297 $u1 = $dg->create_user(array('idnumber' => 'Cats', 'firstname' => 'Bob', 'lastname' => 'Dyyylan', 298 'email' => 'bobbyyy@dyyylan.com', 'phone1' => '123456', 'phone2' => '78910', 'department' => 'Marketing', 299 'institution' => 'HQ')); 300 $u2 = $dg->create_user(array('idnumber' => 'Dogs', 'firstname' => 'Alice', 'lastname' => 'Dyyylan', 301 'email' => 'alyyyson@dyyylan.com', 'phone1' => '33333', 'phone2' => '77777', 'department' => 'Development', 302 'institution' => 'O2')); 303 $u3 = $dg->create_user(array('idnumber' => 'Fish', 'firstname' => 'Thomas', 'lastname' => 'Xow', 304 'email' => 'fishyyy@moodle.com', 'phone1' => '77777', 'phone2' => '33333', 'department' => 'Research', 305 'institution' => 'Bob')); 306 307 // We need to give the user the capability we are searching for on each of the test users. 308 $this->setAdminUser(); 309 $usercontext = context_user::instance($u1->id); 310 $dummyrole = $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id); 311 $usercontext = context_user::instance($u2->id); 312 $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole); 313 $usercontext = context_user::instance($u3->id); 314 $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole); 315 316 $this->setUser($ux); 317 $usercontext = context_user::instance($u1->id); 318 $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole); 319 $usercontext = context_user::instance($u2->id); 320 $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole); 321 $usercontext = context_user::instance($u3->id); 322 $this->assignUserCapability('moodle/competency:planmanage', $usercontext->id, $dummyrole); 323 324 $this->setAdminUser(); 325 326 // No identity fields. 327 $CFG->showuseridentity = ''; 328 $result = external::search_users('cats', 'moodle/competency:planmanage'); 329 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 330 $this->assertCount(0, $result['users']); 331 $this->assertEquals(0, $result['count']); 332 333 // Filter by name. 334 $CFG->showuseridentity = ''; 335 $result = external::search_users('dyyylan', 'moodle/competency:planmanage'); 336 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 337 $this->assertCount(2, $result['users']); 338 $this->assertEquals(2, $result['count']); 339 $this->assertEquals($u2->id, $result['users'][0]['id']); 340 $this->assertEquals($u1->id, $result['users'][1]['id']); 341 342 // Filter by institution and name. 343 $CFG->showuseridentity = 'institution'; 344 $result = external::search_users('bob', 'moodle/competency:planmanage'); 345 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 346 $this->assertCount(2, $result['users']); 347 $this->assertEquals(2, $result['count']); 348 $this->assertEquals($u1->id, $result['users'][0]['id']); 349 $this->assertEquals($u3->id, $result['users'][1]['id']); 350 351 // Filter by id number. 352 $CFG->showuseridentity = 'idnumber'; 353 $result = external::search_users('cats', 'moodle/competency:planmanage'); 354 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 355 $this->assertCount(1, $result['users']); 356 $this->assertEquals(1, $result['count']); 357 $this->assertEquals($u1->id, $result['users'][0]['id']); 358 $this->assertEquals($u1->idnumber, $result['users'][0]['idnumber']); 359 $this->assertEmpty($result['users'][0]['email']); 360 $this->assertEmpty($result['users'][0]['phone1']); 361 $this->assertEmpty($result['users'][0]['phone2']); 362 $this->assertEmpty($result['users'][0]['department']); 363 $this->assertEmpty($result['users'][0]['institution']); 364 365 // Filter by email. 366 $CFG->showuseridentity = 'email'; 367 $result = external::search_users('yyy', 'moodle/competency:planmanage'); 368 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 369 $this->assertCount(3, $result['users']); 370 $this->assertEquals(3, $result['count']); 371 $this->assertEquals($u2->id, $result['users'][0]['id']); 372 $this->assertEquals($u2->email, $result['users'][0]['email']); 373 $this->assertEquals($u1->id, $result['users'][1]['id']); 374 $this->assertEquals($u1->email, $result['users'][1]['email']); 375 $this->assertEquals($u3->id, $result['users'][2]['id']); 376 $this->assertEquals($u3->email, $result['users'][2]['email']); 377 378 // Filter by any. 379 $CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution'; 380 $result = external::search_users('yyy', 'moodle/competency:planmanage'); 381 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 382 $this->assertCount(3, $result['users']); 383 $this->assertEquals(3, $result['count']); 384 $this->assertArrayHasKey('idnumber', $result['users'][0]); 385 $this->assertArrayHasKey('email', $result['users'][0]); 386 $this->assertArrayHasKey('phone1', $result['users'][0]); 387 $this->assertArrayHasKey('phone2', $result['users'][0]); 388 $this->assertArrayHasKey('department', $result['users'][0]); 389 $this->assertArrayHasKey('institution', $result['users'][0]); 390 391 // Switch to a user that cannot view identity fields. 392 $this->setUser($ux); 393 $CFG->showuseridentity = 'idnumber,email,phone1,phone2,department,institution'; 394 395 // Only names are included. 396 $result = external::search_users('fish'); 397 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 398 $this->assertCount(0, $result['users']); 399 $this->assertEquals(0, $result['count']); 400 401 $result = external::search_users('bob', 'moodle/competency:planmanage'); 402 $result = external_api::clean_returnvalue(external::search_users_returns(), $result); 403 $this->assertCount(1, $result['users']); 404 $this->assertEquals(1, $result['count']); 405 $this->assertEquals($u1->id, $result['users'][0]['id']); 406 $this->assertEmpty($result['users'][0]['idnumber']); 407 $this->assertEmpty($result['users'][0]['email']); 408 $this->assertEmpty($result['users'][0]['phone1']); 409 $this->assertEmpty($result['users'][0]['phone2']); 410 $this->assertEmpty($result['users'][0]['department']); 411 $this->assertEmpty($result['users'][0]['institution']); 412 } 413 414 public function test_data_for_user_competency_summary_in_plan() { 415 global $CFG; 416 417 $this->setUser($this->creator); 418 419 $dg = $this->getDataGenerator(); 420 $lpg = $dg->get_plugin_generator('core_competency'); 421 422 $f1 = $lpg->create_framework(); 423 424 $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); 425 426 $tpl = $lpg->create_template(); 427 $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1->get('id'))); 428 429 $plan = $lpg->create_plan(array('userid' => $this->user->id, 'templateid' => $tpl->get('id'), 'name' => 'Evil')); 430 431 $uc = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c1->get('id'))); 432 433 $evidence = \core_competency\external::grade_competency_in_plan($plan->get('id'), $c1->get('id'), 1, true); 434 $evidence = \core_competency\external::grade_competency_in_plan($plan->get('id'), $c1->get('id'), 2, true); 435 436 $summary = external::data_for_user_competency_summary_in_plan($c1->get('id'), $plan->get('id')); 437 $this->assertTrue($summary->usercompetencysummary->cangrade); 438 $this->assertEquals('Evil', $summary->plan->name); 439 $this->assertEquals('B', $summary->usercompetencysummary->usercompetency->gradename); 440 $this->assertEquals('B', $summary->usercompetencysummary->evidence[0]->gradename); 441 $this->assertEquals('A', $summary->usercompetencysummary->evidence[1]->gradename); 442 } 443 444 public function test_data_for_user_competency_summary() { 445 $this->setUser($this->creator); 446 447 $dg = $this->getDataGenerator(); 448 $lpg = $dg->get_plugin_generator('core_competency'); 449 $f1 = $lpg->create_framework(); 450 $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); 451 452 $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 1, true); 453 $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 2, true); 454 455 $summary = external::data_for_user_competency_summary($this->user->id, $c1->get('id')); 456 $this->assertTrue($summary->cangrade); 457 $this->assertEquals('B', $summary->usercompetency->gradename); 458 $this->assertEquals('B', $summary->evidence[0]->gradename); 459 $this->assertEquals('A', $summary->evidence[1]->gradename); 460 } 461 462 public function test_data_for_course_competency_page() { 463 $this->setAdminUser(); 464 465 $dg = $this->getDataGenerator(); 466 $lpg = $dg->get_plugin_generator('core_competency'); 467 $f1 = $lpg->create_framework(); 468 $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); 469 $course1 = $dg->create_course(array('category' => $this->category->id)); 470 $cc = api::add_competency_to_course($course1->id, $c1->get('id')); 471 472 $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 1, true); 473 $evidence = \core_competency\external::grade_competency($this->user->id, $c1->get('id'), 2, true); 474 475 $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page'); 476 $page = $pagegenerator->create_instance(array('course' => $course1->id)); 477 $page2 = $pagegenerator->create_instance(array('course' => $course1->id)); 478 479 $cm = get_coursemodule_from_instance('page', $page->id); 480 $cm2 = get_coursemodule_from_instance('page', $page2->id); 481 // Add the competency to the course module. 482 $ccm = api::add_competency_to_course_module($cm, $c1->get('id')); 483 $summary = external::data_for_course_competencies_page($course1->id, 0); 484 $summary2 = external::data_for_course_competencies_page($course1->id, $cm->id); 485 $summary3 = external::data_for_course_competencies_page($course1->id, $cm2->id); 486 487 $this->assertEquals(count($summary->competencies), 1); 488 $this->assertEquals(count($summary->competencies), count($summary2->competencies)); 489 $this->assertEquals(count($summary3->competencies), 0); 490 } 491 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body