Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
View
20 app/conf/client_services.conf
@@ -10,8 +10,8 @@ notification_login_url = http://digitallibrary.hsp.org/index.php/LoginReg/form
# email address(es) to send administrative notifications to
administrative_email_addresses = [rnr@hsp.org, seth@whirl-i-gig.com]
-administrative_email_on_order_status = [SUBMITTED, AWAITING_PAYMENT, PROCESSED, PROCESSED_AWAITING_DIGITIZATION, COMPLETED, REOPENED]
-administrative_email_on_payment_status = [SENT_INVOICE, PROCESSING, PROCESSED_AWAITING_DIGITIZATION, DECLINED, RECEIVED]
+administrative_email_on_order_status = [SUBMITTED, AWAITING_PAYMENT, PROCESSED, PROCESSED_AWAITING_DIGITIZATION, PROCESSED_AWAITING_MEDIA_ACCESS, COMPLETED, REOPENED]
+administrative_email_on_payment_status = [SENT_INVOICE, PROCESSING, PROCESSED_AWAITING_DIGITIZATION, PROCESSED_AWAITING_MEDIA_ACCESS, DECLINED, RECEIVED]
# Service to use for processing of credit card payments
#
@@ -49,6 +49,22 @@ fulfillment_methods = {
}
}
+
+# Remote media storage access
+# If high-resolution media is not stored in this system you can pull them on-demand
+# from another CA instance using the configuration options below. On-demand pull of high-resolution
+# to this commerce system is accomplished using media URLs discovered via the itemInfo
+# getObjectRepresentationURLByMD5() web service.
+
+# Base URL (everything before "index.php" or "service.php") of instance to pull media from
+remote_media_base_url = http://test.com/admin
+
+# Remote instance login
+# To ensure these are not accidentally made visible in a web-served configuration file
+# you can place them in setup.php and then references them here using the constants below
+remote_media_username = __CA_CLIENT_SERVICES_REMOTE_MEDIA_USERNAME__
+remote_media_password = __CA_CLIENT_SERVICES_REMOTE_MEDIA_PASSWORD__
+
# Set disposal policy determines what is done with a user's set once
# an order is created from it. Possible values are:
#
View
22 app/helpers/mailHelpers.php
@@ -62,11 +62,13 @@
* $pa_bcc: Email address(es) of bcc'ed message recipients. Can be a string containing a single email address or
* an associative array with keys set to multiple addresses and corresponding values optionally set to
* a human-readable recipient name. (optional)
+ * $pa_attachment: array containing file path, name and mime_type of file to attach.
+ * keys are "path", "name", "mime_type"
*
* While both $ps_body_text and $ps_html_text are optional, at least one should be set and both can be set for a
* combination text and HTML email
*/
- function caSendmail($pa_to, $pa_from, $ps_subject, $ps_body_text, $ps_body_html='', $pa_cc=null, $pa_bcc=null) {
+ function caSendmail($pa_to, $pa_from, $ps_subject, $ps_body_text, $ps_body_html='', $pa_cc=null, $pa_bcc=null, $pa_attachment=null) {
$o_config = Configuration::load();
$o_log = new Eventlog();
@@ -152,7 +154,19 @@ function caSendmail($pa_to, $pa_from, $ps_subject, $ps_body_text, $ps_body_html=
}
}
-
+ if(is_array($pa_attachment) && $pa_attachment["path"]){
+ $ps_attachment_url = $pa_attachment["path"];
+ $vs_file_contents = file_get_contents($ps_attachment_url);
+
+ $o_attachment = $o_mail->createAttachment($vs_file_contents);
+ if($pa_attachment["name"]){
+ $o_attachment->filename = $pa_attachment["name"];
+ }
+ if($pa_attachment["mime_type"]){
+ $o_attachment->type = $pa_attachment["mime_type"];
+ }
+ }
+
$o_mail->setSubject($ps_subject);
if ($ps_body_text) {
$o_mail->setBodyText($ps_body_text);
@@ -224,7 +238,9 @@ function caCheckEmailAddressRegex($ps_address) {
* @return string True if send, false if error
*/
function caSendMessageUsingView($po_request, $pa_to, $pa_from, $ps_subject, $ps_view, $pa_values, $pa_cc=null, $pa_bcc=null) {
- $o_view = new View(null, $po_request->getViewsDirectoryPath()."/mailTemplates");
+ $vs_view_path = (is_object($po_request)) ? $po_request->getViewsDirectoryPath() : __CA_BASE_DIR__.'/themes/default/views';
+
+ $o_view = new View(null, $vs_view_path."/mailTemplates");
foreach($pa_values as $vs_key => $vm_val) {
$o_view->setVar($vs_key, $vm_val);
}
View
4 app/helpers/preload.php
@@ -7,7 +7,7 @@
* ----------------------------------------------------------------------
*
* Software by Whirl-i-Gig (http://www.whirl-i-gig.com)
- * Copyright 2008-2009 Whirl-i-Gig
+ * Copyright 2008-2012 Whirl-i-Gig
*
* For more information visit http://www.CollectiveAccess.org
*
@@ -54,6 +54,8 @@
require(__CA_LIB_DIR__."/core/Controller/ActionController.php");
+ require(__CA_MODELS_DIR__."/ca_acl.php");
+
// initialize Tooltip manager
TooltipManager::init();
?>
View
90 app/lib/ca/BaseEditorController.php
@@ -95,6 +95,16 @@ public function Edit($pa_values=null, $pa_options=null) {
}
//
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
+ //
// Are we duplicating?
//
if (($vs_mode == 'dupe') && $this->request->user->canDoAction('can_duplicate_'.$t_subject->tableName())) {
@@ -204,6 +214,16 @@ public function Save($pa_options=null) {
$this->response->setRedirect($this->request->config->get('error_display_url').'/n/2560?r='.urlencode($this->request->getFullUrlPath()));
return;
}
+
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) < __CA_ACL_EDIT_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
if($vn_above_id) {
// 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) {
$vs_type_name = $t_subject->getProperty('NAME_SINGULAR');
}
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) < __CA_ACL_EDIT_DELETE_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
// get parent_id, if it exists, prior to deleting so we can
// set the browse_last_id parameter to something sensible
$vn_parent_id = null;
@@ -463,6 +493,16 @@ public function Summary($pa_options=null) {
return;
}
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
$t_display = new ca_bundle_displays();
$va_displays = $t_display->getBundleDisplays(array('table' => $t_subject->tableNum(), 'user_id' => $this->request->getUserID(), 'access' => __CA_BUNDLE_DISPLAY_READ_ACCESS__));
@@ -533,6 +573,16 @@ public function PrintSummary($pa_options=null) {
return;
}
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
$t_display = new ca_bundle_displays();
$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) {
return;
}
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
$this->render('log_html.php');
}
# -------------------------------------------------------
@@ -639,6 +699,16 @@ public function Access($pa_options=null) {
return;
}
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
if ((!$this->request->user->canDoAction('can_change_acl_'.$t_subject->tableName()))) {
$this->response->setRedirect($this->request->config->get('error_display_url').'/n/2570?r='.urlencode($this->request->getFullUrlPath()));
return;
@@ -767,6 +837,16 @@ public function DownloadFile() {
list($vn_subject_id, $t_subject) = $this->_initView();
if (!($pn_value_id = $this->request->getParameter('value_id', pInteger))) { return; }
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
$o_view = new View($this->request, $this->request->getViewsDirectoryPath().'/bundles/');
// TODO: check that file is part of item user has access rights for
@@ -802,6 +882,16 @@ public function DownloadMedia($pa_options=null) {
if (!($pn_value_id = $this->request->getParameter('value_id', pInteger))) { return; }
$ps_version = $this->request->getParameter('version', pString);
+ //
+ // Does user have access to row?
+ //
+ if ($t_subject->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($t_subject->checkACLAccessForUser($this->request->user) == __CA_ACL_NO_ACCESS__) {
+ $this->response->setRedirect($this->request->config->get('error_display_url').'/n/2580?r='.urlencode($this->request->getFullUrlPath()));
+ return;
+ }
+ }
+
// TODO: check that file is part of item user has access rights for
$t_attr_val = new ca_attribute_values($pn_value_id);
if (!$t_attr_val->getPrimaryKey()) { return; }
View
324 app/lib/ca/Browse/BrowseEngine.php
@@ -37,13 +37,15 @@
require_once(__CA_LIB_DIR__.'/core/BaseObject.php');
require_once(__CA_LIB_DIR__.'/core/Datamodel.php');
require_once(__CA_LIB_DIR__.'/core/Db.php');
- require_once(__CA_MODELS_DIR__.'/ca_metadata_elements.php');
- require_once(__CA_MODELS_DIR__.'/ca_lists.php');
require_once(__CA_LIB_DIR__.'/ca/Browse/BrowseResult.php');
require_once(__CA_LIB_DIR__.'/ca/Browse/BrowseCache.php');
require_once(__CA_LIB_DIR__.'/core/Parsers/TimeExpressionParser.php');
require_once(__CA_APP_DIR__.'/helpers/searchHelpers.php');
require_once(__CA_APP_DIR__.'/helpers/accessHelpers.php');
+
+ require_once(__CA_MODELS_DIR__.'/ca_metadata_elements.php');
+ require_once(__CA_MODELS_DIR__.'/ca_lists.php');
+ require_once(__CA_MODELS_DIR__.'/ca_acl.php');
class BrowseEngine extends BaseObject {
# ------------------------------------------------------
@@ -696,9 +698,12 @@ public function getInfoForFacetsWithContent() {
* no_cache = don't use cached browse results
* showDeleted = if set to true, related items that have been deleted are returned. Default is false.
* 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
+ * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
*/
public function execute($pa_options=null) {
+ global $AUTH_CURRENT_USER_ID;
if (!is_array($pa_options)) { $pa_options = array(); }
+ $vn_user_id = (isset($pa_options['user_id']) && (int)$pa_options['user_id']) ? (int)$pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
if (!is_array($this->opa_browse_settings)) { return null; }
$va_params = $this->opo_ca_browse_cache->getParameters();
@@ -1308,7 +1313,7 @@ public function execute($pa_options=null) {
$this->_dropTempTable('ca_browses_acc');
$this->_dropTempTable('ca_browses_tmp');
- $this->opo_ca_browse_cache->setResults($va_results);
+ $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);
$vb_need_to_save_in_cache = true;
}
} else {
@@ -1360,7 +1365,7 @@ public function execute($pa_options=null) {
");
$va_results = $qr_res->getAllFieldValues($vs_pk);
- $this->opo_ca_browse_cache->setResults($va_results);
+ $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);
$vb_need_to_save_in_cache = true;
}
}
@@ -1440,8 +1445,20 @@ public function getFacet($ps_facet_name, $pa_options=null) {
* Options:
* checkAccess = array of access values to filter facets that have an 'access' field by
* checkAvailabilityOnly = if true then content is not actually fetch - only the availablility of content is verified
+ * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
*/
public function getFacetContent($ps_facet_name, $pa_options=null) {
+ global $AUTH_CURRENT_USER_ID;
+ $vn_user_id = (isset($pa_options['user_id']) && (int)$pa_options['user_id']) ? (int)$pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
+ $vb_show_if_no_acl = (bool)($this->opo_config->get('default_item_access_level') > __CA_ACL_NO_ACCESS__);
+
+ $t_user = new ca_users($vn_user_id);
+ if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
+ $va_group_ids = array_keys($va_groups);
+ } else {
+ $va_group_ids = array();
+ }
+
if (!is_array($this->opa_browse_settings)) { return null; }
if (!isset($this->opa_browse_settings['facets'][$ps_facet_name])) { return null; }
if (!is_array($pa_options)) { $pa_options = array(); }
@@ -1531,11 +1548,11 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
);
$vs_cur_table = array_shift($va_path);
- $va_joins = array();
+ $va_joins_init = array();
foreach($va_path as $vs_join_table) {
$va_rel_info = $this->opo_datamodel->getRelationships($vs_cur_table, $vs_join_table);
- $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";
+ $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";
$vs_cur_table = $vs_join_table;
}
@@ -1543,8 +1560,7 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
$va_counts = array();
foreach($va_facet_values as $vs_state_name => $va_state_info) {
$va_wheres = array();
-
- $vs_join_sql = join("\n", $va_joins);
+ $va_joins = $va_joins_init;
if ((sizeof($va_restrict_to_relationship_types) > 0) && is_object($t_item_rel)) {
$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) {
$va_wheres[] = $this->ops_browse_table_name.".".$t_item->primaryKey()." IN (".join(",", $va_results).")";
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+ $vs_join_sql = join("\n", $va_joins);
+
$vs_where_sql = '';
if (sizeof($va_wheres) > 0) {
$vs_where_sql = ' WHERE '.join(' AND ', $va_wheres);
@@ -1632,6 +1666,8 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
$vb_needs_join = false;
$va_where_sql = array();
+ $va_joins = array();
+
if (sizeof($va_results)) {
$va_where_sql[] = "l.{$vs_item_pk} IN (".join(",", $va_results).")";
}
@@ -1663,10 +1699,30 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
}
}
+
if ($vb_needs_join) {
- $vs_join_sql = "INNER JOIN ".$this->ops_browse_table_name." ON ".$this->ops_browse_table_name.".".$t_item->primaryKey()." = l.".$t_item->primaryKey();
+ $va_joins[] = "INNER JOIN ".$this->ops_browse_table_name." ON ".$this->ops_browse_table_name.".".$t_item->primaryKey()." = l.".$t_item->primaryKey();
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_where_sql[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
+ $vs_join_sql = join("\n", $va_joins);
+
if (sizeof($va_where_sql)) {
$vs_where_sql = "WHERE ".join(" AND ", $va_where_sql);
}
@@ -1768,6 +1824,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
$va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
$vs_join_sql = join("\n", $va_joins);
if (is_array($va_wheres) && sizeof($va_wheres) && ($vs_where_sql = join(' AND ', $va_wheres))) {
$vs_where_sql = ' AND ('.$vs_where_sql.')';
@@ -1912,6 +1985,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
$va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
$vs_join_sql = join("\n", $va_joins);
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) {
$va_wheres[] = $vs_browse_type_limit_sql;
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
+
if (is_array($va_wheres) && sizeof($va_wheres) && ($vs_where_sql = join(' AND ', $va_wheres))) {
$vs_where_sql = '('.$vs_where_sql.')';
}
@@ -2066,6 +2174,23 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
$va_wheres[] = $vs_browse_type_limit_sql;
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
$vs_join_sql = join("\n", $va_joins);
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) {
$va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
$vs_join_sql = join("\n", $va_joins);
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) {
$va_wheres[] = "(".$this->ops_browse_table_name.".deleted = 0)";
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
$vs_join_sql = join("\n", $va_joins);
$vs_where_sql = '';
@@ -2508,6 +2667,37 @@ public function getFacetContent($ps_facet_name, $pa_options=null) {
}
}
+ if ($this->opo_config->get('perform_item_level_access_checking')) {
+ if ($t_item = $this->opo_datamodel->getInstanceByTableName($this->ops_browse_table_name, true)) {
+
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+
+ // Join to limit what related items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (rel_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (rel_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (rel_acl.user_id IS NULL and rel_acl.group_id IS NULL)
+ ) AND rel_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR rel_acl.acl_id IS NULL" : "")."
+ )";
+ }
+ }
+
$vs_join_sql = join("\n", $va_joins);
@@ -2947,6 +3137,122 @@ public function sortHits(&$pa_hits, $ps_field, $ps_direction='asc') {
}
# ------------------------------------------------------------------
/**
+ * @param $pa_hits Array of row_ids to filter. *MUST HAVE row_ids AS KEYS, NOT VALUES*
+ */
+ public function filterHitsByACL($pa_hits, $pn_user_id, $pn_access=__CA_ACL_READONLY_ACCESS__, $pa_options=null) {
+ if (!sizeof($pa_hits)) { return $pa_hits; }
+ if (!(int)$pn_user_id) { return $pa_hits; }
+ if (!($t_table = $this->opo_datamodel->getInstanceByTableNum($this->opn_browse_table_num, true))) { return $pa_hits; }
+
+ $vs_table_name = $t_table->tableName();
+ $vs_table_pk = $t_table->primaryKey();
+
+ $t_user = new ca_users($pn_user_id);
+ if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
+ $va_group_ids = array_keys($va_groups);
+ $vs_group_sql = '
+ OR
+ (ca_acl.group_id IN (?))';
+ $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id, $va_group_ids);
+ } else {
+ $va_group_ids = null;
+ $vs_group_sql = '';
+ $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id);
+ }
+
+ $o_db = new Db();
+ $qr_sort = $o_db->query($vs_sql = "
+ SELECT ca_acl.acl_id, {$vs_table_name}.{$vs_table_pk}, ca_acl.access
+ FROM {$vs_table_name}
+ INNER JOIN ca_acl ON ca_acl.row_id = {$vs_table_name}.{$vs_table_pk} AND ca_acl.table_num = ?
+ WHERE
+ {$vs_table_name}.{$vs_table_pk} IN (".join(", ", array_keys($pa_hits)).") AND
+
+ (
+ (ca_acl.user_id = ?)
+ {$vs_group_sql}
+ OR
+ (ca_acl.user_id IS NULL AND ca_acl.group_id IS NULL)
+ )
+ ", $va_params);
+ $va_hits = array();
+ while($qr_sort->nextRow()) {
+ $vn_id = $qr_sort->get($vs_table_pk, array('binary' => true));
+ unset($pa_hits[$vn_id]);
+ if ($qr_sort->get('access', array('binary' => true)) < $pn_access) { continue; }
+ $va_hits[$vn_id] = true;
+ }
+
+ // For row_ids that have no ACL entry
+ if ($this->opo_config->get('default_item_access_level') >= $pn_access) {
+ // Add row_ids that have no ACL entry because default access meets or exceeds requested access
+ foreach($pa_hits as $vn_id => $vb_dummy) {
+ $va_hits[$vn_id] = true;
+ }
+ }
+
+ return $va_hits;
+ }
+ # ------------------------------------------------------------------
+ /**
+ * @param $pa_hits Array of row_ids to filter. *MUST HAVE row_ids AS KEYS, NOT VALUES*
+ */
+ public function filterFacetByACL($pn_user_id, $pn_access=__CA_ACL_READONLY_ACCESS__, $pa_options=null) {
+ if (!sizeof($pa_hits)) { return $pa_hits; }
+ if (!(int)$pn_user_id) { return $pa_hits; }
+ if (!($t_table = $this->opo_datamodel->getInstanceByTableNum($this->opn_browse_table_num, true))) { return $pa_hits; }
+
+ $vs_table_name = $t_table->tableName();
+ $vs_table_pk = $t_table->primaryKey();
+
+ $t_user = new ca_users($pn_user_id);
+ if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
+ $va_group_ids = array_keys($va_groups);
+ $vs_group_sql = '
+ OR
+ (ca_acl.group_id IN (?))';
+ $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id, $va_group_ids);
+ } else {
+ $va_group_ids = null;
+ $vs_group_sql = '';
+ $va_params = array((int)$this->opn_browse_table_num, (int)$pn_user_id);
+ }
+
+ $o_db = new Db();
+ $qr_sort = $o_db->query($vs_sql = "
+ SELECT ca_acl.acl_id, {$vs_table_name}.{$vs_table_pk}, ca_acl.access
+ FROM {$vs_table_name}
+ INNER JOIN ca_acl ON ca_acl.row_id = {$vs_table_name}.{$vs_table_pk} AND ca_acl.table_num = ?
+ WHERE
+ {$vs_table_name}.{$vs_table_pk} IN (".join(", ", array_keys($pa_hits)).") AND
+
+ (
+ (ca_acl.user_id = ?)
+ {$vs_group_sql}
+ OR
+ (ca_acl.user_id IS NULL AND ca_acl.group_id IS NULL)
+ )
+ ", $va_params);
+ $va_hits = array();
+ while($qr_sort->nextRow()) {
+ $vn_id = $qr_sort->get($vs_table_pk, array('binary' => true));
+ unset($pa_hits[$vn_id]);
+ if ($qr_sort->get('access', array('binary' => true)) < $pn_access) { continue; }
+ $va_hits[$vn_id] = true;
+ }
+
+ // For row_ids that have no ACL entry
+ if ($this->opo_config->get('default_item_access_level') >= $pn_access) {
+ // Add row_ids that have no ACL entry because default access meets or exceeds requested access
+ foreach($pa_hits as $vn_id => $vb_dummy) {
+ $va_hits[$vn_id] = true;
+ }
+ }
+
+ return $va_hits;
+ }
+ # ------------------------------------------------------------------
+ /**
*
*/
public function setCachedFacetHTML($ps_cache_key, $ps_content) {
View
106 app/lib/ca/BundlableLabelableBaseModelWithAttributes.php
@@ -58,6 +58,7 @@ class BundlableLabelableBaseModelWithAttributes extends LabelableBaseModelWithAt
# ------------------------------------------------------
public function __construct($pn_id=null) {
require_once(__CA_MODELS_DIR__."/ca_editor_uis.php");
+ require_once(__CA_MODELS_DIR__."/ca_acl.php");
parent::__construct($pn_id); # call superclass constructor
$this->initLabelDefinitions();
@@ -67,7 +68,16 @@ public function __construct($pn_id=null) {
* Overrides load() to initialize bundle specifications
*/
public function load ($pm_id=null) {
+ global $AUTH_CURRENT_USER_ID;
+
$vn_rc = parent::load($pm_id);
+
+ if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) == __CA_ACL_NO_ACCESS__) {
+ $this->clear();
+ return false;
+ }
+ }
$this->initLabelDefinitions();
return $vn_rc;
@@ -78,6 +88,13 @@ public function load ($pm_id=null) {
* against the ca_lists list for the table (as defined by getTypeListCode())
*/
public function insert($pa_options=null) {
+ global $AUTH_CURRENT_USER_ID;
+ if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) < __CA_ACL_EDIT_ACCESS__) {
+ $this->postError(2580, _t("You do not have edit access for this item: %1/%2", $this->tableName(), $this->getPrimaryKey()), "BundlableLabelableBaseModelWithAttributes->insert()");
+ return false;
+ }
+ }
$vb_we_set_transaction = false;
if (!$this->inTransaction()) {
@@ -191,6 +208,13 @@ public function insert($pa_options=null) {
* Override update() to generate sortable version of user-defined identifier field
*/
public function update($pa_options=null) {
+ global $AUTH_CURRENT_USER_ID;
+ if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) < __CA_ACL_EDIT_ACCESS__) {
+ $this->postError(2580, _t("You do not have edit access for this item: %1/%2", $this->tableName(), $this->getPrimaryKey()), "BundlableLabelableBaseModelWithAttributes->update()");
+ return false;
+ }
+ }
$vb_we_set_transaction = false;
if (!$this->inTransaction()) {
$this->setTransaction(new Transaction($this->getDb()));
@@ -224,6 +248,21 @@ public function update($pa_options=null) {
}
# ------------------------------------------------------------------
/**
+ * Check user's item level access before passing delete to lower level libraries
+ *
+ */
+ public function delete ($pb_delete_related=false, $pa_options=null, $pa_fields=null, $pa_table_list=null) {
+ global $AUTH_CURRENT_USER_ID;
+ if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
+ if ($this->checkACLAccessForUser(new ca_users($AUTH_CURRENT_USER_ID)) < __CA_ACL_EDIT_DELETE_ACCESS__) {
+ $this->postError(2580, _t("You do not have delete access for this item"), "BundlableLabelableBaseModelWithAttributes->delete()");
+ return false;
+ }
+ }
+ return parent::delete($pb_delete_related, $pa_options, $pa_fields, $pa_table_list);
+ }
+ # ------------------------------------------------------------------
+ /**
* Duplicates record, including labels, attributes and relationships. "Special" bundles - those
* specific to a model - should be duplicated by the model by overriding BundlableLabelablleBaseModelWithAttributes::duplicate()
* and doing any required work after BundlableLabelablleBaseModelWithAttributes::duplicate() has finished
@@ -955,6 +994,7 @@ public function isDeletable($po_request) {
* config
* viewPath
* graphicsPath
+ * request
*/
public function getBundleFormHTML($ps_bundle_name, $ps_placement_code, $pa_bundle_settings, $pa_options) {
global $g_ui_locale;
@@ -975,6 +1015,17 @@ public function getBundleFormHTML($ps_bundle_name, $ps_placement_code, $pa_bundl
}
}
+ if ((bool)$this->getAppConfig()->get('perform_item_level_access_checking')) {
+ $vn_item_access = $this->checkACLAccessForUser($pa_options['request']->user);
+ if ($vn_item_access == __CA_ACL_NO_ACCESS__) {
+ return;
+ }
+ if ($vn_item_access == __CA_ACL_READONLY_ACCESS__) {
+ $pa_bundle_settings['readonly'] = true;
+ }
+ }
+
+
$va_info = $this->getBundleInfo($ps_bundle_name);
if (!($vs_type = $va_info['type'])) { return null; }
@@ -2968,9 +3019,14 @@ private function _processRelated($po_request, $ps_bundlename, $ps_form_prefix) {
* 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.
* showDeleted = if set to true, related items that have been deleted are returned. Default is false.
* 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).
+ * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
* @return array - list of related items
*/
public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
+ global $AUTH_CURRENT_USER_ID;
+ $vn_user_id = (isset($pa_options['user_id']) && $pa_options['user_id']) ? $pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
+ $vb_show_if_no_acl = (bool)($this->getAppConfig()->get('default_item_access_level') > __CA_ACL_NO_ACCESS__);
+
// convert options
if(isset($pa_options['restrictToType']) && (!isset($pa_options['restrict_to_type']) || !$pa_options['restrict_to_type'])) { $pa_options['restrict_to_type'] = $pa_options['restrictToType']; }
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) {
$va_wheres = array();
$va_selects = array();
-
+ $va_joins_post_add = array();
+
// TODO: get these field names from models
if ($t_item_rel) {
//define table names
@@ -3144,6 +3201,27 @@ public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
}
}
+ if ($this->getAppConfig()->get('perform_item_level_access_checking')) {
+ $t_user = new ca_users($vn_user_id, true);
+ if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
+ $va_group_ids = array_keys($va_groups);
+ } else {
+ $va_group_ids = array();
+ }
+
+ // Join to limit what browse table items are used to generate facet
+ $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";
+ $va_wheres[] = "(
+ ((
+ (ca_acl.user_id = ".(int)$vn_user_id.")
+ ".((sizeof($va_group_ids) > 0) ? "OR
+ (ca_acl.group_id IN (".join(",", $va_group_ids)."))" : "")."
+ OR
+ (ca_acl.user_id IS NULL and ca_acl.group_id IS NULL)
+ ) AND ca_acl.access >= ".__CA_ACL_READONLY_ACCESS__.")
+ ".(($vb_show_if_no_acl) ? "OR ca_acl.acl_id IS NULL" : "")."
+ )";
+ }
if (is_array($va_get_where)) {
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) {
$vs_sql = "
SELECT ".join(', ', $va_selects)."
FROM ".$va_path[0]."
- ".join("\n", $va_joins)."
+ ".join("\n", array_merge($va_joins, $va_joins_post_add))."
WHERE
".join(' AND ', array_merge($va_wheres, array('('.$va_path[1].'.'.$vs_other_field .' IN ('.join(',', $va_row_ids).'))')))."
{$vs_order_by}";
@@ -3332,7 +3410,7 @@ public function getRelatedItems($pm_rel_table_name_or_num, $pa_options=null) {
$vs_sql = "
SELECT ".join(', ', $va_selects)."
FROM ".$this->tableName()."
- ".join("\n", $va_joins)."
+ ".join("\n", array_merge($va_joins, $va_joins_post_add))."
WHERE
".join(' AND ', $va_wheres)."
{$vs_order_by}
@@ -4099,14 +4177,26 @@ public function setACLWorldAccess($pn_world_access) {
}
# --------------------------------------------------------------------------------------------
/**
- *
+ * Checks access control list for currently loaded row for the specified user and returns an access value. Values are:
+ *
+ * __CA_ACL_NO_ACCESS__ (0)
+ * __CA_ACL_READONLY_ACCESS__ (1)
+ * __CA_ACL_EDIT_ACCESS__ (2)
+ * __CA_ACL_EDIT_DELETE_ACCESS__ (3)
+ *
+ * @param ca_users $t_user A ca_users object
+ * @param int $pn_id Optional row_id to check ACL for; if omitted currently loaded row_id is used
+ * @return int An access value
*/
- public function checkACLAccessForUser($t_user) {
- if (!($vn_id = (int)$this->getPrimaryKey())) { return null; }
-
+ public function checkACLAccessForUser($t_user, $pn_id=null) {
+ if (!$pn_id) {
+ $pn_id = (int)$this->getPrimaryKey();
+ if (!$pn_id) { return null; }
+ }
+ if ($t_user->canDoAction('is_administrator')) { return __CA_ACL_EDIT_DELETE_ACCESS__; }
require_once(__CA_MODELS_DIR__.'/ca_acl.php');
- return ca_acl::loadACLForRow($t_user, $this->tableNum(), $vn_id);
+ return ca_acl::accessForRow($t_user, $this->tableNum(), $vn_id);
}
# ------------------------------------------------------
}
View
28 app/lib/core/BaseModel.php
@@ -318,6 +318,8 @@ class BaseModel extends BaseObject {
*/
static $s_ca_models_definitions;
+ static $s_instance_cache = array();
+
/**
* Constructor
* In general you should not call this constructor directly. Any table in your database
@@ -327,8 +329,9 @@ class BaseModel extends BaseObject {
* if omitted, an empty object is created which can be used to create a new row in the database.
* @return BaseModel
*/
- public function __construct($pn_id=null) {
+ public function __construct($pn_id=null, $pb_use_cache=true) {
$vs_table_name = $this->tableName();
+
if (!$this->FIELDS =& BaseModel::$s_ca_models_definitions[$vs_table_name]['FIELDS']) {
die("Field definitions not found for {$vs_table_name}");
}
@@ -355,7 +358,7 @@ public function __construct($pn_id=null) {
$this->setMode(ACCESS_READ);
- if ($pn_id) { $this->load($pn_id);}
+ if ($pn_id) { $this->load($pn_id, $pb_use_cache);}
}
/**
@@ -1532,8 +1535,15 @@ public function setMode($pn_mode) {
* @param mixed $pm_id primary key value of the record to load (assuming we have no composed primary key)
* @return bool success state
*/
- public function load ($pm_id=null) {
+ public function load($pm_id=null, $pb_use_cache=true) {
$this->clear();
+ $vs_table_name = $this->tableName();
+ 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])) {
+ $this->_FIELD_VALUES = BaseModel::$s_instance_cache[$vs_table_name][$pm_id];
+ $this->_FIELD_VALUES_OLD = $this->_FIELD_VALUES;
+ $this->_FILES_CLEAR = array();
+ return true;
+ }
if ($pm_id == null) {
//$this->postError(750,_t("Can't load record; key is blank"), "BaseModel->load()");
@@ -1626,6 +1636,8 @@ public function load ($pm_id=null) {
$this->_FIELD_VALUES_OLD = $this->_FIELD_VALUES;
$this->_FILES_CLEAR = array();
+
+ if ($vn_id = $this->getPrimaryKey()) { BaseModel::$s_instance_cache[$vs_table_name][$vn_id] = $this->_FIELD_VALUES; }
return true;
} else {
if (!is_array($pm_id)) {
@@ -2123,7 +2135,11 @@ public function insert ($pa_options=null) {
if ($vb_we_set_transaction) { $this->removeTransaction(true); }
- $this->_FIELD_VALUE_CHANGED = array();
+ $this->_FIELD_VALUE_CHANGED = array();
+
+ // Update instance cache
+ BaseModel::$s_instance_cache[$this->tableName()][$vn_id] = $this->_FIELD_VALUES;
+
return $vn_id;
} else {
foreach($o_db->errors() as $o_e) {
@@ -2668,6 +2684,9 @@ public function update ($pa_options=null) {
if ($vb_we_set_transaction) { $this->removeTransaction(true); }
$this->_FIELD_VALUE_CHANGED = array();
+
+ // Update instance cache
+ BaseModel::$s_instance_cache[$this->tableName()][$this->getPrimaryKey()] = $this->_FIELD_VALUES;
return true;
} else {
if ($vb_we_set_transaction) { $this->removeTransaction(false); }
@@ -6802,6 +6821,7 @@ public function htmlFormElement($ps_field, $ps_format=null, $pa_options=null) {
}
if (isset($pa_options['usewysiwygeditor']) && $pa_options['usewysiwygeditor']) {
+ JavascriptLoadManager::register("ckeditor");
$vs_width = $vn_display_width;
$vs_height = $vn_display_height;
if (!preg_match("!^[\d\.]+px$!i", $vs_width)) {
View
1  app/lib/core/Error/errors.en_us
@@ -192,6 +192,7 @@
2550 = Item has been deleted
2560 = Item is not correct type
2570 = Can't change access control list for this item
+2580 = You do not have access to this item
# --- Object representation errors
2700 = Could not update primary flag for representation
View
76 app/lib/core/Search/SearchEngine.php
@@ -37,7 +37,6 @@
require_once(__CA_LIB_DIR__."/core/Search/SearchCache.php");
require_once(__CA_LIB_DIR__."/core/Logging/Searchlog.php");
require_once(__CA_LIB_DIR__."/core/Utils/Timer.php");
-require_once(__CA_MODELS_DIR__.'/ca_lists.php');
require_once(__CA_APP_DIR__.'/helpers/accessHelpers.php');
require_once(__CA_LIB_DIR__."/core/Search/Common/Parsers/LuceneSyntaxParser.php");
@@ -45,6 +44,9 @@
require_once(__CA_LIB_DIR__."/core/Zend/Search/Lucene/Search/Query/Boolean.php");
require_once(__CA_LIB_DIR__."/core/Zend/Search/Lucene/Search/Query/Term.php");
+require_once(__CA_MODELS_DIR__.'/ca_lists.php');
+require_once(__CA_MODELS_DIR__.'/ca_acl.php');
+
# ----------------------------------------------------------------------
class SearchEngine extends SearchBase {
@@ -110,11 +112,14 @@ public function isValidOption($ps_option) {
* showDeleted = if set to true, related items that have been deleted are returned. Default is false.
* 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
* sets = if value is a list of set_ids, only rows that are members of those sets will be returned
+ * user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
*
* @return SearchResult Results packages in a SearchResult object, or sub-class of SearchResult if an instance was passed in $po_result
* @uses TimeExpressionParser::parse
*/
public function &search($ps_search, $po_result=null, $pa_options=null) {
+ global $AUTH_CURRENT_USER_ID;
+
$t = new Timer();
if (!is_array($pa_options)) { $pa_options = array(); }
$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) {
$va_hits = $this->filterHitsBySets($va_hits, $pa_options['sets']);
}
+ $vn_user_id = (isset($pa_options['user_id']) && (int)$pa_options['user_id']) ? (int)$pa_options['user_id'] : (int)$AUTH_CURRENT_USER_ID;
+ if ($this->opo_app_config->get('perform_item_level_access_checking')) {
+ $va_hits = $this->filterHitsByACL($va_hits, $vn_user_id, __CA_ACL_READONLY_ACCESS__);
+ }
+
if (isset($pa_options['sort']) && $pa_options['sort'] && ($pa_options['sort'] != '_natural')) {
$va_hits = $this->sortHits($va_hits, $pa_options['sort'], (isset($pa_options['sort_direction']) ? $pa_options['sort_direction'] : null));
}
@@ -216,15 +226,13 @@ public function &search($ps_search, $po_result=null, $pa_options=null) {
// log search
$o_log = new Searchlog();
- global $AUTH_CURRENT_USER_ID;
- $vn_search_user_id = $AUTH_CURRENT_USER_ID ? $AUTH_CURRENT_USER_ID : null;
$vn_search_form_id = isset($pa_options['form_id']) ? $pa_options['form_id'] : null;
$vs_log_details = isset($pa_options['log_details']) ? $pa_options['log_details'] : '';
$vs_search_source = isset($pa_options['search_source']) ? $pa_options['search_source'] : '';
$vn_execution_time = $t->getTime(4);
$o_log->log(array(
- 'user_id' => $vn_search_user_id,
+ 'user_id' => $vn_user_id,
'table_num' => $this->opn_tablenum,
'search_expression' => $ps_search,
'num_hits' => sizeof($va_hit_values),
@@ -394,6 +402,64 @@ public function filterHitsBySets($pa_hits, $pa_set_ids, $pa_options=null) {
}
# ------------------------------------------------------------------
/**
+ * @param $pa_hits Array of row_ids to filter. *MUST HAVE row_ids AS KEYS, NOT VALUES*
+ */
+ public function filterHitsByACL($pa_hits, $pn_user_id, $pn_access=__CA_ACL_READONLY_ACCESS__, $pa_options=null) {
+ if (!sizeof($pa_hits)) { return $pa_hits; }
+ if (!(int)$pn_user_id) { $pn_user_id = 0; }
+ if (!($t_table = $this->opo_datamodel->getInstanceByTableNum($this->opn_tablenum, true))) { return $pa_hits; }
+
+ $vs_table_name = $t_table->tableName();
+ $vs_table_pk = $t_table->primaryKey();
+
+ $t_user = new ca_users($pn_user_id);
+ if (is_array($va_groups = $t_user->getUserGroups()) && sizeof($va_groups)) {
+ $va_group_ids = array_keys($va_groups);
+ $vs_group_sql = '
+ OR
+ (ca_acl.group_id IN (?))';
+ $va_params = array((int)$this->opn_tablenum, (int)$pn_user_id, $va_group_ids);
+ } else {
+ $va_group_ids = null;
+ $vs_group_sql = '';
+ $va_params = array((int)$this->opn_tablenum, (int)$pn_user_id);
+ }
+
+ $o_db = new Db();
+ $qr_sort = $o_db->query($vs_sql = "
+ SELECT ca_acl.acl_id, {$vs_table_name}.{$vs_table_pk}, ca_acl.access
+ FROM {$vs_table_name}
+ INNER JOIN ca_acl ON ca_acl.row_id = {$vs_table_name}.{$vs_table_pk} AND ca_acl.table_num = ?
+ WHERE
+ {$vs_table_name}.{$vs_table_pk} IN (".join(", ", array_keys($pa_hits)).") AND
+
+ (
+ (ca_acl.user_id = ?)
+ {$vs_group_sql}
+ OR
+ (ca_acl.user_id IS NULL AND ca_acl.group_id IS NULL)
+ )
+ ", $va_params);
+ $va_hits = array();
+ while($qr_sort->nextRow()) {
+ $vn_id = $qr_sort->get($vs_table_pk, array('binary' => true));
+ unset($pa_hits[$vn_id]);
+ if ($qr_sort->get('access', array('binary' => true)) < $pn_access) { continue; }
+ $va_hits[$vn_id] = true;
+ }
+
+ // For row_ids that have no ACL entry
+ if ($this->opo_app_config->get('default_item_access_level') >= $pn_access) {
+ // Add row_ids that have no ACL entry because default access meets or exceeds requested access
+ foreach($pa_hits as $vn_id => $vb_dummy) {
+ $va_hits[$vn_id] = true;
+ }
+ }
+
+ return $va_hits;
+ }
+ # ------------------------------------------------------------------
+ /**
*
*/
public function getRandomResult($pn_num_hits=10, $po_result=null) {
@@ -1145,4 +1211,4 @@ static function quickSearch($ps_search, $ps_tablename, $pn_tablenum, $pa_options
}
# ------------------------------------------------------------------
}
-?>
+?>
View
18 app/lib/core/Search/SearchResult.php
@@ -561,21 +561,11 @@ function get($ps_field, $pa_options=null) {
case 'label':
case 'preferred_labels':
case $va_path_components['table_name'].'.preferred_labels':
- $vs_field_val = $va_relation_info['label'];
+
if ($vb_show_hierarachy) {
- if ($va_ids_by_hier = $this->get($va_path_components['table_name'].'.hierarchy.'.$t_instance->primaryKey(), array_merge($pa_options, array('returnAsArray' => true)))) {
-
- $va_vals = array();
- foreach($va_ids_by_hier as $va_ids) {
- foreach($va_ids as $vn_id) {
- if($t_instance->load($vn_id)) {
- $va_vals[] = $t_instance->get($va_path_components['table_name'].".preferred_labels", $pa_options);
- }
- }
- }
-
- $vs_field_val = join($vb_show_hierarachy ? $vs_hierarchical_delimiter : $vs_delimiter, $va_vals);
- }
+ $vs_field_val = $qr_rel_items->get($va_path_components['table_name'].'.hierarchy.preferred_labels', array_merge($pa_options, array('delimiter' => $vs_hierarchical_delimiter)));
+ } else {
+ $vs_field_val = $va_relation_info['label'];
}
$vs_value = str_replace("^{$vs_tag}", $vs_field_val, $vs_value);
break;
View
49 app/models/ca_acl.php
@@ -36,9 +36,9 @@
define('__CA_ACL_NO_ACCESS__', 0);
-define('__CA_ACL_READ_ACCESS__', 1);
+define('__CA_ACL_READONLY_ACCESS__', 1);
define('__CA_ACL_EDIT_ACCESS__', 2);
-define('__CA_ACL_DELETE_ACCESS__', 3);
+define('__CA_ACL_EDIT_DELETE_ACCESS__', 3);
BaseModel::$s_ca_models_definitions['ca_acl'] = array(
'NAME_SINGULAR' => _t('access control list'),
@@ -88,7 +88,7 @@
'LABEL' => _t('Access'), 'DESCRIPTION' => _t('Access'),
'BOUNDS_CHOICE_LIST' => array(
_t('none') => __CA_ACL_NO_ACCESS__,
- _t('can read') => __CA_ACL_READ_ACCESS__,
+ _t('can read') => __CA_ACL_READONLY_ACCESS__,
_t('can edit') => __CA_ACL_EDIT_ACCESS__,
_t('can edit + delete') => __CA_ACL_EDIT_DELETE_ACCESS__
)
@@ -190,6 +190,9 @@ class ca_acl extends BaseModel {
protected $FIELDS;
+
+ static $s_acl_access_value_cache = array();
+
# ------------------------------------------------------
# --- Constructor
#
@@ -205,17 +208,33 @@ public function __construct($pn_id=null) {
parent::__construct($pn_id); # call superclass constructor
}
# ------------------------------------------------------
- /**
+ /**
+ * Checks access control list for the specified row and user and returns an access value. Values are:
*
+ * __CA_ACL_NO_ACCESS__ (0)
+ * __CA_ACL_READONLY_ACCESS__ (1)
+ * __CA_ACL_EDIT_ACCESS__ (2)
+ * __CA_ACL_EDIT_DELETE_ACCESS__ (3)
+ *
+ * @param ca_users $t_user A ca_users object
+ * @param int $pn_table_num The table number for the row to check
+ * @param int $pn_row_id The primary key value for the row to check.
+ * @return int An access value
*/
- public static function loadACLForRow($t_user, $pn_table_num, $pn_row_id) {
+ public static function accessForRow($t_user, $pn_table_num, $pn_row_id) {
if (!is_object($t_user)) { $t_user = new ca_users(); }
$o_db = new Db();
+ $vn_user_id = (int)$t_user->getPrimaryKey();
+
+ if (isset(ca_acl::$s_acl_access_value_cache[$vn_user_id][$pn_table_num][$pn_row_id])) {
+ return ca_acl::$s_acl_access_value_cache[$vn_user_id][$pn_table_num][$pn_row_id];
+ }
+
$vn_access = null;
// try to load ACL for user
- if ($vn_user_id = (int)$t_user->getPrimaryKey()) {
+ if ($vn_user_id) {
$qr_res = $o_db->query("
SELECT max(access) a
FROM ca_acl
@@ -227,7 +246,9 @@ public static function loadACLForRow($t_user, $pn_table_num, $pn_row_id) {
if ($qr_res->nextRow()) {
if (strlen($vs_access = $qr_res->get('a'))) {
$vn_access = (int)$vs_access;
- if ($vn_access >= 3) { return $vn_access; } // max access found so just return
+ if ($vn_access >= __CA_ACL_EDIT_DELETE_ACCESS__) {
+ return ca_acl::$s_acl_access_value_cache[$vn_user_id][$pn_table_num][$pn_row_id] = $vn_access;
+ } // max access found so just return
}
}
@@ -247,7 +268,9 @@ public static function loadACLForRow($t_user, $pn_table_num, $pn_row_id) {
if (strlen($vs_access = $qr_res->get('a'))) {
$vn_acl_access = (int)$vs_access;
if ($vn_acl_access >= $vn_access) { $vn_access = $vn_acl_access; }
- if ($vn_access >= 3) { return $vn_access; } // max access found so just return
+ if ($vn_access >= __CA_ACL_EDIT_DELETE_ACCESS__) {
+ return ca_acl::$s_acl_access_value_cache[$vn_user_id][$pn_table_num][$pn_row_id] = $vn_access;
+ } // max access found so just return
}
}
}
@@ -263,15 +286,17 @@ public static function loadACLForRow($t_user, $pn_table_num, $pn_row_id) {
", (int)$pn_table_num, (int)$pn_row_id);
if ($qr_res->nextRow()) {
- if (strlen($vs_access = $qr_res->get('a')) && ((int)$vs_access > $vn_access)) {
- return (int)$vs_access;
+ if (strlen($vs_access = $qr_res->get('a')) && ((int)$vs_access >= $vn_access)) {
+ return ca_acl::$s_acl_access_value_cache[$vn_user_id][$pn_table_num][$pn_row_id] = (int)$vs_access;
}
}
- if (!is_null($vn_access)) { return $vn_access; }
+ if (!is_null($vn_access)) {
+ return ca_acl::$s_acl_access_value_cache[$vn_user_id][$pn_table_num][$pn_row_id] = $vn_access;
+ }
// If no ACL exists return default
$o_config = Configuration::load();
- return (int)$o_config->get('default_item_access_level');
+ return ca_acl::$s_acl_access_value_cache[$vn_user_id][$pn_table_num][$pn_row_id] = (int)$o_config->get('default_item_access_level');
}
# ------------------------------------------------------
}
View
2  app/models/ca_bundle_displays.php
@@ -1083,7 +1083,7 @@ public function getAvailableBundles($pm_table_name_or_num=null, $pa_options=null
'displayType' => DT_CHECKBOXES,
'width' => 10, 'height' => 1,
'takesLocale' => false,
- 'default' => '1',
+ 'default' => '0',
'label' => _t('Show hierarchy?'),
'description' => _t('If checked the full hierarchical path will be shown.')
),
View
108 app/models/ca_commerce_orders.php
@@ -34,6 +34,7 @@
*
*/
require_once(__CA_MODELS_DIR__.'/ca_commerce_transactions.php');
+require_once(__CA_MODELS_DIR__.'/ca_sets.php');
require_once(__CA_LIB_DIR__.'/core/Parsers/TimeExpressionParser.php');
require_once(__CA_LIB_DIR__.'/ca/Search/CommerceOrderSearch.php');
require_once(__CA_LIB_DIR__.'/core/Payment.php');
@@ -69,6 +70,7 @@
_t('submitted – awaiting quote') => 'SUBMITTED', // user has submitted order for pricing - only address may be modified
_t('awaiting payment') => 'AWAITING_PAYMENT', // order is awaiting payment before completion - only payment details can be submitted by user
_t('payment processed - awaiting digitization') => 'PROCESSED_AWAITING_DIGITIZATION', // processing completed; awaiting digitization before fulfillment
+ _t('payment processed - awaiting media access') => 'PROCESSED_AWAITING_MEDIA_ACCESS', // processing completed; awaiting transfer of media before fulfillment
_t('payment processed - ready for fulfillment') => 'PROCESSED', // processing completed; awaiting fulfillment
_t('completed') => 'COMPLETED', // order complete - user has been sent items
_t('reopened') => 'REOPENED' // order reopened due to issue
@@ -553,11 +555,15 @@ public function update($pa_options=null) {
case 'AWAITING_PAYMENT':
if ($this->get('payment_received_on') && $this->changed('payment_received_on')) {
// If it paid for then flip status to "PROCESSED" (if it's all ready to go) or "PROCESSED_AWAITING_DIGITIZATION" if stuff needs to be digitized
- $va_items_missing_media = $this->itemsMissingDownloadableMedia();
- if(sizeof($va_items_missing_media) > 0) {
+ if(sizeof($va_items_with_no_media = $this->itemsWithNoDownloadableMedia()) > 0) {
$this->set('order_status', 'PROCESSED_AWAITING_DIGITIZATION');
} else {
- $this->set('order_status', 'PROCESSED');
+ // If "original" files are missing then mark as PROCESSED_AWAITING_MEDIA_ACCESS
+ if (sizeof($va_items_missing_media = $this->itemsMissingDownloadableMedia('original'))) {
+ $this->set('order_status', 'PROCESSED_AWAITING_MEDIA_ACCESS');
+ } else {
+ $this->set('order_status', 'PROCESSED');
+ }
}
}
break;
@@ -567,7 +573,7 @@ public function update($pa_options=null) {
if($vn_rc = parent::update($pa_options)) {
if ($vb_status_changed) { $this->sendStatusChangeEmailNotification($vn_old_status, $vn_old_ship_date, $vn_old_shipped_on_date); }
- if (in_array($this->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION', 'COMPLETED'))) {
+ if (in_array($this->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION', 'PROCESSED_AWAITING_MEDIA_ACCESS', 'COMPLETED'))) {
// Delete originating set if configured to do so
if($this->opo_client_services_config->get('set_disposal_policy') == 'DELETE_WHEN_ORDER_PROCESSED') {
$t_trans = new ca_commerce_transactions($this->get('transaction_id'));
@@ -618,7 +624,7 @@ private function _preSaveActions() {
*/
public function sendStatusChangeEmailNotification($pn_old_status, $pn_old_ship_date, $pn_old_shipped_on_date) {
global $g_request;
- if (!$g_request) { return null; }
+ $vn_user_id = is_object($g_request) ? $g_request->getUserID() : null;
$vb_status_has_changed = (($vs_status = $this->get('order_status')) != $pn_old_status) ? true : false;
$vb_shipping_has_changed = (($this->get('shipped_on_date', array('GET_DIRECT_DATE' => true)) != $pn_old_shipped_on_date) || ($this->get('shipping_date', array('GET_DIRECT_DATE' => true)) != $pn_old_ship_date)) ? true : false;
@@ -647,31 +653,34 @@ public function sendStatusChangeEmailNotification($pn_old_status, $pn_old_ship_d
if ($vb_status_has_changed) { // has status changed?
$va_admin_addresses = null;
if (in_array($vs_status, $va_administrative_email_on_order_status)) { $va_admin_addresses = $va_administrative_email_addresses; }
-
switch($vs_status) {
case 'SUBMITTED':
$vs_subject = _t('Your order posted on %1 has been received', $vs_order_date);
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_submitted.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_submitted.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
break;
case 'AWAITING_PAYMENT':
$vs_subject = _t('Your order (%2) posted on %1 requires payment', $vs_order_date, $this->getOrderNumber());
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_awaiting_payment.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_awaiting_payment.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
break;
case 'PROCESSED_AWAITING_DIGITIZATION':
$vs_subject = _t('Payment for order (%2) posted on %1 has been processed; your downloads are now pending digitization of purchased items', $vs_order_date, $this->getOrderNumber());
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_processed_awaiting_digitization.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_processed_awaiting_digitization.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
+ break;
+ case 'PROCESSED_AWAITING_MEDIA_ACCESS':
+ $vs_subject = _t('Payment for order (%2) posted on %1 has been processed; your downloads are now pending digitization of transfer of media to the server', $vs_order_date, $this->getOrderNumber());
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_processed_awaiting_media_access.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
break;
case 'PROCESSED':
$vs_subject = _t('Payment for order (%2) posted on %1 has been processed', $vs_order_date, $this->getOrderNumber());
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_processed.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_processed.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
break;
case 'COMPLETED':
$vs_subject = _t('Your order (%2) posted on %1 is complete', $vs_order_date, $this->getOrderNumber());
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_completed.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_completed.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
break;
case 'REOPENED':
$vs_subject = _t('Order (%2) posted on %1 has been reopened', $vs_order_date, $this->getOrderNumber());
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_reopened.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_status_reopened.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this), null, $va_admin_addresses);
break;
}
} else {
@@ -683,14 +692,14 @@ public function sendStatusChangeEmailNotification($pn_old_status, $pn_old_ship_d
if (($vn_shipped_on_date > 0) && ($vn_shipped_on_date != $pn_old_shipped_on_date)) {
// Notify client that package has shipped
$vs_subject = _t('Order (%2) posted on %1 has shipped', $vs_order_date, $this->getOrderNumber());
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_shipped.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this));
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_shipped.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this));
return true;
}
if (($vn_ship_date > 0) && ($vn_ship_date != $pn_old_ship_date)) {
// Notify client that package has been schedule for shipping
$vs_subject = _t('Order (%2) posted on %1 has been scheduled for shipping', $vs_order_date, $this->getOrderNumber());
- caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_will_ship_on.tpl", array('subject' => $vs_subject, 'from_user_id' => $g_request->getUserID(), 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this));
+ caSendMessageUsingView($g_request, $vs_to_email, $vs_sender_email, "[{$vs_app_name}] {$vs_subject}", "commerce_order_will_ship_on.tpl", array('subject' => $vs_subject, 'from_user_id' => $vn_user_id, 'sender_name' => $vs_sender_name, 'sender_email' => $vs_sender_email, 'sent_on' => time(), 'login_url' => $vs_login_url, 't_order' => $this));
return true;
}
}
@@ -741,7 +750,7 @@ public function set($pa_fields, $pm_value="", $pa_options=null) {
foreach($pa_fields as $vs_f => $vs_v) {
switch($vs_f) {
case 'shipped_on_date':
- if (!in_array($this->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION'))) {
+ if (!in_array($this->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION', 'PROCESSED_AWAITING_MEDIA_ACCESS'))) {
$this->postError(1101, _t('Cannot ship order until it is paid for'), 'ca_commerce_orders->set()');
return false;
}
@@ -1662,11 +1671,12 @@ public function requiresDownload() {
}
# ------------------------------------------------------
/**
- *
+ * Checks all items in order that are downloaded and returns a list of item_ids for items
+ * that have no representations attached.
*
- * @return array
+ * @return array A list of item_ids for which no representations are defined.
*/
- public function itemsMissingDownloadableMedia() {
+ public function itemsWithNoDownloadableMedia() {
if (!$this->getPrimaryKey()) { return null; }
$o_db = $this->getDb();
@@ -1688,14 +1698,70 @@ public function itemsMissingDownloadableMedia() {
$t_object = new ca_objects();
$va_rep_counts = $t_object->getMediaCountsForIDs($va_object_ids);
- $va_items_missing_downloadable_media = array();
+ $va_items_with_no_downloadable_media = array();
while($qr_res->nextRow()) {
$vn_object_id = $qr_res->get('object_id');
if (!isset($va_rep_counts[$vn_object_id]) || !$va_rep_counts[$vn_object_id]) {
- $va_items_missing_downloadable_media[$qr_res->get('item_id')] = true;
+ $va_items_with_no_downloadable_media[$qr_res->get('item_id')] = true;
+ }
+ }
+ return array_keys($va_items_with_no_downloadable_media);
+ }
+ # ------------------------------------------------------
+ /**
+ * Checks all items in order that are downloadable and returns a list of object representation (organized by object)
+ * for which the specified version of media is missing on the server. This can be useful in situations where you are
+ * not keeping high-resolution media on the server. Rather than passing a dead media URL to the user it can be detected
+ * and action, such as downloading the missing media, taken.
+ *
+ * @param string $ps_version Optional version to check media for. If omitted defaults to version "original" (by convention this is the original uploaded media)
+ * @param array $pa_options Array of options. Support options are:
+ * returnRepresentationIDs = If set representation_id's for missing media are returned, otherwise MD5 hashes for missing representations are returned. Default is false.
+ * @return array List of missing items, key'ed on object_id. Value for each is a list of MD5 hashes (or representation_ids if the returnRepresentationIDs option is set)
+ */
+ public function itemsMissingDownloadableMedia($ps_version='original', $pa_options=null) {
+ if (!$this->getPrimaryKey()) { return null; }
+
+ $o_db = $this->getDb();
+
+ // Get items that require download
+ $qr_res = $o_db->query("
+ SELECT i.item_id, i.object_id, coxor.representation_id
+ FROM ca_commerce_order_items i
+ LEFT JOIN ca_commerce_order_items_x_object_representations AS coxor ON i.item_id = coxor.item_id
+ WHERE
+ i.fullfillment_method = 'DOWNLOAD' AND i.order_id = ?
+ ", (int)$this->getPrimaryKey());
+
+ $va_object_ids = array();
+ $va_representation_list = array();
+ while($qr_res->nextRow()) {
+ $vn_object_id = (int)$qr_res->get('object_id');
+ $vn_representation_id = (int)$qr_res->get('representation_id');
+ if ($vn_representation_id) {
+ $va_object_ids[$vn_object_id][$vn_representation_id] = true;
+ $va_representation_list[$vn_representation_id] = $vn_object_id;
+ } else {
+ // get all representations attached to this object
+ $qr_reps = $o_db->query("SELECT representation_id FROM ca_objects_x_object_representations WHERE object_id = ?", $vn_object_id);
+ while($qr_reps->nextRow()) {
+ $va_object_ids[$vn_object_id][$vn_representation_id = (int)$qr_reps->get('representation_id')] = true;
+ $va_representation_list[$vn_representation_id] = $vn_object_id;
+ }
+ }
+ }
+
+ // Check if files are missing
+ $va_missing_items = array();
+ if (sizeof($va_representation_list)) {
+ $qr_rep_check = $o_db->query("SELECT representation_id, media, md5 FROM ca_object_representations WHERE representation_id IN (?)", array(array_keys($va_representation_list)));
+ while($qr_rep_check->nextRow()) {
+ if (!file_exists($qr_rep_check->getMediaPath('media', $ps_version))) {
+ $va_missing_items[$va_representation_list[(int)$qr_rep_check->get('representation_id')]][] = (isset($pa_options['returnRepresentationIDs']) && $pa_options['returnRepresentationIDs']) ? $qr_rep_check->get('representation_id') : $qr_rep_check->get('md5');
+ }
}
}
- return array_keys($va_items_missing_downloadable_media);
+ return $va_missing_items;
}
# ------------------------------------------------------
/**
View
6 app/models/ca_users.php
@@ -287,8 +287,8 @@ class ca_users extends BaseModel {
# the record identified by the primary key value
#
# ------------------------------------------------------
- public function __construct($pn_id=null) {
- parent::__construct($pn_id); # call superclass constructor
+ public function __construct($pn_id=null, $pb_use_cache=false) {
+ parent::__construct($pn_id, $pb_use_cache); # call superclass constructor
$this->opo_auth_config = Configuration::load($this->getAppConfig()->get("authentication_config"));
}
@@ -300,7 +300,7 @@ public function __construct($pn_id=null) {
* @param integer $pm_user_id User id to load. If you pass a string instead of an integer, the record with a user name matching the string will be loaded.
* @return bool Returns true if no error, false if error occurred
*/
- public function load($pm_user_id=null) {
+ public function load($pm_user_id=null, $pb_use_cache=false) {
if (is_numeric($pm_user_id)) {
$vn_rc = parent::load($pm_user_id);
} else {
View
209 app/plugins/clientServices/clientServicesPlugin.php
@@ -0,0 +1,209 @@
+<?php
+/* ----------------------------------------------------------------------
+ * clientServicesPlugin.php :
+ * ----------------------------------------------------------------------
+ * CollectiveAccess
+ * Open-source collections management software
+ * ----------------------------------------------------------------------
+ *
+ * Software by Whirl-i-Gig (http://www.whirl-i-gig.com)
+ * Copyright 2012 Whirl-i-Gig
+ *
+ * For more information visit http://www.CollectiveAccess.org
+ *
+ * This program is free software; you may redistribute it and/or modify it under
+ * the terms of the provided license as published by Whirl-i-Gig
+ *
+ * CollectiveAccess is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTIES whatsoever, including any implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * This source code is free and modifiable under the terms of
+ * GNU General Public License. (http://www.gnu.org/copyleft/gpl.html). See
+ * the "license.txt" file for details, or visit the CollectiveAccess web site at
+ * http://www.CollectiveAccess.org
+ *
+ * ----------------------------------------------------------------------
+ */
+ require_once(__CA_APP_DIR__.'/helpers/mailHelpers.php');
+ require_once(__CA_APP_DIR__.'/helpers/clientServicesHelpers.php');
+ require_once(__CA_MODELS_DIR__.'/ca_lists.php');
+ require_once(__CA_MODELS_DIR__.'/ca_commerce_orders.php');
+ require_once(__CA_LIB_DIR__.'/core/Logging/Eventlog.php');
+ require_once(__CA_LIB_DIR__.'/core/Db.php');
+ require_once(__CA_LIB_DIR__.'/ca/Service/RestClient.php');
+
+ class clientServicesPlugin extends BaseApplicationPlugin {
+ # -------------------------------------------------------
+ private $opo_config;
+ private $opo_client_services_config;
+ # -------------------------------------------------------
+ public function __construct($ps_plugin_path) {
+ $this->description = _t('Handles periodic cleanup and email alert tasks for client services features.');
+ parent::__construct();
+
+ $this->opo_config = Configuration::load();
+ $this->opo_client_services_config = caGetClientServicesConfiguration();
+ }
+ # -------------------------------------------------------
+ /**
+ * Override checkStatus() to return true - the twitterPlugin plugin always initializes ok
+ */
+ public function checkStatus() {
+ return array(
+ 'description' => $this->getDescription(),
+ 'errors' => array(),
+ 'warnings' => array(),
+ 'available' => ((bool)$this->opo_config->get('enable_client_services'))
+ );
+ }
+ # -------------------------------------------------------
+ /**
+ * Perform client services-related periodic tasks
+ */
+ public function hookPeriodicTask(&$pa_params) {
+ $t_log = new Eventlog();
+ $o_db = new Db();
+
+
+
+ // Find any orders with status PROCESSED_AWAITING_MEDIA_ACCESS and fetch media
+ $qr_orders = $o_db->query("
+ SELECT order_id
+ FROM ca_commerce_orders
+ WHERE
+ order_status = 'PROCESSED_AWAITING_MEDIA_ACCESS'
+ ");
+
+ //
+ // Set up HTTP client for REST calls
+ //
+ $vs_base_url = $this->opo_client_services_config->get('remote_media_base_url'); //'http://dams.hsp.org/admin';
+ $o_client = new RestClient($vs_base_url."/service.php/iteminfo/ItemInfo/rest");
+ $o_res = $o_client->auth($this->opo_client_services_config->get('remote_media_username'), $this->opo_client_services_config->get('remote_media_password'))->get();
+ if (!$o_res->isSuccess()) {
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Could not authenticate to remote system %1', $vs_base_url), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ }
+
+ while($qr_orders->nextRow()) {
+ $t_order = new ca_commerce_orders($qr_orders->get('order_id'));
+
+ $vb_download_errors = false;
+ if ($t_order->getPrimaryKey() && (sizeof($va_missing_media = $t_order->itemsMissingDownloadableMedia()))) {
+ $va_missing_media_representation_ids = $t_order->itemsMissingDownloadableMedia('original', array('returnRepresentationIDs' => true));
+ foreach($va_missing_media as $vn_object_id => $va_representation_md5s) {
+ foreach($va_representation_md5s as $vn_i => $vs_representation_md5) {
+ $o_xml = $o_client->getObjectRepresentationURLByMD5($vs_representation_md5, 'original')->get();
+ $vs_url = (string)$o_xml->getObjectRepresentationURLByMD5->original;
+
+ // fetch the file
+ $t_rep = new ca_object_representations($va_missing_media_representation_ids[$vn_object_id][$vn_i]);
+ if ($t_rep->getPrimaryKey() && ($vs_target_path = $t_rep->getMediaPath('media', 'original'))) {
+ if ($r_source = fopen($vs_url, "rb")) {
+ if ($r_target = fopen ($vs_target_path, "wb")) {
+ while(feof($r_source) === false) {
+ fwrite($r_target, fread($r_source, 1024 * 8), 1024 * 8);
+ }
+ fclose($r_target);
+ } else {
+ $vb_download_errors = true;
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Could not open target path %1', $vs_target_path), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ }
+ fclose($r_source);
+ } else {
+ $vb_download_errors = true;
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Could not open download URL "%1"', $vs_url), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ }
+
+ // verify the file was downloaded correctly
+ if (($vs_target_md5 = md5_file($vs_target_path)) !== $vs_representation_md5) {
+ unlink($vs_target_path);
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Media file %1 failed to be downloaded from %2; checksums differ: %3/%4', $vs_target_path, $vs_url, $vs_representation_md5, $vs_target_md5), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ $vb_download_errors = true;
+ }
+ } else {
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Invalid representation_id "%1" or target path "%2"', $vn_representation_id, $vs_representation_md5, $vs_target_path), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ $vb_download_errors = true;
+ }
+ }
+ }
+ }
+ if (!$vb_download_errors) {
+ $t_order->setMode(ACCESS_WRITE);
+ $t_order->set('order_status', 'PROCESSED');
+ $t_order->update();
+ if ($t_order->numErrors()) {
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Change of order status to PROCESSED from PROCESSED_AWAITING_MEDIA_ACCESS failed for order_id %1: %2', $t_order->getPrimaryKey(), join('; ', $t_order->getErrors())), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ }
+ }
+ }
+
+ // Find any orders with status PROCESSED_AWAITING_DIGITIZATION where all media are now present
+ $qr_orders = $o_db->query("
+ SELECT order_id
+ FROM ca_commerce_orders
+ WHERE
+ order_status = 'PROCESSED_AWAITING_DIGITIZATION'
+ ");
+
+ while($qr_orders->nextRow()) {
+ $t_order = new ca_commerce_orders($qr_orders->get('order_id'));
+ if ($t_order->getPrimaryKey() && !sizeof($t_order->itemsWithNoDownloadableMedia())) {
+ $t_order->setMode(ACCESS_WRITE);
+ $t_order->set('order_status', 'PROCESSED');
+ $t_order->update();
+
+ if ($t_order->numErrors()) {
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Change of order status to PROCESSED from PROCESSED_AWAITING_DIGITIZATION failed for order_id %1: %2', $t_order->getPrimaryKey(), join('; ', $t_order->getErrors())), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ }
+ }
+ }
+
+ // Find orders paid/shipped more than X days ago and mark them as "COMPLETED"
+ $vn_days = (int)$this->opo_client_services_config->get('completed_order_age_threshold');
+
+ if ($vn_days > 1) {
+
+ $vn_threshold = (int)(time() - ($vn_days * 24 * 60 * 60));
+
+ $qr_orders = $o_db->query("
+ SELECT order_id
+ FROM ca_commerce_orders
+ WHERE
+ (order_status = 'PROCESSED')
+ AND
+ ((payment_received_on > 0) AND (payment_received_on < ?))
+ AND
+ (
+ (shipping_date IS NULL AND shipped_on_date IS NULL)
+ OR
+ (
+ (shipped_on_date > 0) AND (shipped_on_date < ?)
+ )
+ )
+ ", $vn_threshold, $vn_threshold);
+
+ while($qr_orders->nextRow()) {
+ $t_order = new ca_commerce_orders($qr_orders->get('order_id'));
+ if ($t_order->getPrimaryKey()) {
+ $t_order->setMode(ACCESS_WRITE);
+ $t_order->set('order_status', 'COMPLETED');
+ $t_order->update();
+
+ if ($t_order->numErrors()) {
+ $t_log->log(array('CODE' => 'ERR', 'MESSAGE' => _t('Change of order status to COMPLETED from PROCESSED failed for order_id %1: %2', $t_order->getPrimaryKey(), join('; ', $t_order->getErrors())), 'SOURCE' => 'clientServicesPlugin->hookPeriodicTask'));
+ }
+ }
+ }
+ }
+ }
+ # -------------------------------------------------------
+ /**
+ * Get plugin user actions
+ */
+ static public function getRoleActionList() {
+ return array();
+ }
+ # -------------------------------------------------------
+ }
+?>
View
6 app/version.php
@@ -1,10 +1,10 @@
<?php
# CollectiveAccess version number; don't change
- define('__CollectiveAccess__', '1.2');
+ define('__CollectiveAccess__', '1.3');
# Schema revision
define('__CollectiveAccess_Schema_Rev__', 64);
# Release type
- define('__CollectiveAccess_Release_Type__', 'SVN');
-?>
+ define('__CollectiveAccess_Release_Type__', 'GIT');
+?>
View
9 support/sql/migrations/53.sql
@@ -4,16 +4,15 @@
Description:
*/
-
-ALTER TABLE ca_commerce_fulfillment_events MODIFY COLUMN fulfillment_method varchar(40) not null;
-ALTER TABLE ca_commerce_fulfillment_events MODIFY COLUMN fulfillment_details blob not null;
+ALTER TABLE ca_commerce_fulfillment_events CHANGE COLUMN fullfillment_method fulfillment_method varchar(40) not null;
+ALTER TABLE ca_commerce_fulfillment_events CHANGE COLUMN fullfillment_details fulfillment_details blob not null;
/* If you updated from SVN in December 2011 you may have run a migration (since removed) on your database that */
/* incorrectly renames the fulfillment_method and fulfillment_details fields in the ca_commerce_fulfillment_events table. */
/* If the two lines above throw an error replace them with the two lines below, which will resolve the issue. */
-/* ALTER TABLE ca_commerce_fulfillment_events CHANGE COLUMN fullfillment_method fulfillment_method varchar(40) not null; */
-/* ALTER TABLE ca_commerce_fulfillment_events CHANGE COLUMN fullfillment_details fulfillment_details blob not null; */
+/* ALTER TABLE ca_commerce_fulfillment_events MODIFY COLUMN fulfillment_method varchar(40) not null; */
+/* ALTER TABLE ca_commerce_fulfillment_events MODIFY COLUMN fulfillment_details blob not null; */
/*==========================================================================*/
View
20 themes/default/views/client/order_overview_html.php
@@ -100,6 +100,16 @@
TooltipManager::add("#commerceOrderStatusMessage", $vs_order_status_description = _t('Order has been paid for and will be ready for fulfillment via user-initiated download once all items are digitized. You will be informed by email when the items are ready for download.'));
}
break;
+ case 'PROCESSED_AWAITING_MEDIA_ACCESS':
+ $vs_status_message = _t('Order status: %1', $vs_order_status_display);
+
+ if ($t_order->requiresShipping()) {
+ TooltipManager::add("#commerceOrderStatusMessage", $vs_order_status_description = _t('Order has been paid for and is ready for fulfillment. When the order has shipped click on the "record shipment details" below and enter the ship date.'));
+ $vs_next_step = caNavLink($this->request, _t('Record shipment details')." &rsaquo;", 'caClientOrderOverviewButton', 'client', 'OrderEditor', 'Shipping', array('order_id' => $vn_order_id));
+ } else {
+ TooltipManager::add("#commerceOrderStatusMessage", $vs_order_status_description = _t('Order has been paid for and will be ready for fulfillment via user-initiated download once all items have been transferred to the server. You will be informed by email when the items are ready for download.'));
+ }
+ break;
case 'COMPLETED':
$vs_status_message = _t('Order status: %1', $vs_order_status_display);
TooltipManager::add("#commerceOrderStatusMessage", $vs_order_status_description = _t('Order has been fulfilled and is complete. No further action is required.'));
@@ -139,6 +149,7 @@
switch($t_order->get('order_status')) {
case 'PROCESSED':
case 'PROCESSED_AWAITING_DIGITIZATION':
+ case 'PROCESSED_AWAITING_MEDIA_ACCESS':
if (!$t_order->get('shipped_on_date') && !$t_order->get('ship_date')) { // Order paid for but not shipped or estimate ship date set
$va_warnings[] = _t('Warning: order requires shipping!');
}
@@ -156,6 +167,7 @@
switch($t_order->get('order_status')) {
case 'PROCESSED':
case 'PROCESSED_AWAITING_DIGITIZATION':
+ case 'PROCESSED_AWAITING_MEDIA_ACCESS':
if (!$t_order->get('payment_received_on')) { // no payment date for processed order?
$va_warnings[] = _t('Warning: no payment recorded!');
}
@@ -218,7 +230,7 @@
<div class="overviewItem"><?php print _t("%1 items planned <a href='%5'>for shipment</a> to %2 via %3 is on %4", $va_item_counts_by_fulfillment_method['SHIPMENT'], $vs_shipping_destination, $vs_shipping_method, $vs_ship_date, caNavUrl($this->request, 'client', 'OrderEditor', 'Shipping', array('order_id' => $vn_order_id))); ?></div>
<?php
} else {
- if (in_array($t_order->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION'))) {
+ if (in_array($t_order->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION', 'PROCESSED_AWAITING_MEDIA_ACCESS'))) {
// needs to be shipped now
?>
<div class="overviewItem"><?php print _t("%1 items require <a href='%4'>shipping</a> to %2 via %3", $va_item_counts_by_fulfillment_method['SHIPMENT'], $vs_shipping_destination, $vs_shipping_method, caNavUrl($this->request, 'client', 'OrderEditor', 'Shipping', array('order_id' => $vn_order_id))); ?></div>
@@ -249,6 +261,12 @@
<div class="overviewItem"><?php print _t("%1 items will be available for download once digitization is completed", $va_item_counts_by_fulfillment_method['DOWNLOAD']); ?></div>
<?php
break;
+ case 'PROCESSED_AWAITING_MEDIA_ACCESS':
+ // download must wait until material is available on the server
+?>
+ <div class="overviewItem"><?php print _t("%1 items will be available for download once it has been transferred to the server", $va_item_counts_by_fulfillment_method['DOWNLOAD']); ?></div>
+<?php
+ break;
default:
// will need to be shipped after payment
?>
View
4 themes/default/views/client/order_shipping_html.php
@@ -33,6 +33,8 @@
$vn_transaction_id = $this->getVar('transaction_id');
$va_errors = $this->getVar('errors');
+ print_R($t_order->itemsMissingDownloadableMedia('original'));
+
$vs_currency_symbol = $this->getVar('currency_symbol');
$vs_currency_input_format = "<div class='formLabel'>^LABEL<br/>{$vs_currency_symbol}^ELEMENT</div>";
@@ -55,7 +57,7 @@
"shipping_method", "shipping_cost", "handling_cost", "shipping_notes", "shipping_date", "shipped_on_date"
);
foreach($va_shipping_fields as $vs_f) {
- if (($vs_f == 'shipped_on_date') && (!in_array($t_order->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION', 'COMPLETED')))) { continue; } // don't show shipped on field if order is not paid for
+ if (($vs_f == 'shipped_on_date') && (!in_array($t_order->get('order_status'), array('PROCESSED', 'PROCESSED_AWAITING_DIGITIZATION', 'PROCESSED_AWAITING_MEDIA_ACCESS', 'COMPLETED')))) { continue; } // don't show shipped on field if order is not paid for
$va_info = $t_order->getFieldInfo($vs_f);
if (($vn_width = $va_info['DISPLAY_WIDTH']) > $vn_max_field_width) { $vn_width = $vn_max_field_width; }
View
48 themes/default/views/mailTemplates/commerce_order_status_processed_awaiting_media_access.tpl
@@ -0,0 +1,48 @@
+<?php
+ $t_order = $this->getVar('t_order');
+ $o_client_services_config = caGetClientServicesConfiguration();
+
+ $vb_requires_shipping = $t_order->requiresShipping();
+ $vb_requires_download = $t_order->requiresDownload();
+?>
+You were sent the following message by <em><?php print $this->getVar('sender_name'); ?></em> on <em><?php print date('F j, Y g:i a', $this->getVar('sent_on')); ?></em>:
+
+<p>Your order <?php print $t_order->getOrderNumber(); ?> submitted on <?php print date('F j, Y g:i a', (int)$t_order->get('created_on', array('GET_DIRECT_DATE' => true))); ?> has been processed.
+<?php
+ if ($vb_requires_download) {
+?>
+ Some of your order requires transfer of media to the server. You will be informed by email when your items are ready for download. Note that transfer may take up to 24 hours.
+<?php
+ }
+ if ($vb_requires_shipping && (($vs_shipping_method = $t_order->get('shipping_method')) != 'NONE')) {
+?>
+</p>
+<p>
+<?php
+ if ($vn_ship_date = $t_order->get('shipped_on_date', array('GET_DIRECT_DATE' => true))) {
+ // Already shipped
+?>
+ Your order was shipped via <?php print $vs_shipping_method; ?> on <?php print date('F j, Y', $vn_ship_date); ?>.
+<?php
+ } else {
+ if ($vn_ship_date = $t_order->get('shipping_date', array('GET_DIRECT_DATE' => true))) {
+ // Not yet shipped but expected shipping date known
+?>
+ Your order will be shipped via <?php print $vs_shipping_method; ?> on <?php print date('F j, Y', $vn_ship_date); ?>.
+<?php
+ } else {
+ // Shipping data not known yet.
+?>
+ Your order will be shipped via <?php print $vs_shipping_method; ?>. You will receive an email when the expected ship date is determined.
+<?php
+ }
+ }
+?>
+</p>
+<?php
+ }
+?>
+
+<p>Log in at <?php print $this->getVar('login_url'); ?> to review your order under <em>My Account</em> and communicate with the R&R Associate.</p>
+
+<p>If you have images ready to download, you may download them through your order overview screen under Account: <?php print $this->getVar('login_url'); ?></p>
Please sign in to comment.
Something went wrong with that request. Please try again.