Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge branch 'master' of https://github.com/collectiveaccess/providence

… into v1.3

Fixed app.conf conflic

Conflicts:
	app/conf/app.conf
  • Loading branch information...
commit 22acc295613b8aa926b4084bf168856cac609788 2 parents 0b611fb + 961cd6c
CollectiveAccess authored
20 app/conf/client_services.conf
@@ -10,8 +10,8 @@ notification_login_url = http://digitallibrary.hsp.org/index.php/LoginReg/form
10 10
11 11 # email address(es) to send administrative notifications to
12 12 administrative_email_addresses = [rnr@hsp.org, seth@whirl-i-gig.com]
13   -administrative_email_on_order_status = [SUBMITTED, AWAITING_PAYMENT, PROCESSED, PROCESSED_AWAITING_DIGITIZATION, COMPLETED, REOPENED]
14   -administrative_email_on_payment_status = [SENT_INVOICE, PROCESSING, PROCESSED_AWAITING_DIGITIZATION, DECLINED, RECEIVED]
  13 +administrative_email_on_order_status = [SUBMITTED, AWAITING_PAYMENT, PROCESSED, PROCESSED_AWAITING_DIGITIZATION, PROCESSED_AWAITING_MEDIA_ACCESS, COMPLETED, REOPENED]
  14 +administrative_email_on_payment_status = [SENT_INVOICE, PROCESSING, PROCESSED_AWAITING_DIGITIZATION, PROCESSED_AWAITING_MEDIA_ACCESS, DECLINED, RECEIVED]
15 15
16 16 # Service to use for processing of credit card payments
17 17 #
@@ -49,6 +49,22 @@ fulfillment_methods = {
49 49 }
50 50 }
51 51
  52 +
  53 +# Remote media storage access
  54 +# If high-resolution media is not stored in this system you can pull them on-demand
  55 +# from another CA instance using the configuration options below. On-demand pull of high-resolution
  56 +# to this commerce system is accomplished using media URLs discovered via the itemInfo
  57 +# getObjectRepresentationURLByMD5() web service.
  58 +
  59 +# Base URL (everything before "index.php" or "service.php") of instance to pull media from
  60 +remote_media_base_url = http://test.com/admin
  61 +
  62 +# Remote instance login
  63 +# To ensure these are not accidentally made visible in a web-served configuration file
  64 +# you can place them in setup.php and then references them here using the constants below
  65 +remote_media_username = __CA_CLIENT_SERVICES_REMOTE_MEDIA_USERNAME__
  66 +remote_media_password = __CA_CLIENT_SERVICES_REMOTE_MEDIA_PASSWORD__
  67 +
52 68 # Set disposal policy determines what is done with a user's set once
53 69 # an order is created from it. Possible values are:
54 70 #
22 app/helpers/mailHelpers.php
@@ -62,11 +62,13 @@
62 62 * $pa_bcc: Email address(es) of bcc'ed message recipients. Can be a string containing a single email address or
63 63 * an associative array with keys set to multiple addresses and corresponding values optionally set to
64 64 * a human-readable recipient name. (optional)
  65 + * $pa_attachment: array containing file path, name and mime_type of file to attach.
  66 + * keys are "path", "name", "mime_type"
65 67 *
66 68 * While both $ps_body_text and $ps_html_text are optional, at least one should be set and both can be set for a
67 69 * combination text and HTML email
68 70 */
69   - function caSendmail($pa_to, $pa_from, $ps_subject, $ps_body_text, $ps_body_html='', $pa_cc=null, $pa_bcc=null) {
  71 + function caSendmail($pa_to, $pa_from, $ps_subject, $ps_body_text, $ps_body_html='', $pa_cc=null, $pa_bcc=null, $pa_attachment=null) {
70 72 $o_config = Configuration::load();
71 73 $o_log = new Eventlog();
72 74
@@ -152,7 +154,19 @@ function caSendmail($pa_to, $pa_from, $ps_subject, $ps_body_text, $ps_body_html=
152 154 }
153 155 }
154 156
155   -
  157 + if(is_array($pa_attachment) && $pa_attachment["path"]){
  158 + $ps_attachment_url = $pa_attachment["path"];
  159 + $vs_file_contents = file_get_contents($ps_attachment_url);
  160 +
  161 + $o_attachment = $o_mail->createAttachment($vs_file_contents);
  162 + if($pa_attachment["name"]){
  163 + $o_attachment->filename = $pa_attachment["name"];
  164 + }
  165 + if($pa_attachment["mime_type"]){
  166 + $o_attachment->type = $pa_attachment["mime_type"];
  167 + }
  168 + }
  169 +
156 170 $o_mail->setSubject($ps_subject);
157 171 if ($ps_body_text) {
158 172 $o_mail->setBodyText($ps_body_text);
@@ -224,7 +238,9 @@ function caCheckEmailAddressRegex($ps_address) {
224 238 * @return string True if send, false if error
225 239 */
226 240 function caSendMessageUsingView($po_request, $pa_to, $pa_from, $ps_subject, $ps_view, $pa_values, $pa_cc=null, $pa_bcc=null) {
227   - $o_view = new View(null, $po_request->getViewsDirectoryPath()."/mailTemplates");
  241 + $vs_view_path = (is_object($po_request)) ? $po_request->getViewsDirectoryPath() : __CA_BASE_DIR__.'/themes/default/views';
  242 +
  243 + $o_view = new View(null, $vs_view_path."/mailTemplates");
228 244 foreach($pa_values as $vs_key => $vm_val) {
229 245 $o_view->setVar($vs_key, $vm_val);
230 246 }
4 app/helpers/preload.php
@@ -7,7 +7,7 @@
7 7 * ----------------------------------------------------------------------
8 8 *
9 9 * Software by Whirl-i-Gig (http://www.whirl-i-gig.com)
10   - * Copyright 2008-2009 Whirl-i-Gig
  10 + * Copyright 2008-2012 Whirl-i-Gig
11 11 *
12 12 * For more information visit http://www.CollectiveAccess.org
13 13 *
@@ -54,6 +54,8 @@
54 54
55 55 require(__CA_LIB_DIR__."/core/Controller/ActionController.php");
56 56
  57 + require(__CA_MODELS_DIR__."/ca_acl.php");
  58 +
57 59 // initialize Tooltip manager
58 60 TooltipManager::init();
59 61 ?>
90 app/lib/ca/BaseEditorController.php
@@ -95,6 +95,16 @@ public function Edit($pa_values=null, $pa_options=null) {
95 95 }
96 96
97 97 //
  98 + // Does user have access to row?
  99 + //
  100 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  101 + if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
  102 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  103 + return;
  104 + }
  105 + }
  106 +
  107 + //
98 108 // Are we duplicating?
99 109 //
100 110 if (($vs_mode == 'dupe') && $this->request->user->canDoAction('can_duplicate_'.$t_subject->tableName())) {
@@ -204,6 +214,16 @@ public function Save($pa_options=null) {
204 214 $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2560?r='.urlencode($this->request->getFullUrlPath()));
205 215 return;
206 216 }
  217 +
  218 + //
  219 + // Does user have access to row?
  220 + //
  221 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  222 + if ($t_subject->checkACLAccessForUser($this->request->user) < __CA_ACL_EDIT_ACCESS__) {
  223 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  224 + return;
  225 + }
  226 + }
207 227
208 228 if($vn_above_id) {
209 229 // Convert "above" id (the id of the record we're going to make the newly created record parent of
@@ -350,6 +370,16 @@ public function Delete($pa_options=null) {
350 370 $vs_type_name = $t_subject->getProperty('NAME_SINGULAR');
351 371 }
352 372
  373 + //
  374 + // Does user have access to row?
  375 + //
  376 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  377 + if ($t_subject->checkACLAccessForUser($this->request->user) < __CA_ACL_EDIT_DELETE_ACCESS__) {
  378 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  379 + return;
  380 + }
  381 + }
  382 +
353 383 // get parent_id, if it exists, prior to deleting so we can
354 384 // set the browse_last_id parameter to something sensible
355 385 $vn_parent_id = null;
@@ -463,6 +493,16 @@ public function Summary($pa_options=null) {
463 493 return;
464 494 }
465 495
  496 + //
  497 + // Does user have access to row?
  498 + //
  499 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  500 + if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
  501 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  502 + return;
  503 + }
  504 + }
  505 +
466 506 $t_display = new ca_bundle_displays();
467 507 $va_displays = $t_display->getBundleDisplays(array('table' => $t_subject->tableNum(), 'user_id' => $this->request->getUserID(), 'access' => __CA_BUNDLE_DISPLAY_READ_ACCESS__));
468 508
@@ -533,6 +573,16 @@ public function PrintSummary($pa_options=null) {
533 573 return;
534 574 }
535 575
  576 + //
  577 + // Does user have access to row?
  578 + //
  579 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  580 + if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
  581 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  582 + return;
  583 + }
  584 + }
  585 +
536 586
537 587 $t_display = new ca_bundle_displays();
538 588 $va_displays = $t_display->getBundleDisplays(array('table' => $t_subject->tableNum(), 'user_id' => $this->request->getUserID(), 'access' => __CA_BUNDLE_DISPLAY_READ_ACCESS__));
@@ -615,6 +665,16 @@ public function Log($pa_options=null) {
615 665 return;
616 666 }
617 667
  668 + //
  669 + // Does user have access to row?
  670 + //
  671 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  672 + if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
  673 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  674 + return;
  675 + }
  676 + }
  677 +
618 678 $this->render('log_html.php');
619 679 }
620 680 # -------------------------------------------------------
@@ -639,6 +699,16 @@ public function Access($pa_options=null) {
639 699 return;
640 700 }
641 701
  702 + //
  703 + // Does user have access to row?
  704 + //
  705 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  706 + if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
  707 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  708 + return;
  709 + }
  710 + }
  711 +
642 712 if ((!$this->request->user->canDoAction('can_change_acl_'.$t_subject->tableName()))) {
643 713 $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2570?r='.urlencode($this->request->getFullUrlPath()));
644 714 return;
@@ -767,6 +837,16 @@ public function DownloadFile() {
767 837 list($vn_subject_id, $t_subject) = $this->_initView();
768 838 if (!($pn_value_id = $this->request->getParameter('value_id', pInteger))) { return; }
769 839
  840 + //
  841 + // Does user have access to row?
  842 + //
  843 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  844 + if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
  845 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  846 + return;
  847 + }
  848 + }
  849 +
770 850 $o_view = new View($this->request, $this->request->getViewsDirectoryPath().'/bundles/');
771 851
772 852 // TODO: check that file is part of item user has access rights for
@@ -802,6 +882,16 @@ public function DownloadMedia($pa_options=null) {
802 882 if (!($pn_value_id = $this->request->getParameter('value_id', pInteger))) { return; }
803 883 $ps_version = $this->request->getParameter('version', pString);
804 884
  885 + //
  886 + // Does user have access to row?
  887 + //
  888 + if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
  889 + if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
  890 + $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
  891 + return;
  892 + }
  893 + }
  894 +
805 895 // TODO: check that file is part of item user has access rights for
806 896 $t_attr_val = new ca_attribute_values($pn_value_id);
807 897 if (!$t_attr_val->getPrimaryKey()) { return; }
324 app/lib/ca/Browse/BrowseEngine.php
@@ -37,13 +37,15 @@
37 37 require_once(__CA_LIB_DIR__.'/core/BaseObject.php');
38 38 require_once(__CA_LIB_DIR__.'/core/Datamodel.php');
39 39 require_once(__CA_LIB_DIR__.'/core/Db.php');
40   - require_once(__CA_MODELS_DIR__.'/ca_metadata_elements.php');
41   - require_once(__CA_MODELS_DIR__.'/ca_lists.php');
42 40 require_once(__CA_LIB_DIR__.'/ca/Browse/BrowseResult.php');
43 41 require_once(__CA_LIB_DIR__.'/ca/Browse/BrowseCache.php');
44 42 require_once(__CA_LIB_DIR__.'/core/Parsers/TimeExpressionParser.php');
45 43 require_once(__CA_APP_DIR__.'/helpers/searchHelpers.php');
46 44 require_once(__CA_APP_DIR__.'/helpers/accessHelpers.php');
  45 +
  46 + require_once(__CA_MODELS_DIR__.'/ca_metadata_elements.php');
  47 + require_once(__CA_MODELS_DIR__.'/ca_lists.php');
  48 + require_once(__CA_MODELS_DIR__.'/ca_acl.php');
47 49
48 50 class BrowseEngine extends BaseObject {
49 51 # ------------------------------------------------------
@@ -696,9 +698,12 @@ public function getInfoForFacetsWithContent() {
696 698 * no_cache = don't use cached browse results
697 699 * showDeleted = if set to true, related items that have been deleted are returned. Default is false.
698 700 * limitToModifiedOn = if set returned results will be limited to rows modified within the specified date range. The value should be a date/time expression parse-able by TimeExpressionParser
  701 + * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
699 702 */
700 703 public function execute($pa_options=null) {
  704 + global $AUTH_CURRENT_USER_ID;
701 705 if (!is_array($pa_options)) { $pa_options = array(); }
  706 + $vn_user_id = (isset($pa_options['user_id']) && (int)$pa_options['user_id']) ? (int)$pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
702 707 if (!is_array($this->opa_browse_settings)) { return null; }
703 708
704 709 $va_params = $this->opo_ca_browse_cache->getParameters();
@@ -1308,7 +1313,7 @@ public function execute($pa_options=null) {
1308 1313 $this->_dropTempTable('ca_browses_acc');
1309 1314 $this->_dropTempTable('ca_browses_tmp');
1310 1315
1311   - $this->opo_ca_browse_cache->setResults($va_results);
  1316 + $this->opo_ca_browse_cache->setResults(($this->opo_config->get('perform_item_level_access_checking')) ? array_keys($this->filterHitsByACL(array_flip($va_results), $vn_user_id, __CA_ACL_READONLY_ACCESS__)): $va_results);
1312 1317 $vb_need_to_save_in_cache = true;
1313 1318 }
1314 1319 } else {
@@ -1360,7 +1365,7 @@ public function execute($pa_options=null) {
1360 1365 ");
1361 1366 $va_results = $qr_res->getAllFieldValues($vs_pk);
1362 1367
1363   - $this->opo_ca_browse_cache->setResults($va_results);
  1368 + $this->opo_ca_browse_cache->setResults(($this->opo_config->get('perform_item_level_access_checking')) ? array_keys($this->filterHitsByACL(array_flip($va_results), $vn_user_id, __CA_ACL_READONLY_ACCESS__)): $va_results);
1364 1369 $vb_need_to_save_in_cache = true;
1365 1370 }
1366 1371 }
@@ -1440,8 +1445,20 @@ public function getFacet($ps_facet_name, $pa_options=null) {
1440 1445 * Options:
1441 1446 * checkAccess = array of access values to filter facets that have an 'access' field by
1442 1447 * checkAvailabilityOnly = if true then content is not actually fetch - only the availablility of content is verified
  1448 + * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
1443 1449 */
1444 1450 public function getFacetContent($ps_facet_name, $pa_options=null) {
  1451 + global $AUTH_CURRENT_USER_ID;
  1452 + $vn_user_id = (isset($pa_options['user_id']) && (int)$pa_options['user_id']) ? (int)$pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
  1453 + $vb_show_if_no_acl = (bool)($this->opo_config->get('default_item_access_level') > __CA_ACL_NO_ACCESS__);
  1454 +
  1455 + $t_user = new ca_users($vn_user_id);
  1456 + if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
  1457 + $va_group_ids = array_keys($va_groups);
  1458 + } else {
  1459 + $va_group_ids = array();
  1460 + }
  1461 +
1445 1462 if (!is_array($this->opa_browse_settings)) { return null; }
1446 1463 if (!isset($this->opa_browse_settings['facets'][$ps_facet_name])) { return null; }
1447 1464 if (!is_array($pa_options)) { $pa_options = array(); }
@@ -1531,11 +1548,11 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1531 1548 );
1532 1549
1533 1550 $vs_cur_table = array_shift($va_path);
1534   - $va_joins = array();
  1551 + $va_joins_init = array();
1535 1552
1536 1553 foreach($va_path as $vs_join_table) {
1537 1554 $va_rel_info = $this->opo_datamodel->getRelationships($vs_cur_table, $vs_join_table);
1538   - $va_joins[] = ($vn_state ? 'INNER' : 'LEFT').' JOIN '.$vs_join_table.' ON '.$vs_cur_table.'.'.$va_rel_info[$vs_cur_table][$vs_join_table][0][0].' = '.$vs_join_table.'.'.$va_rel_info[$vs_cur_table][$vs_join_table][0][1]."\n";
  1555 + $va_joins_init[] = ($vn_state ? 'INNER' : 'LEFT').' JOIN '.$vs_join_table.' ON '.$vs_cur_table.'.'.$va_rel_info[$vs_cur_table][$vs_join_table][0][0].' = '.$vs_join_table.'.'.$va_rel_info[$vs_cur_table][$vs_join_table][0][1]."\n";
1539 1556 $vs_cur_table = $vs_join_table;
1540 1557 }
1541 1558
@@ -1543,8 +1560,7 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1543 1560 $va_counts = array();
1544 1561 foreach($va_facet_values as $vs_state_name => $va_state_info) {
1545 1562 $va_wheres = array();
1546   -
1547   - $vs_join_sql = join("\n", $va_joins);
  1563 + $va_joins = $va_joins_init;
1548 1564
1549 1565 if ((sizeof($va_restrict_to_relationship_types) > 0) && is_object($t_item_rel)) {
1550 1566 $va_wheres[] = "(".$t_item_rel->tableName().".type_id IN (".join(',', $va_restrict_to_relationship_types)."))";
@@ -1573,6 +1589,24 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1573 1589 $va_wheres[] = $this->ops_browse_table_name.".".$t_item->primaryKey()." IN (".join(",", $va_results).")";
1574 1590 }
1575 1591
  1592 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  1593 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  1594 + // Join to limit what browse table items are used to generate facet
  1595 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  1596 + $va_wheres[] = "(
  1597 + ((
  1598 + (ca_acl.user_id = ".(int)$vn_user_id.")
  1599 + ".((sizeof($va_group_ids) > 0) ? "OR
  1600 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  1601 + OR
  1602 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  1603 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  1604 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  1605 + )";
  1606 + }
  1607 + }
  1608 + $vs_join_sql = join("\n", $va_joins);
  1609 +
1576 1610 $vs_where_sql = '';
1577 1611 if (sizeof($va_wheres) > 0) {
1578 1612 $vs_where_sql = ' WHERE '.join(' AND ', $va_wheres);
@@ -1632,6 +1666,8 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1632 1666 $vb_needs_join = false;
1633 1667
1634 1668 $va_where_sql = array();
  1669 + $va_joins = array();
  1670 +
1635 1671 if (sizeof($va_results)) {
1636 1672 $va_where_sql[] = "l.{$vs_item_pk} IN (".join(",", $va_results).")";
1637 1673 }
@@ -1663,10 +1699,30 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1663 1699 }
1664 1700 }
1665 1701
  1702 +
1666 1703 if ($vb_needs_join) {
1667   - $vs_join_sql = "INNER JOIN ".$this->ops_browse_table_name." ON ".$this->ops_browse_table_name.".".$t_item->primaryKey()." = l.".$t_item->primaryKey();
  1704 + $va_joins[] = "INNER JOIN ".$this->ops_browse_table_name." ON ".$this->ops_browse_table_name.".".$t_item->primaryKey()." = l.".$t_item->primaryKey();
1668 1705 }
1669 1706
  1707 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  1708 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  1709 + // Join to limit what browse table items are used to generate facet
  1710 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  1711 + $va_where_sql[] = "(
  1712 + ((
  1713 + (ca_acl.user_id = ".(int)$vn_user_id.")
  1714 + ".((sizeof($va_group_ids) > 0) ? "OR
  1715 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  1716 + OR
  1717 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  1718 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  1719 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  1720 + )";
  1721 + }
  1722 + }
  1723 +
  1724 + $vs_join_sql = join("\n", $va_joins);
  1725 +
1670 1726 if (sizeof($va_where_sql)) {
1671 1727 $vs_where_sql = "WHERE ".join(" AND ", $va_where_sql);
1672 1728 }
@@ -1768,6 +1824,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1768 1824 $va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
1769 1825 }
1770 1826
  1827 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  1828 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  1829 + // Join to limit what browse table items are used to generate facet
  1830 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  1831 + $va_wheres[] = "(
  1832 + ((
  1833 + (ca_acl.user_id = ".(int)$vn_user_id.")
  1834 + ".((sizeof($va_group_ids) > 0) ? "OR
  1835 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  1836 + OR
  1837 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  1838 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  1839 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  1840 + )";
  1841 + }
  1842 + }
  1843 +
1771 1844 $vs_join_sql = join("\n", $va_joins);
1772 1845 if (is_array($va_wheres) && sizeof($va_wheres) && ($vs_where_sql = join(' AND ', $va_wheres))) {
1773 1846 $vs_where_sql = ' AND ('.$vs_where_sql.')';
@@ -1912,6 +1985,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1912 1985 $va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
1913 1986 }
1914 1987
  1988 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  1989 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  1990 + // Join to limit what browse table items are used to generate facet
  1991 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  1992 + $va_wheres[] = "(
  1993 + ((
  1994 + (ca_acl.user_id = ".(int)$vn_user_id.")
  1995 + ".((sizeof($va_group_ids) > 0) ? "OR
  1996 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  1997 + OR
  1998 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  1999 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  2000 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  2001 + )";
  2002 + }
  2003 + }
  2004 +
1915 2005 $vs_join_sql = join("\n", $va_joins);
1916 2006
1917 2007 if (is_array($va_wheres) && sizeof($va_wheres) && ($vs_where_sql = join(' AND ', $va_wheres))) {
@@ -1988,6 +2078,24 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
1988 2078 $va_wheres[] = $vs_browse_type_limit_sql;
1989 2079 }
1990 2080
  2081 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  2082 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  2083 + // Join to limit what browse table items are used to generate facet
  2084 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  2085 + $va_wheres[] = "(
  2086 + ((
  2087 + (ca_acl.user_id = ".(int)$vn_user_id.")
  2088 + ".((sizeof($va_group_ids) > 0) ? "OR
  2089 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  2090 + OR
  2091 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  2092 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  2093 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  2094 + )";
  2095 + }
  2096 + }
  2097 +
  2098 +
1991 2099 if (is_array($va_wheres) && sizeof($va_wheres) && ($vs_where_sql = join(' AND ', $va_wheres))) {
1992 2100 $vs_where_sql = '('.$vs_where_sql.')';
1993 2101 }
@@ -2066,6 +2174,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
2066 2174 $va_wheres[] = $vs_browse_type_limit_sql;
2067 2175 }
2068 2176
  2177 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  2178 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  2179 + // Join to limit what browse table items are used to generate facet
  2180 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  2181 + $va_wheres[] = "(
  2182 + ((
  2183 + (ca_acl.user_id = ".(int)$vn_user_id.")
  2184 + ".((sizeof($va_group_ids) > 0) ? "OR
  2185 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  2186 + OR
  2187 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  2188 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  2189 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  2190 + )";
  2191 + }
  2192 + }
  2193 +
2069 2194 $vs_join_sql = join("\n", $va_joins);
2070 2195
2071 2196 if (is_array($va_wheres) && sizeof($va_wheres) && ($vs_where_sql = join(' AND ', $va_wheres))) {
@@ -2156,6 +2281,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
2156 2281 $va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
2157 2282 }
2158 2283
  2284 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  2285 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  2286 + // Join to limit what browse table items are used to generate facet
  2287 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  2288 + $va_wheres[] = "(
  2289 + ((
  2290 + (ca_acl.user_id = ".(int)$vn_user_id.")
  2291 + ".((sizeof($va_group_ids) > 0) ? "OR
  2292 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  2293 + OR
  2294 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  2295 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  2296 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  2297 + )";
  2298 + }
  2299 + }
  2300 +
2159 2301 $vs_join_sql = join("\n", $va_joins);
2160 2302
2161 2303 if (is_array($va_wheres) && sizeof($va_wheres) && ($vs_where_sql = join(' AND ', $va_wheres))) {
@@ -2247,6 +2389,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
2247 2389 $va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
2248 2390 }
2249 2391
  2392 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  2393 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  2394 + // Join to limit what browse table items are used to generate facet
  2395 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  2396 + $va_wheres[] = "(
  2397 + ((
  2398 + (ca_acl.user_id = ".(int)$vn_user_id.")
  2399 + ".((sizeof($va_group_ids) > 0) ? "OR
  2400 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  2401 + OR
  2402 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  2403 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  2404 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  2405 + )";
  2406 + }
  2407 + }
  2408 +
2250 2409 $vs_join_sql = join("\n", $va_joins);
2251 2410
2252 2411 $vs_where_sql = '';
@@ -2508,6 +2667,37 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
2508 2667 }
2509 2668 }
2510 2669
  2670 + if ($this->opo_config->get('perform_item_level_access_checking')) {
  2671 + if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
  2672 +
  2673 + // Join to limit what browse table items are used to generate facet
  2674 + $va_joins[] = 'LEFT JOIN ca_acl ON '.$this->ops_browse_table_name.'.'.$t_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_item->tableNum()."\n";
  2675 + $va_wheres[] = "(
  2676 + ((
  2677 + (ca_acl.user_id = ".(int)$vn_user_id.")
  2678 + ".((sizeof($va_group_ids) > 0) ? "OR
  2679 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  2680 + OR
  2681 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  2682 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  2683 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  2684 + )";
  2685 +
  2686 + // Join to limit what related items are used to generate facet
  2687 + $va_joins[] = 'LEFT JOIN ca_acl AS rel_acl ON '.$t_rel_item->tableName().'.'.$t_rel_item->primaryKey().' = rel_acl.row_id AND rel_acl.table_num = '.$t_rel_item->tableNum()."\n";
  2688 + $va_wheres[] = "(
  2689 + ((
  2690 + (rel_acl.user_id = ".(int)$vn_user_id.")
  2691 + ".((sizeof($va_group_ids) > 0) ? "OR
  2692 + (rel_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  2693 + OR
  2694 + (rel_acl.user_id IS NULL and rel_acl.group_id IS NULL)
  2695 + ) AND rel_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  2696 + ".(($vb_show_if_no_acl) ? "OR rel_acl.acl_id IS NULL" : "")."
  2697 + )";
  2698 + }
  2699 + }
  2700 +
2511 2701 $vs_join_sql = join("\n", $va_joins);
2512 2702
2513 2703
@@ -2947,6 +3137,122 @@ public function sortHits(&$pa_hits, $ps_field, $ps_direction='asc') {
2947 3137 }
2948 3138 # ------------------------------------------------------------------
2949 3139 /**
  3140 + * @param $pa_hits Array of row_ids to filter. *MUST HAVE row_ids AS KEYS, NOT VALUES*
  3141 + */
  3142 + public function filterHitsByACL($pa_hits, $pn_user_id, $pn_access=__CA_ACL_READONLY_ACCESS__, $pa_options=null) {
  3143 + if (!sizeof($pa_hits)) { return $pa_hits; }
  3144 + if (!(int)$pn_user_id) { return $pa_hits; }
  3145 + if (!($t_table = $this->opo_datamodel->getInstanceByTableNum($this->opn_browse_table_num, true))) { return $pa_hits; }
  3146 +
  3147 + $vs_table_name = $t_table->tableName();
  3148 + $vs_table_pk = $t_table->primaryKey();
  3149 +
  3150 + $t_user = new ca_users($pn_user_id);
  3151 + if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
  3152 + $va_group_ids = array_keys($va_groups);
  3153 + $vs_group_sql = '
  3154 + OR
  3155 + (ca_acl.group_id IN (?))';
  3156 + $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id, $va_group_ids);
  3157 + } else {
  3158 + $va_group_ids = null;
  3159 + $vs_group_sql = '';
  3160 + $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id);
  3161 + }
  3162 +
  3163 + $o_db = new Db();
  3164 + $qr_sort = $o_db->query($vs_sql = "
  3165 + SELECT ca_acl.acl_id, {$vs_table_name}.{$vs_table_pk}, ca_acl.access
  3166 + FROM {$vs_table_name}
  3167 + INNER JOIN ca_acl ON ca_acl.row_id = {$vs_table_name}.{$vs_table_pk} AND ca_acl.table_num = ?
  3168 + WHERE
  3169 + {$vs_table_name}.{$vs_table_pk} IN (".join(", ", array_keys($pa_hits)).") AND
  3170 +
  3171 + (
  3172 + (ca_acl.user_id = ?)
  3173 + {$vs_group_sql}
  3174 + OR
  3175 + (ca_acl.user_id IS NULL AND ca_acl.group_id IS NULL)
  3176 + )
  3177 + ", $va_params);
  3178 + $va_hits = array();
  3179 + while($qr_sort->nextRow()) {
  3180 + $vn_id = $qr_sort->get($vs_table_pk, array('binary' => true));
  3181 + unset($pa_hits[$vn_id]);
  3182 + if ($qr_sort->get('access', array('binary' => true)) < $pn_access) { continue; }
  3183 + $va_hits[$vn_id] = true;
  3184 + }
  3185 +
  3186 + // For row_ids that have no ACL entry
  3187 + if ($this->opo_config->get('default_item_access_level') >= $pn_access) {
  3188 + // Add row_ids that have no ACL entry because default access meets or exceeds requested access
  3189 + foreach($pa_hits as $vn_id => $vb_dummy) {
  3190 + $va_hits[$vn_id] = true;
  3191 + }
  3192 + }
  3193 +
  3194 + return $va_hits;
  3195 + }
  3196 + # ------------------------------------------------------------------
  3197 + /**
  3198 + * @param $pa_hits Array of row_ids to filter. *MUST HAVE row_ids AS KEYS, NOT VALUES*
  3199 + */
  3200 + public function filterFacetByACL($pn_user_id, $pn_access=__CA_ACL_READONLY_ACCESS__, $pa_options=null) {
  3201 + if (!sizeof($pa_hits)) { return $pa_hits; }
  3202 + if (!(int)$pn_user_id) { return $pa_hits; }
  3203 + if (!($t_table = $this->opo_datamodel->getInstanceByTableNum($this->opn_browse_table_num, true))) { return $pa_hits; }
  3204 +
  3205 + $vs_table_name = $t_table->tableName();
  3206 + $vs_table_pk = $t_table->primaryKey();
  3207 +
  3208 + $t_user = new ca_users($pn_user_id);
  3209 + if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
  3210 + $va_group_ids = array_keys($va_groups);
  3211 + $vs_group_sql = '
  3212 + OR
  3213 + (ca_acl.group_id IN (?))';
  3214 + $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id, $va_group_ids);
  3215 + } else {
  3216 + $va_group_ids = null;
  3217 + $vs_group_sql = '';
  3218 + $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id);
  3219 + }
  3220 +
  3221 + $o_db = new Db();
  3222 + $qr_sort = $o_db->query($vs_sql = "
  3223 + SELECT ca_acl.acl_id, {$vs_table_name}.{$vs_table_pk}, ca_acl.access
  3224 + FROM {$vs_table_name}
  3225 + INNER JOIN ca_acl ON ca_acl.row_id = {$vs_table_name}.{$vs_table_pk} AND ca_acl.table_num = ?
  3226 + WHERE
  3227 + {$vs_table_name}.{$vs_table_pk} IN (".join(", ", array_keys($pa_hits)).") AND
  3228 +
  3229 + (
  3230 + (ca_acl.user_id = ?)
  3231 + {$vs_group_sql}
  3232 + OR
  3233 + (ca_acl.user_id IS NULL AND ca_acl.group_id IS NULL)
  3234 + )
  3235 + ", $va_params);
  3236 + $va_hits = array();
  3237 + while($qr_sort->nextRow()) {
  3238 + $vn_id = $qr_sort->get($vs_table_pk, array('binary' => true));
  3239 + unset($pa_hits[$vn_id]);
  3240 + if ($qr_sort->get('access', array('binary' => true)) < $pn_access) { continue; }
  3241 + $va_hits[$vn_id] = true;
  3242 + }
  3243 +
  3244 + // For row_ids that have no ACL entry
  3245 + if ($this->opo_config->get('default_item_access_level') >= $pn_access) {
  3246 + // Add row_ids that have no ACL entry because default access meets or exceeds requested access
  3247 + foreach($pa_hits as $vn_id => $vb_dummy) {
  3248 + $va_hits[$vn_id] = true;
  3249 + }
  3250 + }
  3251 +
  3252 + return $va_hits;
  3253 + }
  3254 + # ------------------------------------------------------------------
  3255 + /**
2950 3256 *
2951 3257 */
2952 3258 public function setCachedFacetHTML($ps_cache_key, $ps_content) {
106 app/lib/ca/BundlableLabelableBaseModelWithAttributes.php
@@ -58,6 +58,7 @@ class BundlableLabelableBaseModelWithAttributes extends LabelableBaseModelWithAt
58 58 # ------------------------------------------------------
59 59 public function __construct($pn_id=null) {
60 60 require_once(__CA_MODELS_DIR__."/ca_editor_uis.php");
  61 + require_once(__CA_MODELS_DIR__."/ca_acl.php");
61 62 parent::__construct($pn_id); # call superclass constructor
62 63
63 64 $this->initLabelDefinitions();
@@ -67,7 +68,16 @@ public function __construct($pn_id=null) {
67 68 * Overrides load() to initialize bundle specifications
68 69 */
69 70 public function load ($pm_id=null) {
  71 + global $AUTH_CURRENT_USER_ID;
  72 +
70 73 $vn_rc = parent::load($pm_id);
  74 +
  75 + if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
  76 + if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) == __CA_ACL_NO_ACCESS__) {
  77 + $this->clear();
  78 + return false;
  79 + }
  80 + }
71 81 $this->initLabelDefinitions();
72 82
73 83 return $vn_rc;
@@ -78,6 +88,13 @@ public function load ($pm_id=null) {
78 88 * against the ca_lists list for the table (as defined by getTypeListCode())
79 89 */
80 90 public function insert($pa_options=null) {
  91 + global $AUTH_CURRENT_USER_ID;
  92 + if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
  93 + if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) < __CA_ACL_EDIT_ACCESS__) {
  94 + $this->postError(2580, _t("You do not have edit access for this item: %1/%2", $this->tableName(), $this->getPrimaryKey()), "BundlableLabelableBaseModelWithAttributes->insert()");
  95 + return false;
  96 + }
  97 + }
81 98 $vb_we_set_transaction = false;
82 99
83 100 if (!$this->inTransaction()) {
@@ -191,6 +208,13 @@ public function insert($pa_options=null) {
191 208 * Override update() to generate sortable version of user-defined identifier field
192 209 */
193 210 public function update($pa_options=null) {
  211 + global $AUTH_CURRENT_USER_ID;
  212 + if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
  213 + if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) < __CA_ACL_EDIT_ACCESS__) {
  214 + $this->postError(2580, _t("You do not have edit access for this item: %1/%2", $this->tableName(), $this->getPrimaryKey()), "BundlableLabelableBaseModelWithAttributes->update()");
  215 + return false;
  216 + }
  217 + }
194 218 $vb_we_set_transaction = false;
195 219 if (!$this->inTransaction()) {
196 220 $this->setTransaction(new Transaction($this->getDb()));
@@ -224,6 +248,21 @@ public function update($pa_options=null) {
224 248 }
225 249 # ------------------------------------------------------------------
226 250 /**
  251 + * Check user's item level access before passing delete to lower level libraries
  252 + *
  253 + */
  254 + public function delete ($pb_delete_related=false, $pa_options=null, $pa_fields=null, $pa_table_list=null) {
  255 + global $AUTH_CURRENT_USER_ID;
  256 + if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
  257 + if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) < __CA_ACL_EDIT_DELETE_ACCESS__) {
  258 + $this->postError(2580, _t("You do not have delete access for this item"), "BundlableLabelableBaseModelWithAttributes->delete()");
  259 + return false;
  260 + }
  261 + }
  262 + return parent::delete($pb_delete_related, $pa_options, $pa_fields, $pa_table_list);
  263 + }
  264 + # ------------------------------------------------------------------
  265 + /**
227 266 * Duplicates record, including labels, attributes and relationships. "Special" bundles - those
228 267 * specific to a model - should be duplicated by the model by overriding BundlableLabelablleBaseModelWithAttributes::duplicate()
229 268 * and doing any required work after BundlableLabelablleBaseModelWithAttributes::duplicate() has finished
@@ -955,6 +994,7 @@ public function isDeletable($po_request) {
955 994 * config
956 995 * viewPath
957 996 * graphicsPath
  997 + * request
958 998 */
959 999 public function getBundleFormHTML($ps_bundle_name, $ps_placement_code, $pa_bundle_settings, $pa_options) {
960 1000 global $g_ui_locale;
@@ -975,6 +1015,17 @@ public function getBundleFormHTML($ps_bundle_name, $ps_placement_code, $pa_bundl
975 1015 }
976 1016 }
977 1017
  1018 + if ((bool)$this->getAppConfig()->get('perform_item_level_access_checking')) {
  1019 + $vn_item_access = $this->checkACLAccessForUser($pa_options['request']->user);
  1020 + if ($vn_item_access == __CA_ACL_NO_ACCESS__) {
  1021 + return;
  1022 + }
  1023 + if ($vn_item_access == __CA_ACL_READONLY_ACCESS__) {
  1024 + $pa_bundle_settings['readonly'] = true;
  1025 + }
  1026 + }
  1027 +
  1028 +
978 1029 $va_info = $this->getBundleInfo($ps_bundle_name);
979 1030 if (!($vs_type = $va_info['type'])) { return null; }
980 1031
@@ -2968,9 +3019,14 @@ private function _processRelated($po_request, $ps_bundlename, $ps_form_prefix) {
2968 3019 * sort = optional array of bundles to sort returned values on. Currently only supported when getting related values via simple related <table_name> and <table_name>.related invokations. Eg. from a ca_objects results you can use the 'sort' option got get('ca_entities'), get('ca_entities.related') or get('ca_objects.related'). The bundle specifiers are fields with or without tablename. Only those fields returned for the related tables (intrinsics, label fields and attributes) are sortable.
2969 3020 * showDeleted = if set to true, related items that have been deleted are returned. Default is false.
2970 3021 * where = optional array of fields and field values to filter returned values on. The fields must be intrinsic and in the same table as the field being "get()'ed" Can be used to filter returned values from primary and related tables. This option can be useful when you want to fetch certain values from a related table. For example, you want to get the relationship source_info values, but only for relationships going to a specific related record. Note that multiple fields/values are effectively AND'ed together - all must match for a row to be returned - and that only equivalence is supported (eg. field equals value).
  3022 + * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
2971 3023 * @return array - list of related items
2972 3024 */
2973 3025 public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
  3026 + global $AUTH_CURRENT_USER_ID;
  3027 + $vn_user_id = (isset($pa_options['user_id']) && $pa_options['user_id']) ? $pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
  3028 + $vb_show_if_no_acl = (bool)($this->getAppConfig()->get('default_item_access_level') > __CA_ACL_NO_ACCESS__);
  3029 +
2974 3030 // convert options
2975 3031 if(isset($pa_options['restrictToType']) && (!isset($pa_options['restrict_to_type']) || !$pa_options['restrict_to_type'])) { $pa_options['restrict_to_type'] = $pa_options['restrictToType']; }
2976 3032 if(isset($pa_options['restrictToTypes']) && (!isset($pa_options['restrict_to_types']) || !$pa_options['restrict_to_types'])) { $pa_options['restrict_to_types'] = $pa_options['restrictToTypes']; }
@@ -3039,7 +3095,8 @@ public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
3039 3095
3040 3096 $va_wheres = array();
3041 3097 $va_selects = array();
3042   -
  3098 + $va_joins_post_add = array();
  3099 +
3043 3100 // TODO: get these field names from models
3044 3101 if ($t_item_rel) {
3045 3102 //define table names
@@ -3144,6 +3201,27 @@ public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
3144 3201 }
3145 3202 }
3146 3203
  3204 + if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
  3205 + $t_user = new ca_users($vn_user_id, true);
  3206 + if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
  3207 + $va_group_ids = array_keys($va_groups);
  3208 + } else {
  3209 + $va_group_ids = array();
  3210 + }
  3211 +
  3212 + // Join to limit what browse table items are used to generate facet
  3213 + $va_joins_post_add[] = 'LEFT JOIN ca_acl ON '.$t_rel_item->tableName().'.'.$t_rel_item->primaryKey().' = ca_acl.row_id AND ca_acl.table_num = '.$t_rel_item->tableNum()."\n";
  3214 + $va_wheres[] = "(
  3215 + ((
  3216 + (ca_acl.user_id = ".(int)$vn_user_id.")
  3217 + ".((sizeof($va_group_ids) > 0) ? "OR
  3218 + (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
  3219 + OR
  3220 + (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
  3221 + ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
  3222 + ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
  3223 + )";
  3224 + }
3147 3225
3148 3226 if (is_array($va_get_where)) {
3149 3227 foreach($va_get_where as $vs_fld => $vm_val) {
@@ -3244,7 +3322,7 @@ public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
3244 3322 $vs_sql = "
3245 3323 SELECT ".join(', ', $va_selects)."
3246 3324 FROM ".$va_path[0]."
3247   - ".join("\n", $va_joins)."
  3325 + ".join("\n", array_merge($va_joins, $va_joins_post_add))."
3248 3326 WHERE
3249 3327 ".join(' AND ', array_merge($va_wheres, array('('.$va_path[1].'.'.$vs_other_field .' IN ('.join(',', $va_row_ids).'))')))."
3250 3328 {$vs_order_by}";
@@ -3332,7 +3410,7 @@ public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
3332 3410 $vs_sql = "
3333 3411 SELECT ".join(', ', $va_selects)."
3334 3412 FROM ".$this->tableName()."
3335   - ".join("\n", $va_joins)."
  3413 + ".join("\n", array_merge($va_joins, $va_joins_post_add))."
3336 3414 WHERE
3337 3415 ".join(' AND ', $va_wheres)."
3338 3416 {$vs_order_by}
@@ -4099,14 +4177,26 @@ public function setACLWorldAccess($pn_world_access) {
4099 4177 }
4100 4178 # --------------------------------------------------------------------------------------------
4101 4179 /**
4102   - *
  4180 + * Checks access control list for currently loaded row for the specified user and returns an access value. Values are:
  4181 + *
  4182 + * __CA_ACL_NO_ACCESS__ (0)
  4183 + * __CA_ACL_READONLY_ACCESS__ (1)
  4184 + * __CA_ACL_EDIT_ACCESS__ (2)
  4185 + * __CA_ACL_EDIT_DELETE_ACCESS__ (3)
  4186 + *
  4187 + * @param ca_users $t_user A ca_users object
  4188 + * @param int $pn_id Optional row_id to check ACL for; if omitted currently loaded row_id is used
  4189 + * @return int An access value
4103 4190 */
4104   - public function checkACLAccessForUser($t_user) {
4105   - if (!($vn_id = (int)$this->getPrimaryKey())) { return null; }
4106   -
  4191 + public function checkACLAccessForUser($t_user, $pn_id=null) {
  4192 + if (!$pn_id) {
  4193 + $pn_id = (int)$this->getPrimaryKey();
  4194 + if (!$pn_id) { return null; }
  4195 + }
  4196 + if ($t_user->canDoAction('is_administrator')) { return __CA_ACL_EDIT_DELETE_ACCESS__; }
4107 4197 require_once(__CA_MODELS_DIR__.'/ca_acl.php');
4108 4198
4109   - return ca_acl::loadACLForRow($t_user, $this->tableNum(), $vn_id);
  4199 + return ca_acl::accessForRow($t_user, $this->tableNum(), $vn_id);
4110 4200 }
4111 4201 # ------------------------------------------------------
4112 4202 }
28 app/lib/core/BaseModel.php
@@ -318,6 +318,8 @@ class BaseModel extends BaseObject {
318 318 */
319 319 static $s_ca_models_definitions;
320 320
  321 + static $s_instance_cache = array();
  322 +
321 323 /**
322 324 * Constructor
323 325 * In general you should not call this constructor directly. Any table in your database
@@ -327,8 +329,9 @@ class BaseModel extends BaseObject {
327 329 * if omitted, an empty object is created which can be used to create a new row in the database.
328 330 * @return BaseModel
329 331 */
330   - public function __construct($pn_id=null) {
  332 + public function __construct($pn_id=null, $pb_use_cache=true) {
331 333 $vs_table_name = $this->tableName();
  334 +
332 335 if (!$this->FIELDS =& BaseModel::$s_ca_models_definitions[$vs_table_name]['FIELDS']) {
333 336 die("Field definitions not found for {$vs_table_name}");
334 337 }
@@ -355,7 +358,7 @@ public function __construct($pn_id=null) {
355 358
356 359 $this->setMode(ACCESS_READ);
357 360
358   - if ($pn_id) { $this->load($pn_id);}
  361 + if ($pn_id) { $this->load($pn_id, $pb_use_cache);}
359 362 }
360 363
361 364 /**
@@ -1532,8 +1535,15 @@ public function setMode($pn_mode) {
1532 1535 * @param mixed $pm_id primary key value of the record to load (assuming we have no composed primary key)
1533 1536 * @return bool success state
1534 1537 */
1535   - public function load ($pm_id=null) {
  1538 + public function load($pm_id=null, $pb_use_cache=true) {
1536 1539 $this->clear();
  1540 + $vs_table_name = $this->tableName();
  1541 + if ($pb_use_cache && is_numeric($pm_id) && isset(BaseModel::$s_instance_cache[$vs_table_name][$pm_id]) && is_array(BaseModel::$s_instance_cache[$vs_table_name][$pm_id])) {
  1542 + $this->_FIELD_VALUES = BaseModel::$s_instance_cache[$vs_table_name][$pm_id];
  1543 + $this->_FIELD_VALUES_OLD = $this->_FIELD_VALUES;
  1544 + $this->_FILES_CLEAR = array();
  1545 + return true;
  1546 + }
1537 1547
1538 1548 if ($pm_id == null) {
1539 1549 //$this->postError(750,_t("Can't load record; key is blank"), "BaseModel->load()");
@@ -1626,6 +1636,8 @@ public function load ($pm_id=null) {
1626 1636
1627 1637 $this->_FIELD_VALUES_OLD = $this->_FIELD_VALUES;
1628 1638 $this->_FILES_CLEAR = array();
  1639 +
  1640 + if ($vn_id = $this->getPrimaryKey()) { BaseModel::$s_instance_cache[$vs_table_name][$vn_id] = $this->_FIELD_VALUES; }
1629 1641 return true;
1630 1642 } else {
1631 1643 if (!is_array($pm_id)) {
@@ -2123,7 +2135,11 @@ public function insert ($pa_options=null) {
2123 2135
2124 2136 if ($vb_we_set_transaction) { $this->removeTransaction(true); }
2125 2137
2126   - $this->_FIELD_VALUE_CHANGED = array();
  2138 + $this->_FIELD_VALUE_CHANGED = array();
  2139 +
  2140 + // Update instance cache
  2141 + BaseModel::$s_instance_cache[$this->tableName()][$vn_id] = $this->_FIELD_VALUES;
  2142 +
2127 2143 return $vn_id;
2128 2144 } else {
2129 2145 foreach($o_db->errors() as $o_e) {
@@ -2668,6 +2684,9 @@ public function update ($pa_options=null) {
2668 2684
2669 2685 if ($vb_we_set_transaction) { $this->removeTransaction(true); }
2670 2686 $this->_FIELD_VALUE_CHANGED = array();
  2687 +
  2688 + // Update instance cache
  2689 + BaseModel::$s_instance_cache[$this->tableName()][$this->getPrimaryKey()] = $this->_FIELD_VALUES;
2671 2690 return true;
2672 2691 } else {
2673 2692 if ($vb_we_set_transaction) { $this->removeTransaction(false); }
@@ -6802,6 +6821,7 @@ public function htmlFormElement($ps_field, $ps_format=null, $pa_options=null) {
6802 6821 }
6803 6822
6804 6823 if (isset($pa_options['usewysiwygeditor']) && $pa_options['usewysiwygeditor']) {
  6824 + JavascriptLoadManager::register("ckeditor");
6805 6825 $vs_width = $vn_display_width;
6806 6826 $vs_height = $vn_display_height;
6807 6827 if (!preg_match("!^[\d\.]+px$!i", $vs_width)) {
1  app/lib/core/Error/errors.en_us
@@ -192,6 +192,7 @@
192 192 2550 = Item has been deleted
193 193 2560 = Item is not correct type
194 194 2570 = Can't change access control list for this item
  195 +2580 = You do not have access to this item
195 196
196 197 # --- Object representation errors
197 198 2700 = Could not update primary flag for representation
76 app/lib/core/Search/SearchEngine.php
@@ -37,7 +37,6 @@
37 37 require_once(__CA_LIB_DIR__."/core/Search/SearchCache.php");
38 38 require_once(__CA_LIB_DIR__."/core/Logging/Searchlog.php");
39 39 require_once(__CA_LIB_DIR__."/core/Utils/Timer.php");
40   -require_once(__CA_MODELS_DIR__.'/ca_lists.php');
41 40 require_once(__CA_APP_DIR__.'/helpers/accessHelpers.php');
42 41
43 42 require_once(__CA_LIB_DIR__."/core/Search/Common/Parsers/LuceneSyntaxParser.php");
@@ -45,6 +44,9 @@
45 44 require_once(__CA_LIB_DIR__."/core/Zend/Search/Lucene/Search/Query/Boolean.php");
46 45 require_once(__CA_LIB_DIR__."/core/Zend/Search/Lucene/Search/Query/Term.php");
47 46
  47 +require_once(__CA_MODELS_DIR__.'/ca_lists.php');
  48 +require_once(__CA_MODELS_DIR__.'/ca_acl.php');
  49 +
48 50 # ----------------------------------------------------------------------
49 51 class SearchEngine extends SearchBase {
50 52
@@ -110,11 +112,14 @@ public function isValidOption($ps_option) {
110 112 * showDeleted = if set to true, related items that have been deleted are returned. Default is false.
111 113 * limitToModifiedOn = if set returned results will be limited to rows modified within the specified date range. The value should be a date/time expression parse-able by TimeExpressionParser
112 114 * sets = if value is a list of set_ids, only rows that are members of those sets will be returned
  115 + * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
113 116 *
114 117 * @return SearchResult Results packages in a SearchResult object, or sub-class of SearchResult if an instance was passed in $po_result
115 118 * @uses TimeExpressionParser::parse
116 119 */
117 120 public function &search($ps_search, $po_result=null, $pa_options=null) {
  121 + global $AUTH_CURRENT_USER_ID;
  122 +
118 123 $t = new Timer();
119 124 if (!is_array($pa_options)) { $pa_options = array(); }
120 125 $vn_limit = (isset($pa_options['limit']) && ($pa_options['limit'] > 0)) ? (int)$pa_options['limit'] : null;
@@ -203,6 +208,11 @@ public function &search($ps_search, $po_result=null, $pa_options=null) {
203 208 $va_hits = $this->filterHitsBySets($va_hits, $pa_options['sets']);
204 209 }
205 210
  211 + $vn_user_id = (isset($pa_options['user_id']) && (int)$pa_options['user_id']) ? (int)$pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
  212 + if ($this->opo_app_config->get('perform_item_level_access_checking')) {
  213 + $va_hits = $this->filterHitsByACL($va_hits, $vn_user_id, __CA_ACL_READONLY_ACCESS__);
  214 + }
  215 +
206 216 if (isset($pa_options['sort']) && $pa_options['sort'] && ($pa_options['sort'] != '_natural')) {
207 217 $va_hits = $this->sortHits($va_hits, $pa_options['sort'], (isset($pa_options['sort_direction']) ? $pa_options['sort_direction'] : null));
208 218 }
@@ -216,15 +226,13 @@ public function &search($ps_search, $po_result=null, $pa_options=null) {
216 226 // log search
217 227 $o_log = new Searchlog();
218 228
219   - global $AUTH_CURRENT_USER_ID;
220   - $vn_search_user_id = $AUTH_CURRENT_USER_ID ? $AUTH_CURRENT_USER_ID : null;
221 229 $vn_search_form_id = isset($pa_options['form_id']) ? $pa_options['form_id'] : null;
222 230 $vs_log_details = isset($pa_options['log_details']) ? $pa_options['log_details'] : '';
223 231 $vs_search_source = isset($pa_options['search_source']) ? $pa_options['search_source'] : '';
224 232
225 233 $vn_execution_time = $t->getTime(4);
226 234 $o_log->log(array(
227   - 'user_id' => $vn_search_user_id,
  235 + 'user_id' => $vn_user_id,
228 236 'table_num' => $this->opn_tablenum,
229 237 'search_expression' => $ps_search,
230 238 <