Commit 8ae049223c8e286fcce6fbc092fc41a2b745ffa3

Authored by Kevin Cyster
2 parents ecd34cf4 ed42993c

Merge branch 'master' of git@github.com:ktgit/knowledgetree

ktapi/KTAPIAcl.inc.php
... ... @@ -1719,7 +1719,7 @@ final class KTAPI_RoleAllocation extends KTAPI_AllocationBase
1719 1719  
1720 1720 $roleAllocation = RoleAllocation::getAllocationsForFolderAndRole($objectId, $roleId);
1721 1721  
1722   - $res = $oRoleAllocation->delete();
  1722 + $res = $roleAllocation->delete();
1723 1723 if (PEAR::isError($res))
1724 1724 {
1725 1725 return $res;
... ...
ktapi/KTAPIBulkActions.inc.php
... ... @@ -63,7 +63,7 @@ class KTAPI_BulkActions
63 63 * @access public
64 64 * @param KTAPI $ktapi Instance of the KTAPI object
65 65 */
66   - function __construct(&$ktapi)
  66 + public function __construct(&$ktapi)
67 67 {
68 68 $this->ktapi = $ktapi;
69 69 }
... ...
ktapi/KTAPIDocument.inc.php
... ... @@ -2438,7 +2438,7 @@ class KTAPI_Document extends KTAPI_FolderItem
2438 2438  
2439 2439 $config = KTConfig::getSingleton();
2440 2440 $allowAttachment = $config->get('email/allowAttachment', false);
2441   - $allowEmailAddresses = $oConfig->get('email/allowEmailAddresses', false);
  2441 + $allowEmailAddresses = $config->get('email/allowEmailAddresses', false);
2442 2442  
2443 2443 $emailErrors = array();
2444 2444 $userEmails = array();
... ...
ktapi/KTAPIFolder.inc.php
... ... @@ -1031,6 +1031,15 @@ class KTAPI_Folder extends KTAPI_FolderItem
1031 1031 DBUtil::rollback();
1032 1032 return new KTAPI_Error(KTAPI_ERROR_INTERNAL_ERROR, $result);
1033 1033 }
  1034 +
  1035 + // regenerate internal folder object
  1036 + $res = $this->updateObject();
  1037 +
  1038 + if (PEAR::isError($res))
  1039 + {
  1040 + DBUtil::rollback();
  1041 + return new KTAPI_Error(KTAPI_ERROR_INTERNAL_ERROR, $result);
  1042 + }
1034 1043 DBUtil::commit();
1035 1044 }
1036 1045  
... ... @@ -1132,6 +1141,20 @@ class KTAPI_Folder extends KTAPI_FolderItem
1132 1141 return $this->folder;
1133 1142 }
1134 1143  
  1144 + /**
  1145 + * Updates the Folder object
  1146 + */
  1147 + private function updateObject()
  1148 + {
  1149 + $folder = &Folder::get($this->folderid);
  1150 + if (is_null($folder) || PEAR::isError($folder))
  1151 + {
  1152 + return new KTAPI_Error(KTAPI_ERROR_FOLDER_INVALID, $folder);
  1153 + }
  1154 +
  1155 + $this->folder = $folder;
  1156 + }
  1157 +
1135 1158 /**
1136 1159 * Get the role allocation for the folder
1137 1160 *
... ...
ktapi/ktapi.inc.php
... ... @@ -288,6 +288,39 @@ class KTAPI
288 288 );
289 289  
290 290 }
  291 +
  292 + /**
  293 + * Returns folder permissions
  294 + *
  295 + * @access public
  296 + * @param string
  297 + * @param int
  298 + *
  299 + */
  300 + public function get_document_permissions($username, $document_id) {
  301 + if (is_null($this->session))
  302 + {
  303 + return array(
  304 + "status_code" => 1,
  305 + "message" => "Your session is not active"
  306 + );
  307 + }
  308 + /* We need to create a new instance of KTAPI to get another user */
  309 + $user_ktapi = new KTAPI();
  310 + $user_ktapi->start_system_session($username);
  311 +
  312 + $document = KTAPI_Document::get($user_ktapi, $document_id);
  313 +
  314 + $permissions = $document->getPermissionAllocation();
  315 +
  316 + $user_ktapi->session_logout();
  317 +
  318 + return array(
  319 + "status_code" => 0,
  320 + "results" => $permissions->permissions
  321 + );
  322 +
  323 + }
291 324  
292 325 /**
293 326 * Add folder permission
... ... @@ -1137,8 +1170,646 @@ class KTAPI
1137 1170 return $subscriptions;
1138 1171 }
1139 1172  
  1173 + /**
  1174 + * Perform a bulk action on a list of folders and documents
  1175 + * Available actions are copy, move, delete, archive, checkout, undo_checkout and immute.
  1176 + *
  1177 + * <code>
  1178 + * $ktapi = new KTAPI();
  1179 + * $session = $ktapi->start_system_session();
  1180 + *
  1181 + * $items = array();
  1182 + * $items['documents'][] = $document_id;
  1183 + * $items['folders'][] = $folder_id;
  1184 + *
  1185 + * $response = $ktapi->performBulkAction('move', $items, 'Reason for moving', $target_folder_id);
  1186 + * if($response['status_code'] != 0) return 'ERROR';
  1187 + *
  1188 + * </code>
  1189 + *
  1190 + * @author KnowledgeTree Team
  1191 + * @access public
  1192 + * @param string $action The action to be performed
  1193 + * @param array $items A list of id's and item type in the format array('documents' => array(1,6), 'folders' => array(3,4))
  1194 + * @param string $reason The reason for performing the action - only immute does not require a reason.
  1195 + * @param integer $target_folder_id The id of the target folder if required - copy and move require this.
  1196 + * @return array The response array. On success response['results'] will be empty | contain an array of failed items.
  1197 + */
  1198 + public function performBulkAction($action, $items, $reason = '', $target_folder_id = null)
  1199 + {
  1200 + $response['status_code'] = 1;
  1201 +
  1202 + if(!is_array($items)){
  1203 + $response['message'] = _kt("The list of id's must be an array of format array('documents' => array(1,2), 'folders' => array(2,3)). Received: {$items}");
  1204 + return $response;
  1205 + }
  1206 +
  1207 + if(empty($items)){
  1208 + $response['message'] = _kt('No items found to perform the action on.');
  1209 + return $response;
  1210 + }
  1211 +
  1212 + if(!is_string($action)){
  1213 + $response['message'] = _kt("The bulk action to perform must be a string. Received: {$action}");
  1214 + return $response;
  1215 + }
  1216 +
  1217 + // Check that the action exists in the bulk actions class
  1218 + $bulkActions = new ReflectionClass('KTAPI_BulkActions');
  1219 + $methods = $bulkActions->getMethods();
  1220 +
  1221 + $exists = false;
  1222 + foreach ($methods as $method){
  1223 + if($method->getName() == $action){
  1224 + $actionMethod = $method;
  1225 + $exists = true;
  1226 + break;
  1227 + }
  1228 + }
  1229 +
  1230 + if(!$exists) {
  1231 + $response['message'] = _kt("The requested action has not been implemented: {$action}");
  1232 + return $response;
  1233 + }
  1234 +
  1235 + // Create the document and folder objects
  1236 + $objects = array();
  1237 + if(isset($items['folders'])){
  1238 + foreach($items['folders'] as $item) {
  1239 + $folder = $this->get_folder_by_id($item);
  1240 + $objects[] = $folder;
  1241 + }
  1242 + }
  1243 +
  1244 + if(isset($items['documents'])){
  1245 + foreach($items['documents'] as $item) {
  1246 + $document = $this->get_document_by_id($item);
  1247 + $objects[] = $document;
  1248 + }
  1249 + }
  1250 +
  1251 + if(empty($objects)){
  1252 + $response['message'] = _kt('No folder or document items found to perform the action on.');
  1253 + return $response;
  1254 + }
  1255 +
  1256 + // perform the action
  1257 + $ktapi_bulkactions = new KTAPI_BulkActions($this);
  1258 +
  1259 + // Get target folder object if required
  1260 + if(in_array($action, array('move', 'copy'))){
  1261 + if(!is_int($target_folder_id) || empty($target_folder_id)){
  1262 + $response['message'] = _kt('No target folder has been specified.');
  1263 + return $response;
  1264 + }
  1265 + $target = $this->get_folder_by_id($target_folder_id);
  1266 +
  1267 + // call the action
  1268 + $result = $ktapi_bulkactions->$action($objects, $target, $reason);
  1269 + }else if($action == 'immute'){
  1270 + // call the action
  1271 + $result = $ktapi_bulkactions->$action($objects);
  1272 + }else {
  1273 + // call the action
  1274 + $result = $ktapi_bulkactions->$action($objects, $reason);
  1275 + }
  1276 +
  1277 + if(PEAR::isError($result)) {
  1278 + $response['message'] = _kt("The bulk action failed: {$result->getMessage()}");
  1279 + return $response;
  1280 + }
  1281 +
  1282 + // if failed items are returned - flatten the objects
  1283 + if(is_array($result)){
  1284 + if(isset($result['docs'])){
  1285 + foreach ($result['docs'] as $key => $item){
  1286 + $result['docs'][$key]['object'] = $item['object']->get_detail();
  1287 + }
  1288 + }
  1289 + if(isset($result['folders'])){
  1290 + foreach ($result['folders'] as $key => $item){
  1291 + $result['folders'][$key]['object'] = $item['object']->get_detail();
  1292 + }
  1293 + }
  1294 + }
  1295 +
  1296 + // For a successful action
  1297 + $response['status_code'] = 0;
  1298 + $response['results'] = $result;
  1299 + return $response;
  1300 + }
  1301 +
  1302 + /* *** ACL Roles and Role_Allocation *** */
  1303 +
  1304 + /**
  1305 + * Get a list of available roles
  1306 + *
  1307 + * @author KnowledgeTree Team
  1308 + * @access public
  1309 + * @param string $filter The beginning letter(s) of the role being searched for
  1310 + * @return array Response.
  1311 + */
  1312 + public function get_roles($filter = null)
  1313 + {
  1314 + $response['status_code'] = 1;
  1315 +
  1316 + // check the filter
  1317 + if(!empty($filter)) {
  1318 + if(!is_string($filter)){
  1319 + $response['message'] = _kt('Filter should be a string.');
  1320 + return $response;
  1321 + }
  1322 +
  1323 + // escape filter string - prevent sql injection
  1324 + $filter = addslashes($filter);
  1325 + $filter = "name like '{$filter}%'";
  1326 + }
  1327 +
  1328 + $listing = KTAPI_Role::getList($filter);
  1329 +
  1330 + if(PEAR::isError($listing)){
  1331 + $response['message'] = $listing->getMessage();
  1332 + return $response;
  1333 + }
  1334 +
  1335 + // flatten role objects
  1336 + $roles = array();
  1337 + foreach ($listing as $ktapi_roll) {
  1338 + $roles[] = array(
  1339 + 'id' => $ktapi_roll->getId(),
  1340 + 'name' => $ktapi_roll->getName(),
  1341 + );
  1342 + }
  1343 +
  1344 + $response['status_code'] = 0;
  1345 + $response['results'] = $roles;
  1346 + return $response;
  1347 + }
  1348 +
  1349 + /**
  1350 + * Get a role using its id
  1351 + *
  1352 + * @author KnowledgeTree Team
  1353 + * @access public
  1354 + * @param integer $role_id The id of the role
  1355 + * @return array Response
  1356 + */
  1357 + public function get_role_by_id($role_id)
  1358 + {
  1359 + $response['status_code'] = 1;
  1360 + if(!is_numeric($role_id)){
  1361 + $response['message'] = _kt('Role id must be numeric.');
  1362 + return $response;
  1363 +
  1364 + }
  1365 +
  1366 + $role = KTAPI_Role::getById($role_id);
  1367 +
  1368 + if(PEAR::isError($role)) {
  1369 + $response['message'] = $role->getMessage();
  1370 + return $response;
  1371 + }
  1372 +
  1373 + $response['status_code'] = 0;
  1374 + $response['results'] = array(
  1375 + 'id' => $role->getId(),
  1376 + 'name' => $role->getName()
  1377 + );
  1378 +
  1379 + return $response;
  1380 + }
  1381 +
  1382 + /**
  1383 + * Get a role based on its name
  1384 + *
  1385 + * @author KnowledgeTree Team
  1386 + * @access public
  1387 + * @param string $role_name The name of the role
  1388 + * @return array Response
  1389 + */
  1390 + public function get_role_by_name($role_name)
  1391 + {
  1392 + $response['status_code'] = 1;
  1393 + if(!is_string($role_name)){
  1394 + $response['message'] = _kt('Role name must be a string.');
  1395 + return $response;
  1396 +
  1397 + }
  1398 +
  1399 + $role = KTAPI_Role::getByName($role_name);
  1400 +
  1401 + if(PEAR::isError($role)) {
  1402 + $response['message'] = $role->getMessage();
  1403 + return $response;
  1404 + }
  1405 +
  1406 + $response['status_code'] = 0;
  1407 + $response['results'] = array(
  1408 + 'id' => $role->getId(),
  1409 + 'name' => $role->getName()
  1410 + );
  1411 +
  1412 + return $response;
  1413 + }
  1414 +
  1415 + /**
  1416 + * Get the list of role allocations on a folder
  1417 + *
  1418 + * @author KnowledgeTree Team
  1419 + * @access public
  1420 + * @param integar $folder_id The id of the folder
  1421 + * @return array Response
  1422 + */
  1423 + public function get_role_allocation_for_folder($folder_id)
  1424 + {
  1425 + $response['status_code'] = 1;
  1426 + if(!is_numeric($folder_id)){
  1427 + $response['message'] = _kt('Folder id must be numeric.');
  1428 + return $response;
  1429 +
  1430 + }
  1431 +
  1432 + $folder = $this->get_folder_by_id($folder_id);
  1433 +
  1434 + if(PEAR::isError($folder)) {
  1435 + $response['message'] = $folder->getMessage();
  1436 + return $response;
  1437 + }
  1438 +
  1439 + $role_allocation = $folder->getRoleAllocation();
  1440 +
  1441 + // flatten object
  1442 + $membership = $role_allocation->getMembership();
  1443 +
  1444 + $response['status_code'] = 0;
  1445 + $response['results'] = $membership;
  1446 + return $response;
  1447 + }
  1448 +
  1449 + /**
  1450 + * Add a user to a role on a folder
  1451 + *
  1452 + * @author KnowledgeTree Team
  1453 + * @access public
  1454 + * @param integer $folder_id The folder id
  1455 + * @param integer $role_id The id of the role being modified
  1456 + * @param integer $user_id The id of the user to be added
  1457 + * @return array Response
  1458 + */
  1459 + public function add_user_to_role_on_folder($folder_id, $role_id, $user_id)
  1460 + {
  1461 + $response['status_code'] = 1;
  1462 + if(!is_numeric($user_id)){
  1463 + $response['message'] = _kt('User id must be numeric.');
  1464 + return $response;
  1465 + }
  1466 + $member['users'][] = $user_id;
  1467 +
  1468 + return $this->add_members_to_role_on_folder($folder_id, $role_id, $member);
  1469 + }
  1470 +
  1471 + /**
  1472 + * Add a group to a role on a folder
  1473 + *
  1474 + * @author KnowledgeTree Team
  1475 + * @access public
  1476 + * @param integer $folder_id The folder id
  1477 + * @param integer $role_id The id of the role being modified
  1478 + * @param integer $group_id The id of the group to be added
  1479 + * @return array Response
  1480 + */
  1481 + public function add_group_to_role_on_folder($folder_id, $role_id, $group_id)
  1482 + {
  1483 + $response['status_code'] = 1;
  1484 + if(!is_numeric($group_id)){
  1485 + $response['message'] = _kt('Group id must be numeric.');
  1486 + return $response;
  1487 + }
  1488 + $member['groups'][] = $group_id;
  1489 +
  1490 + return $this->add_members_to_role_on_folder($folder_id, $role_id, $member);
  1491 + }
  1492 +
  1493 + /**
  1494 + * Remove a user from a role on a folder
  1495 + *
  1496 + * @author KnowledgeTree Team
  1497 + * @access public
  1498 + * @param integer $folder_id The folder id
  1499 + * @param integer $role_id The id of the role being modified
  1500 + * @param integer $user_id The id of the user to be removed
  1501 + * @return array Response
  1502 + */
  1503 + public function remove_user_from_role_on_folder($folder_id, $role_id, $user_id)
  1504 + {
  1505 + $response['status_code'] = 1;
  1506 + if(!is_numeric($user_id)){
  1507 + $response['message'] = _kt('User id must be numeric.');
  1508 + return $response;
  1509 + }
  1510 + $member['users'][] = $user_id;
  1511 +
  1512 + return $this->remove_members_from_role_on_folder($folder_id, $role_id, $member);
  1513 + }
  1514 +
  1515 + /**
  1516 + * Remove a group from a role on a folder
  1517 + *
  1518 + * @author KnowledgeTree Team
  1519 + * @access public
  1520 + * @param integer $folder_id The folder id
  1521 + * @param integer $role_id The id of the role being modified
  1522 + * @param integer $group_id The id of the group to be removied
  1523 + * @return array Response
  1524 + */
  1525 + public function remove_group_from_role_on_folder($folder_id, $role_id, $group_id)
  1526 + {
  1527 + $response['status_code'] = 1;
  1528 + if(!is_numeric($group_id)){
  1529 + $response['message'] = _kt('Group id must be numeric.');
  1530 + return $response;
  1531 + }
  1532 + $member['groups'][] = $group_id;
  1533 +
  1534 + return $this->remove_members_from_role_on_folder($folder_id, $role_id, $member);
  1535 + }
  1536 +
  1537 + /**
  1538 + * Remove members (user, group) from a role on a folder
  1539 + *
  1540 + * @author KnowledgeTree Team
  1541 + * @access public
  1542 + * @param integer $folder_id The folder id
  1543 + * @param integer $role_id The id of the role being modified
  1544 + * @param array $members The list of id's of members to be removed - array('users' => array(1,2), 'groups' => array(2,4))
  1545 + * @return array Response
  1546 + */
  1547 + public function remove_members_from_role_on_folder($folder_id, $role_id, $members)
  1548 + {
  1549 + return $this->update_members_on_role_on_folder($folder_id, $role_id, $members, 'remove');
  1550 + }
  1551 +
  1552 + /**
  1553 + * Add members (user, group) to a role on a folder
  1554 + *
  1555 + * @author KnowledgeTree Team
  1556 + * @access public
  1557 + * @param integer $folder_id The folder id
  1558 + * @param integer $role_id The id of the role being modified
  1559 + * @param array $members The list of id's of members to be added - array('users' => array(1,2), 'groups' => array(2,4))
  1560 + * @return array Response
  1561 + */
  1562 + public function add_members_to_role_on_folder($folder_id, $role_id, $members)
  1563 + {
  1564 + return $this->update_members_on_role_on_folder($folder_id, $role_id, $members, 'add');
  1565 + }
  1566 +
  1567 + /**
  1568 + * Add / remove members (user, group) to / from a role on a folder
  1569 + *
  1570 + * @author KnowledgeTree Team
  1571 + * @access private
  1572 + * @param integer $folder_id The folder id
  1573 + * @param integer $role_id The id of the role being modified
  1574 + * @param array $members The list of id's of members to be updated - array('users' => array(1,2), 'groups' => array(2,4))
  1575 + * @param string $update The type of modification - add | remove
  1576 + * @return array Response
  1577 + */
  1578 + private function update_members_on_role_on_folder($folder_id, $role_id, $members, $update = 'add')
  1579 + {
  1580 + // Check input information
  1581 + $response['status_code'] = 1;
  1582 + if(!is_numeric($folder_id)){
  1583 + $response['message'] = _kt('Folder id must be numeric.');
  1584 + return $response;
  1585 + }
  1586 +
  1587 + if(!is_numeric($role_id)){
  1588 + $response['message'] = _kt('Role id must be numeric.');
  1589 + return $response;
  1590 + }
  1591 +
  1592 + if(!is_array($members)){
  1593 + $response['message'] = _kt("The list of members must be in the format: array('users' => array(1,2), 'groups' => array(2,4)).')");
  1594 + return $response;
  1595 + }
  1596 +
  1597 + if(!isset($members['users']) && !isset($members['groups'])){
  1598 + $response['message'] = _kt("The list of members must be in the format: array('users' => array(1,2), 'groups' => array(2,4)).')");
  1599 + return $response;
  1600 + }
  1601 +
  1602 + // Get folder and role objects
  1603 + $folder = $this->get_folder_by_id($folder_id);
  1604 + if(PEAR::isError($folder)) {
  1605 + $response['message'] = $folder->getMessage();
  1606 + return $response;
  1607 + }
  1608 +
  1609 + $role = KTAPI_Role::getById($role_id);
  1610 + if(PEAR::isError($role)) {
  1611 + $response['message'] = $role->getMessage();
  1612 + return $response;
  1613 + }
  1614 +
  1615 + // Get the role allocation for the folder
  1616 + $role_allocation = $folder->getRoleAllocation();
  1617 +
  1618 + // Get member objects and add them to the role
  1619 + // Users
  1620 + if(isset($members['users'])){
  1621 +
  1622 + foreach($members['users'] as $user_id){
  1623 + // Get the user object
  1624 + $member = KTAPI_User::getById($user_id);
  1625 +
  1626 + if(PEAR::isError($member)) {
  1627 + $response['message'] = $member->getMessage();
  1628 + return $response;
  1629 + }
  1630 +
  1631 + // Add to / remove from the role
  1632 + $role_allocation->$update($role, $member);
  1633 + }
  1634 + }
  1635 +
  1636 + // Groups
  1637 + if(isset($members['groups'])){
  1638 +
  1639 + foreach($members['groups'] as $group_id){
  1640 + // Get the group object
  1641 + $member = KTAPI_Group::getById($group_id);
  1642 +
  1643 + if(PEAR::isError($member)) {
  1644 + $response['message'] = $member->getMessage();
  1645 + return $response;
  1646 + }
  1647 +
  1648 + // Add to / remove from the role
  1649 + $role_allocation->$update($role, $member);
  1650 + }
  1651 + }
  1652 +
  1653 + // Save the new allocations
  1654 + $role_allocation->save();
  1655 +
  1656 + $response['status_code'] = 0;
  1657 + return $response;
  1658 + }
  1659 +
  1660 + /**
  1661 + * Check if a user or group is allocated to a role on the folder
  1662 + *
  1663 + * @author KnowledgeTree Team
  1664 + * @access public
  1665 + * @param integer $folder_id The folder id
  1666 + * @param integer $role_id The id of the role being checked
  1667 + * @param integer $member_id The id of the user or group
  1668 + * @param string $member_type user | group
  1669 + * @return array Response
  1670 + */
  1671 + public function is_member_in_role_on_folder($folder_id, $role_id, $member_id, $member_type = 'user')
  1672 + {
  1673 + $response['status_code'] = 1;
  1674 +
  1675 + // Get folder and role objects
  1676 + $folder = $this->get_folder_by_id($folder_id);
  1677 + if(PEAR::isError($folder)) {
  1678 + $response['message'] = $folder->getMessage();
  1679 + return $response;
  1680 + }
  1681 +
  1682 + $role = KTAPI_Role::getById($role_id);
  1683 + if(PEAR::isError($role)) {
  1684 + $response['message'] = $role->getMessage();
  1685 + return $response;
  1686 + }
  1687 +
  1688 + // get the member object
  1689 + switch($member_type){
  1690 + case 'user':
  1691 + $member = KTAPI_User::getById($member_id);
  1692 + break;
  1693 + case 'group':
  1694 + $member = KTAPI_Group::getById($member_id);
  1695 + break;
  1696 + default:
  1697 + $response['message'] = _kt('Unrecognised member type. Must be group or user.');
  1698 + return $response;
  1699 + }
  1700 +
  1701 + if(PEAR::isError($member)) {
  1702 + $response['message'] = $member->getMessage();
  1703 + return $response;
  1704 + }
  1705 +
  1706 + // Get the role allocation for the folder
  1707 + $role_allocation = $folder->getRoleAllocation();
  1708 + $check = $role_allocation->doesRoleHaveMember($role, $member);
  1709 + $result = ($check) ? 'YES' : 'NO';
  1710 +
  1711 + $response['status_code'] = 0;
  1712 + $response['results'] = $result;
  1713 + return $response;
  1714 + }
  1715 +
  1716 + /**
  1717 + * Removes all members (users, groups) from all roles or from the specified role on the folder
  1718 + *
  1719 + * @author KnowledgeTree Team
  1720 + * @access public
  1721 + * @param integer $folder_id The folder id
  1722 + * @param integer $role_id Optional. The id of the role being reset.
  1723 + * @return array Response
  1724 + */
  1725 + public function remove_all_role_allocation_from_folder($folder_id, $role_id = null)
  1726 + {
  1727 + $response['status_code'] = 1;
  1728 +
  1729 + // Get folder and role objects
  1730 + $folder = $this->get_folder_by_id($folder_id);
  1731 + if(PEAR::isError($folder)) {
  1732 + $response['message'] = $folder->getMessage();
  1733 + return $response;
  1734 + }
  1735 +
  1736 + $role = null;
  1737 + if(!empty($role_id)){
  1738 + $role = KTAPI_Role::getById($role_id);
  1739 + if(PEAR::isError($role)) {
  1740 + $response['message'] = $role->getMessage();
  1741 + return $response;
  1742 + }
  1743 + }
  1744 +
  1745 + // Get the role allocation for the folder
  1746 + $role_allocation = $folder->getRoleAllocation();
  1747 + $role_allocation->removeAll($role);
  1748 + $role_allocation->save();
  1749 +
  1750 + $response['status_code'] = 0;
  1751 + $response['results'] = $result;
  1752 + return $response;
  1753 + }
  1754 +
  1755 + /**
  1756 + * Overrides the parents role allocation
  1757 + *
  1758 + * @author KnowledgeTree Team
  1759 + * @access public
  1760 + * @param integer $folder_id The folder id
  1761 + * @return array Response
  1762 + */
  1763 + public function override_role_allocation_on_folder($folder_id)
  1764 + {
  1765 + $response['status_code'] = 1;
  1766 +
  1767 + // Get folder object
  1768 + $folder = $this->get_folder_by_id($folder_id);
  1769 + if(PEAR::isError($folder)) {
  1770 + $response['message'] = $folder->getMessage();
  1771 + return $response;
  1772 + }
  1773 +
  1774 + // Get the role allocation for the folder
  1775 + $role_allocation = $folder->getRoleAllocation();
  1776 + $result = $role_allocation->overrideAllocation();
  1777 +
  1778 + $response['status_code'] = 0;
  1779 + $response['results'] = $result;
  1780 + return $response;
  1781 + }
  1782 +
  1783 + /**
  1784 + * Inherits the role allocation from the parent
  1785 + *
  1786 + * @author KnowledgeTree Team
  1787 + * @access public
  1788 + * @param integer $folder_id The folder id
  1789 + * @return array Response
  1790 + */
  1791 + public function inherit_role_allocation_on_folder($folder_id)
  1792 + {
  1793 + $response['status_code'] = 1;
  1794 +
  1795 + // Get folder object
  1796 + $folder = $this->get_folder_by_id($folder_id);
  1797 + if(PEAR::isError($folder)) {
  1798 + $response['message'] = $folder->getMessage();
  1799 + return $response;
  1800 + }
  1801 +
  1802 + // Get the role allocation for the folder
  1803 + $role_allocation = $folder->getRoleAllocation();
  1804 + $result = $role_allocation->inheritAllocation();
  1805 +
  1806 + $response['status_code'] = 0;
  1807 + $response['results'] = $result;
  1808 + return $response;
  1809 + }
  1810 +
1140 1811  
1141   - /* *** Refactored web services functions *** */
  1812 + /* *** Refactored web services functions *** */
1142 1813  
1143 1814  
1144 1815 /**
... ...
ktwebservice/KTWebService.php
... ... @@ -118,6 +118,7 @@ class JsonEncoder extends EncoderBase
118 118 */
119 119 public function encode($input)
120 120 {
  121 + $input = array('response' => $input);
121 122 return json_encode($input);
122 123 }
123 124 }
... ... @@ -150,11 +151,12 @@ class XmlEncoder extends EncoderBase
150 151 return false;
151 152 }
152 153  
153   - $xml = '<?xml version="1.0">'."\n";
154   - $xml .= '<response-status="ok">'."\n";
  154 + $xml = '<?xml version="1.0" encoding="utf-8" ?>'."\n";
  155 + $xml .= '<response>'."\n";
  156 +// $xml .= '<response status="ok">'."\n";
155 157  
156 158 $xml .= XmlEncoder::createXmlFromArray($input);
157   -
  159 + $xml .= '</response>'."\n";
158 160 return $xml;
159 161 }
160 162  
... ... @@ -171,6 +173,10 @@ class XmlEncoder extends EncoderBase
171 173 $xml = '';
172 174 foreach ($input as $key => $value) {
173 175  
  176 + if(is_numeric($key)){
  177 + $key = 'item';
  178 + }
  179 +
174 180 if(is_array($value)){
175 181 $value = XmlEncoder::createXmlFromArray($value);
176 182 }
... ... @@ -383,6 +389,13 @@ class Response extends ResponseBase
383 389  
384 390 // instantiate KTAPI and invoke method
385 391 $ktapi = $this->get_ktapi($session_id);
  392 +
  393 + if(PEAR::isError($ktapi)){
  394 + $this->error = 'API could not be authenticated: '.$ktapi->getMessage();
  395 + $this->error_code = 404;
  396 + return false;
  397 + }
  398 +
386 399 $result = $reflectMethod->invokeArgs($ktapi, $orderedParams);
387 400  
388 401 return $result;
... ... @@ -409,10 +422,10 @@ class Response extends ResponseBase
409 422 if(!empty($session_id)){
410 423 $session = $kt->get_active_session($session_id, null);
411 424  
412   - if ( PEAR::isError($session))
  425 + if (PEAR::isError($session))
413 426 {
414 427 // return error / exception
415   - return false;
  428 + return $session;
416 429 }
417 430 }
418 431 $this->ktapi = $kt;
... ... @@ -477,6 +490,18 @@ class Response extends ResponseBase
477 490 */
478 491 protected function _post($args)
479 492 {
  493 + $result = $this->callMethod($args);
  494 +
  495 + // if an error occurred, initiate the error response
  496 + if($result === false){
  497 + return false;
  498 + }
  499 +
  500 + $result = $this->flattenInput($result);
  501 +
  502 + $encoder = EncoderBase::getEncoder($this->responseType);
  503 + $this->output = $encoder->encode($result);
  504 + $this->headers = $encoder->getHeaders();
480 505 }
481 506  
482 507 /**
... ... @@ -489,6 +514,18 @@ class Response extends ResponseBase
489 514 */
490 515 protected function _put($args)
491 516 {
  517 + $result = $this->callMethod($args);
  518 +
  519 + // if an error occurred, initiate the error response
  520 + if($result === false){
  521 + return false;
  522 + }
  523 +
  524 + $result = $this->flattenInput($result);
  525 +
  526 + $encoder = EncoderBase::getEncoder($this->responseType);
  527 + $this->output = $encoder->encode($result);
  528 + $this->headers = $encoder->getHeaders();
492 529 }
493 530  
494 531 /**
... ... @@ -501,6 +538,18 @@ class Response extends ResponseBase
501 538 */
502 539 protected function _delete($args)
503 540 {
  541 + $result = $this->callMethod($args);
  542 +
  543 + // if an error occurred, initiate the error response
  544 + if($result === false){
  545 + return false;
  546 + }
  547 +
  548 + $result = $this->flattenInput($result);
  549 +
  550 + $encoder = EncoderBase::getEncoder($this->responseType);
  551 + $this->output = $encoder->encode($result);
  552 + $this->headers = $encoder->getHeaders();
504 553 }
505 554  
506 555 /**
... ... @@ -512,9 +561,12 @@ class Response extends ResponseBase
512 561 public function output()
513 562 {
514 563 if(!empty($this->error)){
  564 + $response = array('message' => $this->error, 'status_code' => 1);
  565 + $encoder = EncoderBase::getEncoder($this->responseType);
  566 + $this->output = $encoder->encode($response);
  567 + $this->headers = $encoder->getHeaders();
  568 +
515 569 $this->_respondError($this->error_code);
516   - echo $this->error;
517   - exit;
518 570 }
519 571  
520 572 $this->_respond($this->output, $this->headers);
... ...
ktwebservice/webservice.php
... ... @@ -1455,7 +1455,7 @@ class KTWebService
1455 1455 }
1456 1456  
1457 1457 $folder = $kt->get_folder_by_id($folder_id);
1458   - if(PEAR::isError($document)){
  1458 + if(PEAR::isError($folder)){
1459 1459 $response=array(
1460 1460 'status_code'=>KTWS_ERR_INVALID_FOLDER,
1461 1461 'message'=>$folder->getMessage()
... ... @@ -1699,7 +1699,7 @@ class KTWebService
1699 1699 }
1700 1700  
1701 1701 $source_document = &$kt->get_document_by_id($source_document_id);
1702   - if (PEAR::isError($source_folder))
  1702 + if (PEAR::isError($source_document))
1703 1703 {
1704 1704 $response = KTWebService::_status(KTWS_ERR_INVALID_DOCUMENT,$source_document);
1705 1705  
... ... @@ -2454,7 +2454,7 @@ class KTWebService
2454 2454 if (PEAR::isError($tempfilename))
2455 2455 {
2456 2456 $reason = $tempfilename->getMessage();
2457   - $response = KTWebService::_status(KTWS_ERR_INVALID_DOCUMENT,'Cannot write to temp file: ' + $tempfilename . ". Reason: $reason");
  2457 + $response = KTWebService::_status(KTWS_ERR_INVALID_DOCUMENT,'Cannot write to temp file: ' . $tempfilename . ". Reason: $reason");
2458 2458 $this->debug("add_small_document - cannot write $tempfilename. Reason: $reason", $session_id);
2459 2459  
2460 2460 return new SOAP_Value('return',"{urn:$this->namespace}kt_document_detail", $response);
... ... @@ -2639,7 +2639,7 @@ class KTWebService
2639 2639 if (PEAR::isError($tempfilename))
2640 2640 {
2641 2641 $reason = $tempfilename->getMessage();
2642   - $response = KTWebService::_status(KTWS_ERR_INVALID_DOCUMENT,'Cannot write to temp file: ' + $tempfilename . ". Reason: $reason");
  2642 + $response = KTWebService::_status(KTWS_ERR_INVALID_DOCUMENT,'Cannot write to temp file: ' . $tempfilename . ". Reason: $reason");
2643 2643 $this->debug("checkin_small_document - cannot write $tempfilename. Reason: $reason", $session_id);
2644 2644  
2645 2645 return new SOAP_Value('return',"{urn:$this->namespace}kt_document_detail", $response);
... ...
lib/documentmanagement/documentutil.inc.php
... ... @@ -995,6 +995,9 @@ $sourceDocument-&gt;getName(),
995 995 return PEAR::raiseError(_kt('There was a problem deleting the document from storage.'));
996 996 }
997 997  
  998 + // get the user object
  999 + $oUser = User::get($_SESSION['userID']);
  1000 +
998 1001 //delete all shortcuts linking to this document
999 1002 $aSymlinks = $oDocument->getSymbolicLinks();
1000 1003 foreach($aSymlinks as $aSymlink){
... ... @@ -1005,7 +1008,7 @@ $sourceDocument-&gt;getName(),
1005 1008  
1006 1009 //send an email to the owner of the shortcut
1007 1010 if($oOwnerUser->getEmail()!=null && $oOwnerUser->getEmailNotification() == true){
1008   - $emailTemplate = new EmailTemplate("kt3/notifications/notification.SymbolicLinkDeleted",array('user_name'=>$this->oUser->getName(),
  1011 + $emailTemplate = new EmailTemplate("kt3/notifications/notification.SymbolicLinkDeleted",array('user_name'=>$oUser->getName(),
1009 1012 'url'=>KTUtil::ktLink(KTBrowseUtil::getUrlForDocument($oShortcutDocument)),
1010 1013 'title' =>$oShortcutDocument->getName()));
1011 1014 $email = new EmailAlert($oOwnerUser->getEmail(),_kt("KnowledgeTree Notification"),$emailTemplate->getBody());
... ...
lib/foldermanagement/Folder.inc
... ... @@ -130,6 +130,11 @@ class Folder extends KTEntity {
130 130 //now load fields from the folder this folder is linking to, if any.
131 131 if($this->isSymbolicLink()){
132 132 $oLinkedFolder = $this->getLinkedFolder();
  133 +
  134 + if(PEAR::isError($oLinkedFolder)){
  135 + return PEAR::raiseError(_kt("Linked folder can't be found: ") . $oLinkedFolder->getMessage());
  136 + }
  137 +
133 138 $this->sName = $oLinkedFolder->getName();
134 139 $this->sDescription = $oLinkedFolder->getDescription();
135 140 }
... ...
lib/foldermanagement/folderutil.inc.php
... ... @@ -241,12 +241,13 @@ class KTFolderUtil {
241 241 // First, deal with SQL, as it, at least, is guaranteed to be atomic
242 242 $table = "folders";
243 243  
244   - if ($oFolder->getId() == 1) {
  244 + if ($oFolder->getId() == 1 || $oFolder->getParentID() == 1) {
245 245 $sOldPath = $oFolder->getName();
246 246 $sNewPath = $sNewName;
247 247 } else {
248 248 $sOldPath = $oFolder->getFullPath();
249   - $sNewPath = dirname($oFolder->getFullPath()) . '/' . $sNewName;
  249 + $sNewPathDir = !empty($sOldPath) ? dirname($sOldPath) . '/' : '';
  250 + $sNewPath = $sNewPathDir . $sNewName;
250 251 }
251 252  
252 253 $sQuery = "UPDATE $table SET full_path = CONCAT(?, SUBSTRING(full_path FROM ?)) WHERE full_path LIKE ? OR full_path = ?";
... ... @@ -400,7 +401,7 @@ class KTFolderUtil {
400 401 foreach($aFolderIds as $iFolder){
401 402 $oFolder = Folder::get($iFolder);
402 403 $aLinks = $oFolder->getSymbolicLinks();
403   - array_merge($aSymlinks, $aLinks);
  404 + $aSymlinks = array_merge($aSymlinks, $aLinks);
404 405 }
405 406  
406 407 // documents all cleared.
... ... @@ -423,11 +424,14 @@ class KTFolderUtil {
423 424 $linkIds = implode(',', $links);
424 425  
425 426 $query = "DELETE FROM folders WHERE id IN ($linkIds)";
  427 + DBUtil::runQuery($query);
426 428 }
427 429  
  430 + /*
428 431 foreach($aSymlinks as $aSymlink){
429 432 KTFolderUtil::deleteSymbolicLink($aSymlink['id']);
430 433 }
  434 + */
431 435  
432 436 // purge caches
433 437 KTEntityUtil::clearAllCaches('Folder');
... ...
tests/api/testAcl.php
... ... @@ -9,6 +9,7 @@ class APIAclTestCase extends KTUnitTestCase {
9 9 */
10 10 var $ktapi;
11 11 var $session;
  12 + var $root;
12 13  
13 14 /**
14 15 * Setup the session
... ... @@ -17,6 +18,7 @@ class APIAclTestCase extends KTUnitTestCase {
17 18 function setUp() {
18 19 $this->ktapi = new KTAPI();
19 20 $this->session = $this->ktapi->start_system_session();
  21 + $this->root = $this->ktapi->get_root_folder();
20 22 }
21 23  
22 24 /**
... ... @@ -27,6 +29,140 @@ class APIAclTestCase extends KTUnitTestCase {
27 29 $this->session->logout();
28 30 }
29 31  
  32 +
  33 + /* *** Testing KTAPI ACL functions *** */
  34 +
  35 + /**
  36 + * Testing get list of roles
  37 + */
  38 + function testGetRoles()
  39 + {
  40 + $list = $this->ktapi->get_roles();
  41 +
  42 + $this->assertEqual($list['status_code'], 0);
  43 + $this->assertTrue(!empty($list['results']));
  44 +
  45 + // filter roles - should return the "Everyone" role
  46 + $list = $this->ktapi->get_roles('Ever');
  47 +
  48 + $this->assertEqual($list['status_code'], 0);
  49 + $this->assertTrue(!empty($list['results']));
  50 + $this->assertEqual(count($list['results']), 1);
  51 + $this->assertEqual($list['results'][0]['name'], 'Everyone');
  52 + }
  53 +
  54 + /**
  55 + * Testing get role by id and name
  56 + */
  57 + function testGetRole()
  58 + {
  59 + // get by id -2 - should return system role Owner
  60 + $role = $this->ktapi->get_role_by_id(-2);
  61 +
  62 + $this->assertEqual($role['status_code'], 0);
  63 + $this->assertTrue(!empty($role['results']));
  64 + $this->assertEqual($role['results']['name'], 'Owner');
  65 +
  66 + // get by name Authenticated
  67 + $role = $this->ktapi->get_role_by_name('Authenticated Users');
  68 +
  69 + $this->assertEqual($role['status_code'], 0);
  70 + $this->assertTrue(!empty($role['results']));
  71 + $this->assertEqual($role['results']['name'], 'Authenticated Users');
  72 + $this->assertEqual($role['results']['id'], -4);
  73 + }
  74 +
  75 + /**
  76 + * Test role allocation on folders
  77 + */
  78 + function testAllocatingMembersToRoles()
  79 + {
  80 + $folder = $this->ktapi->get_folder_by_name('test123');
  81 + if(!$folder instanceof KTAPI_Folder){
  82 + $folder = $this->root->add_folder('test123');
  83 + }
  84 + $folder_id = $folder->get_folderid();
  85 +
  86 + $allocation = $this->ktapi->get_role_allocation_for_folder($folder_id);
  87 + $this->assertEqual($allocation['status_code'], 0);
  88 + $this->assertTrue(empty($allocation['results']));
  89 +
  90 + // add a user to a role
  91 + $role_id = 2; // Publisher
  92 + $user_id = 1; // Admin
  93 + $result = $this->ktapi->add_user_to_role_on_folder($folder_id, $role_id, $user_id);
  94 + $this->assertEqual($result['status_code'], 0);
  95 +
  96 + $allocation = $this->ktapi->get_role_allocation_for_folder($folder_id);
  97 + $this->assertEqual($allocation['status_code'], 0);
  98 + $this->assertTrue(isset($allocation['results']['Publisher']));
  99 + $this->assertEqual($allocation['results']['Publisher']['user'][1], 'Administrator');
  100 +
  101 + // test check on members in the role
  102 + $check = $this->ktapi->is_member_in_role_on_folder($folder_id, $role_id, $user_id, 'user');
  103 + $this->assertEqual($check['status_code'], 0);
  104 + $this->assertEqual($check['results'], 'YES');
  105 +
  106 + // remove user from a role
  107 + $result = $this->ktapi->remove_user_from_role_on_folder($folder_id, $role_id, $user_id);
  108 + $this->assertEqual($result['status_code'], 0);
  109 +
  110 + $allocation = $this->ktapi->get_role_allocation_for_folder($folder_id);
  111 + $this->assertEqual($allocation['status_code'], 0);
  112 + $this->assertFalse(isset($allocation['results']['Publisher']));
  113 +
  114 + // clean up
  115 + $folder->delete('Testing API');
  116 + }
  117 +
  118 + /**
  119 + * Test inherit and override role allocation and remove all allocations
  120 + */
  121 + function testRoleAllocationInheritance()
  122 + {
  123 + $folder = $this->ktapi->get_folder_by_name('test123');
  124 + if(!$folder instanceof KTAPI_Folder){
  125 + $folder = $this->root->add_folder('test123');
  126 + }
  127 + $folder_id = $folder->get_folderid();
  128 +
  129 + $allocation = $this->ktapi->get_role_allocation_for_folder($folder_id);
  130 + $this->assertEqual($allocation['status_code'], 0);
  131 +
  132 + // Override
  133 + $result = $this->ktapi->override_role_allocation_on_folder($folder_id);
  134 + $this->assertEqual($result['status_code'], 0);
  135 +
  136 + $role_id = 2; // Publisher
  137 + $user_id = 1; // Admin
  138 + $group_id = 1; // System Administrators
  139 + $members = array('users' => array($user_id), 'groups' => array($group_id));
  140 +
  141 + $result = $this->ktapi->add_members_to_role_on_folder($folder_id, $role_id, $members);
  142 + $this->assertEqual($result['status_code'], 0);
  143 +
  144 + $check = $this->ktapi->is_member_in_role_on_folder($folder_id, $role_id, $user_id, 'user');
  145 + $this->assertEqual($check['status_code'], 0);
  146 + $this->assertEqual($check['results'], 'YES');
  147 +
  148 + // Remove all
  149 + $result = $this->ktapi->remove_all_role_allocation_from_folder($folder_id, $role_id);
  150 + $this->assertEqual($result['status_code'], 0);
  151 +
  152 + $check = $this->ktapi->is_member_in_role_on_folder($folder_id, $role_id, $group_id, 'group');
  153 + $this->assertEqual($check['status_code'], 0);
  154 + $this->assertEqual($check['results'], 'NO');
  155 +
  156 + // Inherit
  157 + $result = $this->ktapi->inherit_role_allocation_on_folder($folder_id);
  158 + $this->assertEqual($result['status_code'], 0);
  159 +
  160 + // clean up
  161 + $folder->delete('Testing API');
  162 + }
  163 +
  164 + /* *** Testing ACL classes *** */
  165 +
30 166 /**
31 167 *
32 168 * Test KTAPI_User getList(), getById(), getByName, getByUsername()
... ...
tests/api/testApi.php
... ... @@ -449,8 +449,186 @@ class APITestCase extends KTUnitTestCase {
449 449 $document->expunge();
450 450 }
451 451  
452   - function createRandomFile($content = 'this is some text') {
453   - $temp = tempnam(dirname(__FILE__), 'myfile');
  452 + /* *** Test webservice functions *** */
  453 +
  454 + /**
  455 + * Testing folder creation and deletion, add document, get folder contents, folder detail
  456 + * Folder shortcuts and actions
  457 + */
  458 + public function testFolderApiFunctions()
  459 + {
  460 + // check for a negative result
  461 + $result = $this->ktapi->create_folder(0, 'New test error api folder');
  462 + $this->assertNotEqual($result['status_code'], 0);
  463 +
  464 + // Create a folder
  465 + $result1 = $this->ktapi->create_folder(1, 'New test api folder');
  466 + $folder_id = $result1['results']['id'];
  467 +
  468 + $this->assertEqual($result1['status_code'], 0);
  469 + $this->assertTrue($result1['results']['parent_id'] == 1);
  470 +
  471 + // Create a sub folder
  472 + $result2 = $this->ktapi->create_folder($folder_id, 'New test api sub-folder');
  473 + $folder_id2 = $result2['results']['id'];
  474 + $this->assertEqual($result2['status_code'], 0);
  475 +
  476 + // Add a document
  477 + global $default;
  478 + $dir = $default->uploadDirectory;
  479 + $tempfilename = $this->createRandomFile('some text', $dir);
  480 + $doc = $this->ktapi->add_document($folder_id, 'New API test doc', 'testdoc1.txt', 'Default', $tempfilename);
  481 +
  482 + $this->assertEqual($doc['status_code'], 0);
  483 + $this->assertEqual($doc['results']['title'], 'New API test doc');
  484 +
  485 + // Get folder 1 contents
  486 + $contents = $this->ktapi->get_folder_contents($folder_id, $depth=1, $what='DFS');
  487 + $this->assertEqual($contents['status_code'], 0);
  488 + $this->assertEqual(count($contents['results']['items']), 2);
  489 +
  490 + $detail = $this->ktapi->get_folder_detail($folder_id2);
  491 + $this->assertEqual($detail['status_code'], 0);
  492 + $this->assertTrue($detail['results']['parent_id'] == $folder_id);
  493 +
  494 + // Create a shortcut to the subfolder from the root folder
  495 + $shortcut = $this->ktapi->create_folder_shortcut(1, $folder_id2);
  496 + $this->assertEqual($shortcut['status_code'], 0);
  497 + $this->assertEqual($shortcut['results']['folder_name'], 'New test api sub-folder');
  498 + $this->assertEqual($shortcut['results']['parent_id'], 1);
  499 +
  500 + $shortcut_list = $this->ktapi->get_folder_shortcuts($folder_id2);
  501 + $this->assertEqual($shortcut['status_code'], 0);
  502 + $this->assertEqual(count($shortcut_list['results']), 1);
  503 +
  504 + // Rename the folder
  505 + $renamed = $this->ktapi->rename_folder($folder_id, 'Renamed test folder');
  506 + $this->assertEqual($renamed['status_code'], 0);
  507 +
  508 + $renamed_detail = $this->ktapi->get_folder_detail_by_name('Renamed test folder');
  509 + $this->assertEqual($renamed_detail['status_code'], 0);
  510 + $this->assertEqual($renamed_detail['results']['id'], $folder_id);
  511 +
  512 +// $this->ktapi->copy_folder($source_id, $target_id, $reason);
  513 +// $this->ktapi->move_folder($source_id, $target_id, $reason);
  514 +
  515 +
  516 + // Clean up - delete the folder
  517 + $this->ktapi->delete_folder($folder_id, 'Testing API');
  518 + $detail2 = $this->ktapi->get_folder_detail($folder_id);
  519 + $this->assertNotEqual($detail2['status_code'], 0);
  520 + }
  521 +
  522 + /**
  523 + * Testing document get, update, actions, delete, shortcuts and detail
  524 + */
  525 + public function testDocumentApiFunctions()
  526 + {
  527 + // Create a folder
  528 + $result1 = $this->ktapi->create_folder(1, 'New test api folder');
  529 + $folder_id = $result1['results']['id'];
  530 + $this->assertEqual($result1['status_code'], 0);
  531 +
  532 + // Create a sub folder
  533 + $result2 = $this->ktapi->create_folder($folder_id, 'New test api sub-folder');
  534 + $folder_id2 = $result2['results']['id'];
  535 + $this->assertEqual($result2['status_code'], 0);
  536 +
  537 + // Add a document
  538 + global $default;
  539 + $dir = $default->uploadDirectory;
  540 + $tempfilename = $this->createRandomFile('some text', $dir);
  541 + $doc = $this->ktapi->add_document($folder_id, 'New API test doc', 'testdoc1.txt', 'Default', $tempfilename);
  542 +
  543 + $doc_id = $doc['results']['document_id'];
  544 + $this->assertEqual($doc['status_code'], 0);
  545 +
  546 + // Get document detail
  547 + $detail = $this->ktapi->get_document_detail($doc_id);//, 'MLTVH');
  548 + $this->assertEqual($detail['status_code'], 0);
  549 + $this->assertEqual($detail['results']['document_type'], 'Default');
  550 + $this->assertEqual($detail['results']['folder_id'], $folder_id);
  551 +
  552 + // Get document detail - filename
  553 + $detail2 = $this->ktapi->get_document_detail_by_filename($folder_id, 'testdoc1.txt');
  554 + $this->assertEqual($detail2['status_code'], 0);
  555 + $this->assertEqual($detail2['results']['title'], 'New API test doc');
  556 +
  557 + // Get document detail - title
  558 + $detail3 = $this->ktapi->get_document_detail_by_title($folder_id, 'New API test doc');
  559 + $this->assertEqual($detail3['status_code'], 0);
  560 + $this->assertEqual($detail3['results']['filename'], 'testdoc1.txt');
  561 +
  562 + // Get document detail - name
  563 + $detail4 = $this->ktapi->get_document_detail_by_name($folder_id, 'New API test doc');
  564 + $this->assertEqual($detail4['status_code'], 0);
  565 + $this->assertEqual($detail4['results']['title'], 'New API test doc');
  566 +
  567 + // Checkout the document
  568 + $result1 = $this->ktapi->checkout_document($doc_id, 'Testing API', true);
  569 + $this->assertEqual($result1['status_code'], 0);
  570 + $this->assertTrue(!empty($result1['results']));
  571 +
  572 + // Checkin the document
  573 + $dir = $default->uploadDirectory;
  574 + $tempfilename = $this->createRandomFile('some text', $dir);
  575 + $result2 = $this->ktapi->checkin_document($doc_id, 'testdoc1.txt', 'Testing API', $tempfilename, false);
  576 +
  577 + $this->assertEqual($result2['status_code'], 0);
  578 + $this->assertEqual($result2['results']['document_id'], $doc_id);
  579 +
  580 + // Create document shortcut
  581 + $shortcut = $this->ktapi->create_document_shortcut(1, $doc_id);
  582 + $this->assertEqual($shortcut['status_code'], 0);
  583 + $this->assertEqual($shortcut['results']['title'], 'New API test doc');
  584 + $this->assertEqual($shortcut['results']['folder_id'], $folder_id);
  585 +
  586 + // Delete the document
  587 + $result3 = $this->ktapi->delete_document($doc_id, 'Testing API');
  588 + $this->assertEqual($result3['status_code'], 0);
  589 +
  590 + // Clean up - delete the folder
  591 + $this->ktapi->delete_folder($folder_id, 'Testing API');
  592 + $detail2 = $this->ktapi->get_folder_detail($folder_id);
  593 + $this->assertNotEqual($detail2['status_code'], 0);
  594 + }
  595 +
  596 + /**
  597 + * Helper function to create a document
  598 + */
  599 + function createDocument($title, $filename, $folder = null)
  600 + {
  601 + if(is_null($folder)){
  602 + $folder = $this->root;
  603 + }
  604 +
  605 + // Create a new document
  606 + $randomFile = $this->createRandomFile();
  607 + $this->assertTrue(is_file($randomFile));
  608 +
  609 + $document = $folder->add_document($title, $filename, 'Default', $randomFile);
  610 + $this->assertNotError($document);
  611 +
  612 + @unlink($randomFile);
  613 + if(PEAR::isError($document)) return false;
  614 +
  615 + return $document;
  616 + }
  617 +
  618 + /**
  619 + * Helper function to delete docs
  620 + */
  621 + function deleteDocument($document)
  622 + {
  623 + $document->delete('Testing API');
  624 + $document->expunge();
  625 + }
  626 +
  627 + function createRandomFile($content = 'this is some text', $uploadDir = null) {
  628 + if(is_null($uploadDir)){
  629 + $uploadDir = dirname(__FILE__);
  630 + }
  631 + $temp = tempnam($uploadDir, 'myfile');
454 632 $fp = fopen($temp, 'wt');
455 633 fwrite($fp, $content);
456 634 fclose($fp);
... ...
tests/api/testBulkActions.php
... ... @@ -48,6 +48,147 @@ class APIBulkActionsTestCase extends KTUnitTestCase {
48 48 $this->session->logout();
49 49 }
50 50  
  51 + /* *** Test KTAPI functions *** */
  52 +
  53 + /**
  54 + * Testing the bulk actions - copy, move, delete
  55 + */
  56 + public function testApiBulkCopyMoveDelete()
  57 + {
  58 + // Create folder and documents
  59 + $doc1 = $this->createDocument('Test Doc One', 'testdoc1.txt');
  60 + $doc2 = $this->createDocument('Test Doc Two', 'testdoc2.txt');
  61 + $folder1 = $this->root->add_folder("New test folder");
  62 + $this->assertNotError($newFolder);
  63 + if(PEAR::isError($newFolder)) return;
  64 +
  65 + $doc4 = $this->createDocument('Test Doc Four', 'testdoc4.txt', $folder1);
  66 +
  67 + $target_folder = $this->root->add_folder("New target folder");
  68 + $this->assertNotError($target_folder);
  69 + if(PEAR::isError($target_folder)) return;
  70 + $target_folder_id = $target_folder->get_folderid();
  71 +
  72 + $aItems = array();
  73 + $aItems['documents'][] = $doc1->get_documentid();
  74 + $aItems['documents'][] = $doc2->get_documentid();
  75 + $aItems['folders'][] = $folder1->get_folderid();
  76 +
  77 + // Call bulk action - copy
  78 + $response = $this->ktapi->performBulkAction('copy', $aItems, 'Testing API', $target_folder_id);
  79 +
  80 + $this->assertEqual($response['status_code'], 0);
  81 + $this->assertTrue(empty($response['results']));
  82 +
  83 + // Test move action - delete and recreate target folder
  84 + $target_folder->delete('Testing API');
  85 +
  86 + $target_folder = $this->root->add_folder("New target folder");
  87 + $this->assertNotError($target_folder);
  88 + if(PEAR::isError($target_folder)) return;
  89 + $target_folder_id = $target_folder->get_folderid();
  90 +
  91 + $response = $this->ktapi->performBulkAction('move', $aItems, 'Testing API', $target_folder_id);
  92 +
  93 + $this->assertEqual($response['status_code'], 0);
  94 + $this->assertTrue(empty($response['results']));
  95 +
  96 + $response = $this->ktapi->performBulkAction('delete', $aItems, 'Testing API');
  97 +
  98 + $this->assertEqual($response['status_code'], 0);
  99 + $this->assertTrue(empty($response['results']));
  100 +
  101 + // Delete and expunge documents and folder
  102 + $target_folder->delete('Testing API');
  103 + }
  104 +
  105 + /**
  106 + * Testing the bulk actions - checkout and cancel check out
  107 + */
  108 + public function testApiBulkCheckout()
  109 + {
  110 + // Create folder and documents
  111 + $doc1 = $this->createDocument('Test Doc One', 'testdoc1.txt');
  112 + $doc2 = $this->createDocument('Test Doc Two', 'testdoc2.txt');
  113 + $folder1 = $this->root->add_folder("New test folder");
  114 + $this->assertNotError($newFolder);
  115 + if(PEAR::isError($newFolder)) return;
  116 +
  117 + $doc4 = $this->createDocument('Test Doc Four', 'testdoc4.txt', $folder1);
  118 +
  119 + $doc1_id = $doc1->get_documentid();
  120 + $doc2_id = $doc2->get_documentid();
  121 +
  122 + $aItems = array();
  123 + $aItems['documents'][] = $doc1_id;
  124 + $aItems['documents'][] = $doc2_id;
  125 + $aItems['folders'][] = $folder1->get_folderid();
  126 +
  127 + // Call bulk action - checkout
  128 + $response = $this->ktapi->performBulkAction('checkout', $aItems, 'Testing API');
  129 +
  130 + $this->assertEqual($response['status_code'], 0);
  131 + $this->assertTrue(empty($response['results']));
  132 +
  133 + // update document object
  134 + $doc1 = $this->ktapi->get_document_by_id($doc1_id);
  135 + $this->assertTrue($doc1->is_checked_out());
  136 +
  137 + // cancel the checkout
  138 + $response = $this->ktapi->performBulkAction('undo_checkout', $aItems, 'Testing API');
  139 +
  140 + $this->assertEqual($response['status_code'], 0);
  141 + $this->assertTrue(empty($response['results']));
  142 +
  143 + // delete items
  144 + $response = $this->ktapi->performBulkAction('delete', $aItems, 'Testing API');
  145 + $this->assertEqual($response['status_code'], 0);
  146 + }
  147 +
  148 + /**
  149 + * Testing the bulk actions - checkout and cancel check out
  150 + */
  151 + public function testApiBulkImmute()
  152 + {
  153 + // Create folder and documents
  154 + $doc1 = $this->createDocument('Test Doc One', 'testdoc1.txt');
  155 + $doc2 = $this->createDocument('Test Doc Two', 'testdoc2.txt');
  156 + $folder1 = $this->root->add_folder("New test folder");
  157 + $this->assertNotError($newFolder);
  158 + if(PEAR::isError($newFolder)) return;
  159 +
  160 + $doc4 = $this->createDocument('Test Doc Four', 'testdoc4.txt', $folder1);
  161 +
  162 + $doc1_id = $doc1->get_documentid();
  163 + $doc2_id = $doc2->get_documentid();
  164 +
  165 + $aItems = array();
  166 + $aItems['documents'][] = $doc1_id;
  167 + $aItems['documents'][] = $doc2_id;
  168 + $aItems['folders'][] = $folder1->get_folderid();
  169 +
  170 + // Call bulk action - checkout
  171 + $response = $this->ktapi->performBulkAction('immute', $aItems);
  172 +
  173 + $this->assertEqual($response['status_code'], 0);
  174 + $this->assertTrue(empty($response['results']));
  175 +
  176 + // update document object
  177 + $doc1 = $this->ktapi->get_document_by_id($doc1_id);
  178 + $this->assertTrue($doc1->isImmutable());
  179 +
  180 + // remove immutability for deletion
  181 + $doc1->unimmute();
  182 + $doc2->unimmute();
  183 + $doc4->unimmute();
  184 +
  185 + // delete items
  186 + $response = $this->ktapi->performBulkAction('delete', $aItems, 'Testing API');
  187 + $this->assertEqual($response['status_code'], 0);
  188 + }
  189 +
  190 + /* *** Test Bulk actions class *** */
  191 +
51 192 /**
52 193 * Test the bulk copy functionality
53 194 */
... ... @@ -182,6 +323,9 @@ class APIBulkActionsTestCase extends KTUnitTestCase {
182 323 $this->assertTrue($doc1->is_checked_out());
183 324 $this->assertTrue($doc2->is_checked_out());
184 325 $this->assertTrue($doc3->is_checked_out());
  326 +
  327 + // refresh the doc4 document object to reflect changes
  328 + $doc4 = KTAPI_Document::get($this->ktapi, $doc4->get_documentid());
185 329 $this->assertTrue($doc4->is_checked_out());
186 330  
187 331 $res = $this->bulk->undo_checkout($aItems, 'Testing bulk undo / cancel checkout');
... ... @@ -191,6 +335,9 @@ class APIBulkActionsTestCase extends KTUnitTestCase {
191 335 $this->assertFalse($doc1->is_checked_out());
192 336 $this->assertFalse($doc2->is_checked_out());
193 337 $this->assertFalse($doc3->is_checked_out());
  338 +
  339 + // refresh the doc4 document object to reflect changes
  340 + $doc4 = KTAPI_Document::get($this->ktapi, $doc4->get_documentid());
194 341 $this->assertFalse($doc4->is_checked_out());
195 342  
196 343 // Delete and expunge documents and folder
... ... @@ -226,6 +373,9 @@ class APIBulkActionsTestCase extends KTUnitTestCase {
226 373 $this->assertTrue($doc1->isImmutable());
227 374 $this->assertTrue($doc2->isImmutable());
228 375 $this->assertTrue($doc3->isImmutable());
  376 +
  377 + // refresh the doc4 document object to reflect changes
  378 + $doc4 = KTAPI_Document::get($this->ktapi, $doc4->get_documentid());
229 379 $this->assertTrue($doc4->isImmutable());
230 380  
231 381 // remove immutability for deletion
... ... @@ -268,6 +418,9 @@ class APIBulkActionsTestCase extends KTUnitTestCase {
268 418 $this->assertTrue($doc1->is_deleted());
269 419 $this->assertTrue($doc2->is_deleted());
270 420 $this->assertTrue($doc3->is_deleted());
  421 +
  422 + // refresh the doc4 document object to reflect changes
  423 + $doc4 = KTAPI_Document::get($this->ktapi, $doc4->get_documentid());
271 424 $this->assertTrue($doc4->is_deleted());
272 425  
273 426 // Check folder has been deleted
... ... @@ -305,6 +458,9 @@ class APIBulkActionsTestCase extends KTUnitTestCase {
305 458  
306 459 $document1 = $doc1->getObject();
307 460 $this->assertTrue($document1->getStatusID() == 4);
  461 +
  462 + // refresh the doc4 document object to reflect changes
  463 + $doc4 = KTAPI_Document::get($this->ktapi, $doc4->get_documentid());
308 464 $document4 = $doc4->getObject();
309 465 $this->assertTrue($document4->getStatusID() == 4);
310 466  
... ...
tests/webservices/testRest.php 0 โ†’ 100644
  1 +<?php
  2 +require_once (KT_DIR . '/tests/test.php');
  3 +require_once (KT_DIR . '/ktapi/ktapi.inc.php');
  4 +
  5 +/**
  6 +* These are the unit tests for the main KTAPI class
  7 +*
  8 +*/
  9 +class RESTTestCase extends KTUnitTestCase {
  10 +
  11 + /**
  12 + * @var object $ktapi The main ktapi object
  13 + */
  14 + var $ktapi;
  15 +
  16 + /**
  17 + * @var object $session The KT session object
  18 + */
  19 + var $session;
  20 +
  21 + /**
  22 + * @var string $rootUrl The root server url for the rest web service
  23 + */
  24 + var $rootUrl;
  25 +
  26 + /**
  27 + * This method sets up the server url
  28 + *
  29 + */
  30 + public function setUp()
  31 + {
  32 + $url = KTUtil::kt_url();
  33 + $this->rootUrl = $url.'/ktwebservice/KTWebService.php?';
  34 + }
  35 +
  36 + /**
  37 + * This method is a placeholder
  38 + */
  39 + public function tearDown()
  40 + {
  41 + }
  42 +
  43 + /**
  44 + * Test login
  45 + */
  46 + public function testLogin()
  47 + {
  48 + // Login and authenticate
  49 + $url = $this->rootUrl.'method=login&password=admin&username=admin';
  50 + $response = $this->call($url);
  51 + $response = $response['response'];
  52 + $session_id = $response['results'];
  53 +
  54 + $this->assertEqual($response['status_code'], 0);
  55 + $this->assertTrue(!empty($response['results']));
  56 +
  57 + // Logout
  58 + $url = $this->rootUrl.'method=logout&session_id='.$session_id;
  59 + $response = $this->call($url);
  60 + $response = $response['response'];
  61 +
  62 + $this->assertEqual($response['status_code'], 0);
  63 + }
  64 +
  65 + /**
  66 + * Test the successful running of a method
  67 + */
  68 + public function testFolderDetails()
  69 + {
  70 + // Login and authenticate
  71 + $url = $this->rootUrl.'method=login&password=admin&username=admin';
  72 + $response = $this->call($url);
  73 + $response = $response['response'];
  74 +
  75 + $this->assertEqual($response['status_code'], 0);
  76 + $session_id = $response['results'];
  77 +
  78 + $url = $this->rootUrl.'method=get_folder_detail&session_id='.$session_id.'&folder_id=1';
  79 + $response = $this->call($url);
  80 + $response = $response['response'];
  81 +
  82 + $this->assertEqual($response['status_code'], 0);
  83 + $this->assertTrue(!empty($response['results']));
  84 + $this->assertEqual($response['results']['folder_name'], 'Root Folder');
  85 + $this->assertEqual($response['results']['permissions'], 'RWA');
  86 +
  87 + // Logout
  88 + $url = $this->rootUrl.'method=logout&session_id='.$session_id;
  89 + $response = $this->call($url);
  90 + $response = $response['response'];
  91 +
  92 + $this->assertEqual($response['status_code'], 0);
  93 + }
  94 +
  95 + /**
  96 + * Test incorrect authentication and no authentication
  97 + */
  98 + public function testAuthenticationError()
  99 + {
  100 + // Incorrect password
  101 + $url = $this->rootUrl.'method=login&password=random&username=admin';
  102 + $response = $this->call($url);
  103 + $response = $response['response'];
  104 +
  105 + $this->assertNotEqual($response['status_code'], 0);
  106 + $this->assertTrue(empty($response['results']));
  107 + $this->assertTrue(!empty($response['message']));
  108 +
  109 + // No session set up - use a random session id
  110 + $url = $this->rootUrl.'method=get_folder_detail&session_id=09sfirandom3828492&folder_id=1';
  111 + $response = $this->call($url);
  112 + $response = $response['response'];
  113 +
  114 + $this->assertNotEqual($response['status_code'], 0);
  115 + $this->assertTrue(empty($response['results']));
  116 + $this->assertTrue(!empty($response['message']));
  117 + }
  118 +
  119 + /**
  120 + * Test incorrect method error and the error response on incorrect parameters
  121 + */
  122 + public function testMethodErrors()
  123 + {
  124 + $url = $this->rootUrl.'method=incorrect_method&parameter=something';
  125 + $response = $this->call($url);
  126 + $response = $response['response'];
  127 +
  128 + $this->assertNotEqual($response['status_code'], 0);
  129 + $this->assertTrue(empty($response['results']));
  130 + $this->assertTrue(!empty($response['message']));
  131 +
  132 + $url = $this->rootUrl.'method=get_folder_detail&parameter=something';
  133 + $response = $this->call($url);
  134 + $response = $response['response'];
  135 +
  136 + $this->assertNotEqual($response['status_code'], 0);
  137 + $this->assertTrue(empty($response['results']));
  138 + $this->assertTrue(!empty($response['message']));
  139 + }
  140 +
  141 + /**
  142 + * Convert xml into an array structure
  143 + *
  144 + * @param unknown_type $contents
  145 + * @param unknown_type $get_attributes
  146 + * @param unknown_type $priority
  147 + * @return unknown
  148 + */
  149 + private function xml2array($contents, $get_attributes = 1, $priority = 'tag')
  150 + {
  151 + if (!function_exists('xml_parser_create'))
  152 + {
  153 + return array ();
  154 + }
  155 + $parser = xml_parser_create('');
  156 +
  157 + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8");
  158 + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  159 + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
  160 + xml_parse_into_struct($parser, trim($contents), $xml_values);
  161 + xml_parser_free($parser);
  162 +
  163 + if (!$xml_values)
  164 + return; //Hmm...
  165 +
  166 + $xml_array = array ();
  167 + $parents = array ();
  168 + $opened_tags = array ();
  169 + $arr = array ();
  170 + $current = & $xml_array;
  171 + $repeated_tag_index = array ();
  172 + foreach ($xml_values as $data)
  173 + {
  174 + unset ($attributes, $value);
  175 + extract($data);
  176 + $result = array ();
  177 + $attributes_data = array ();
  178 + if (isset ($value))
  179 + {
  180 + if ($priority == 'tag')
  181 + $result = $value;
  182 + else
  183 + $result['value'] = $value;
  184 + }
  185 + if (isset ($attributes) and $get_attributes)
  186 + {
  187 + foreach ($attributes as $attr => $val)
  188 + {
  189 + if ($priority == 'tag')
  190 + $attributes_data[$attr] = $val;
  191 + else
  192 + $result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
  193 + }
  194 + }
  195 + if ($type == "open")
  196 + {
  197 + $parent[$level -1] = & $current;
  198 + if (!is_array($current) or (!in_array($tag, array_keys($current))))
  199 + {
  200 + $current[$tag] = $result;
  201 + if ($attributes_data)
  202 + $current[$tag . '_attr'] = $attributes_data;
  203 + $repeated_tag_index[$tag . '_' . $level] = 1;
  204 + $current = & $current[$tag];
  205 + }
  206 + else
  207 + {
  208 + if (isset ($current[$tag][0]))
  209 + {
  210 + $current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
  211 + $repeated_tag_index[$tag . '_' . $level]++;
  212 + }
  213 + else
  214 + {
  215 + $current[$tag] = array (
  216 + $current[$tag],
  217 + $result
  218 + );
  219 + $repeated_tag_index[$tag . '_' . $level] = 2;
  220 + if (isset ($current[$tag . '_attr']))
  221 + {
  222 + $current[$tag]['0_attr'] = $current[$tag . '_attr'];
  223 + unset ($current[$tag . '_attr']);
  224 + }
  225 + }
  226 + $last_item_index = $repeated_tag_index[$tag . '_' . $level] - 1;
  227 + $current = & $current[$tag][$last_item_index];
  228 + }
  229 + }
  230 + elseif ($type == "complete")
  231 + {
  232 + if (!isset ($current[$tag]))
  233 + {
  234 + $current[$tag] = $result;
  235 + $repeated_tag_index[$tag . '_' . $level] = 1;
  236 + if ($priority == 'tag' and $attributes_data)
  237 + $current[$tag . '_attr'] = $attributes_data;
  238 + }
  239 + else
  240 + {
  241 + if (isset ($current[$tag][0]) and is_array($current[$tag]))
  242 + {
  243 + $current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
  244 + if ($priority == 'tag' and $get_attributes and $attributes_data)
  245 + {
  246 + $current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
  247 + }
  248 + $repeated_tag_index[$tag . '_' . $level]++;
  249 + }
  250 + else
  251 + {
  252 + $current[$tag] = array (
  253 + $current[$tag],
  254 + $result
  255 + );
  256 + $repeated_tag_index[$tag . '_' . $level] = 1;
  257 + if ($priority == 'tag' and $get_attributes)
  258 + {
  259 + if (isset ($current[$tag . '_attr']))
  260 + {
  261 + $current[$tag]['0_attr'] = $current[$tag . '_attr'];
  262 + unset ($current[$tag . '_attr']);
  263 + }
  264 + if ($attributes_data)
  265 + {
  266 + $current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
  267 + }
  268 + }
  269 + $repeated_tag_index[$tag . '_' . $level]++; //0 and 1 index is already taken
  270 + }
  271 + }
  272 + }
  273 + elseif ($type == 'close')
  274 + {
  275 + $current = & $parent[$level -1];
  276 + }
  277 + }
  278 + return ($xml_array);
  279 + }
  280 +
  281 + /**
  282 + * Use curl to run the webservice function
  283 + *
  284 + * @param unknown_type $url
  285 + * @return unknown
  286 + */
  287 + private function call($url) {
  288 + $c = curl_init($url);
  289 + curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
  290 + $xml = curl_exec($c);
  291 + curl_close($c);
  292 +
  293 + $xmlArray = $this->xml2array($xml);
  294 + return $xmlArray;
  295 + }
  296 +}
  297 +?>
0 298 \ No newline at end of file
... ...