/// <summary> /// Get a list of all inherited Attributes that should be applied to this entity. /// </summary> /// <returns>A list of all inherited AttributeCache objects.</returns> public override List <AttributeCache> GetInheritedAttributes(Rock.Data.RockContext rockContext) { var entityTypeCache = EntityTypeCache.Get(TypeId); // Get the registration var registration = this.Registration; if (registration == null && this.RegistrationId > 0) { registration = new RegistrationService(rockContext) .Queryable().AsNoTracking() .FirstOrDefault(r => r.Id == this.RegistrationId); } if (entityTypeCache == null || registration == null) { return(null); } // Get the instance var registrationInstance = registration.RegistrationInstance; if (registrationInstance == null && registration.RegistrationInstanceId > 0) { registrationInstance = new RegistrationInstanceService(rockContext) .Queryable().AsNoTracking() .FirstOrDefault(r => r.Id == registration.RegistrationInstanceId); } if (registrationInstance == null) { return(null); } // Get all attributes there were defined for instance's template. var attributes = new List <AttributeCache>(); foreach (var entityTypeAttribute in AttributeCache.GetByEntityType(entityTypeCache.Id) .Where(e => e.EntityTypeQualifierColumn == "RegistrationTemplateId" && e.EntityTypeQualifierValue.AsInteger() == registrationInstance.RegistrationTemplateId)) { attributes.Add(entityTypeAttribute); } return(attributes); }
/// <summary> /// Get a list of all inherited Attributes that should be applied to this entity. /// </summary> /// <returns>A list of all inherited AttributeCache objects.</returns> public override List <AttributeCache> GetInheritedAttributes(Rock.Data.RockContext rockContext) { var calendarIds = this.EventCalendarItems.Select(c => c.EventCalendarId).ToList(); if (!calendarIds.Any()) { return(null); } var inheritedAttributes = new Dictionary <int, List <AttributeCache> >(); calendarIds.ForEach(c => inheritedAttributes.Add(c, new List <AttributeCache>())); // // Check for any calendar item attributes that the event item inherits. // var calendarItemEntityType = EntityTypeCache.Get(typeof(EventCalendarItem)); if (calendarItemEntityType != null) { foreach (var calendarItemEntityAttribute in AttributeCache .GetByEntityType(calendarItemEntityType.Id) .Where(a => a.EntityTypeQualifierColumn == "EventCalendarId" && calendarIds.Contains(a.EntityTypeQualifierValue.AsInteger()))) { inheritedAttributes[calendarItemEntityAttribute.EntityTypeQualifierValue.AsInteger()].Add(calendarItemEntityAttribute); } } // // Walk the generated list of attribute groups and put them, ordered, into a list // of inherited attributes. // var attributes = new List <AttributeCache>(); foreach (var attributeGroup in inheritedAttributes) { foreach (var attribute in attributeGroup.Value.OrderBy(a => a.Order)) { attributes.Add(attribute); } } return(attributes); }
/// <summary> /// Gets a list of all attributes defined for the GroupTypes specified that /// match the entityTypeQualifierColumn and the GroupType Ids. /// </summary> /// <param name="rockContext">The database context to operate in.</param> /// <param name="entityTypeId">The Entity Type Id for which Attributes to load.</param> /// <param name="entityTypeQualifierColumn">The EntityTypeQualifierColumn value to match against.</param> /// <returns>A list of attributes defined in the inheritance tree.</returns> public List <AttributeCache> GetInheritedAttributesForQualifier(Rock.Data.RockContext rockContext, int entityTypeId, string entityTypeQualifierColumn) { var groupTypeIds = GetInheritedGroupTypeIds(rockContext); var inheritedAttributes = new Dictionary <int, List <AttributeCache> >(); groupTypeIds.ForEach(g => inheritedAttributes.Add(g, new List <AttributeCache>())); // // Walk each group type and generate a list of matching attributes. // foreach (var entityTypeAttribute in AttributeCache.GetByEntityType(entityTypeId)) { // group type ids exist and qualifier is for a group type id if (string.Compare(entityTypeAttribute.EntityTypeQualifierColumn, entityTypeQualifierColumn, true) == 0) { int groupTypeIdValue = int.MinValue; if (int.TryParse(entityTypeAttribute.EntityTypeQualifierValue, out groupTypeIdValue) && groupTypeIds.Contains(groupTypeIdValue)) { inheritedAttributes[groupTypeIdValue].Add(entityTypeAttribute); } } } // // Walk the generated list of attribute groups and put them, ordered, into a list // of inherited attributes. // var attributes = new List <AttributeCache>(); foreach (var attributeGroup in inheritedAttributes) { foreach (var attribute in attributeGroup.Value.OrderBy(a => a.Order)) { attributes.Add(attribute); } } return(attributes); }
/// <summary> /// Filters the attributes. /// </summary> /// <param name="rockContext">The rock context.</param> /// <param name="items">The items.</param> /// <param name="person">The person.</param> private static void FilterAttributes(Data.RockContext rockContext, IEnumerable <Attribute.IHasAttributes> items, Rock.Model.Person person) { if (!items.Any()) { return; } var itemType = items.First().GetType(); var entityType = EntityTypeCache.Get(itemType); if (entityType == null) { // shouldn't happen return; } var entityTypeAttributes = AttributeCache.GetByEntityType(entityType.Id); // only return attributes that the person has VIEW auth to // NOTE: There are some Attributes that even Admin doesn't have VIEW auth so (For example, some of obsolete DISC attributes) foreach (var attribute in entityTypeAttributes) { if (!attribute.IsAuthorized(Rock.Security.Authorization.VIEW, person)) { foreach (var item in items) { if (item.AttributeValues.ContainsKey(attribute.Key)) { item.AttributeValues.Remove(attribute.Key); } if (item.Attributes.ContainsKey(attribute.Key)) { item.Attributes.Remove(attribute.Key); } } } } }
/// <summary> /// Gets a list of all attributes defined for the ConnectionTypes specified that /// match the entityTypeQualifierColumn and the ConnectionRequest Ids. /// </summary> /// <param name="rockContext">The database context to operate in.</param> /// <param name="entityTypeId">The Entity Type Id for which Attributes to load.</param> /// <param name="entityTypeQualifierColumn">The EntityTypeQualifierColumn value to match against.</param> /// <returns>A list of attributes defined in the inheritance tree.</returns> public List <AttributeCache> GetInheritedAttributesForQualifier(Rock.Data.RockContext rockContext, int entityTypeId, string entityTypeQualifierColumn) { var attributes = new List <AttributeCache>(); // // Walk each group type and generate a list of matching attributes. // foreach (var attribute in AttributeCache.GetByEntityType(entityTypeId)) { // group type ids exist and qualifier is for a group type id if (string.Compare(attribute.EntityTypeQualifierColumn, entityTypeQualifierColumn, true) == 0) { int groupTypeIdValue = int.MinValue; if (int.TryParse(attribute.EntityTypeQualifierValue, out groupTypeIdValue) && this.Id == groupTypeIdValue) { attributes.Add(attribute); } } } return(attributes.OrderBy(a => a.Order).ToList()); }
/// <summary> /// Parses the where. /// </summary> /// <param name="whereClause">The where clause.</param> /// <param name="type">The type.</param> /// <param name="service">The service.</param> /// <param name="parmExpression">The parm expression.</param> /// <param name="entityType">Type of the entity.</param> /// <param name="entityTypeCache">The entity type cache.</param> /// <returns></returns> private Expression ParseWhere(string whereClause, Type type, IService service, ParameterExpression parmExpression, Type entityType, EntityTypeCache entityTypeCache) { Expression returnExpression = null; // find locations of and/or's var expressionComponents = Regex.Split(whereClause, @"(\|\||&&)"); var currentExpressionComparisonType = ExpressionComparisonType.And; foreach (var component in expressionComponents) { if (component == "||") { currentExpressionComparisonType = ExpressionComparisonType.Or; continue; } if (component == "&&") { currentExpressionComparisonType = ExpressionComparisonType.And; continue; } // parse the part to get the expression string regexPattern = @"([a-zA-Z]+)|(==|<=|>=|<|!=|\^=|\*=|\*!|_=|_!|>|\$=|#=)|("".*""|\d+)"; var expressionParts = Regex.Matches(component, regexPattern) .Cast <Match>() .Select(m => m.Value) .ToList(); if (expressionParts.Count == 3) { var property = expressionParts[0]; var operatorType = expressionParts[1]; var value = expressionParts[2].Replace("\"", ""); List <string> selectionParms = new List <string>(); selectionParms.Add(PropertyComparisonConversion(operatorType).ToString()); selectionParms.Add(value); selectionParms.Add(property); Expression expression = null; if (entityType.GetProperty(property) != null) { var entityProperty = entityType.GetProperty(property); expression = ExpressionHelper.PropertyFilterExpression(selectionParms, parmExpression, property, entityProperty.PropertyType); } else { AttributeCache filterAttribute = null; Expression attributeWhereExpression = null; var attributeKey = property; // We would really love to further qualify this beyond the EntityType by including the // EntityTypeQualifier and EntityTypeQualifierValue but we can't easily do that so, we // will do that "Just in case..." code below (because this actually happened in our Spark // environment. // Also, there could be multiple attributes that have the same key (due to attribute qualifiers or just simply a duplicate key) var entityAttributeListForAttributeKey = AttributeCache.GetByEntityType(entityTypeCache.Id) .Where(a => a != null && a.Key == attributeKey).ToList(); // Just in case this EntityType has multiple attributes with the same key, create a OR'd clause for each attribute that has this key // NOTE: this could easily happen if doing an entity command against a DefinedValue, and the same attribute key is used in more than one defined type foreach (var attribute in entityAttributeListForAttributeKey) { filterAttribute = attribute; var attributeEntityField = EntityHelper.GetEntityFieldForAttribute(filterAttribute); if (attributeWhereExpression == null) { attributeWhereExpression = ExpressionHelper.GetAttributeExpression(service, parmExpression, attributeEntityField, selectionParms); } else { attributeWhereExpression = Expression.OrElse(attributeWhereExpression, ExpressionHelper.GetAttributeExpression(service, parmExpression, attributeEntityField, selectionParms)); } } if (attributeWhereExpression != null) { expression = attributeWhereExpression; } } if (returnExpression == null) { returnExpression = expression; } else { if (expression == null) { // unable to match to property or attribute, so just return what we got return(returnExpression); } if (currentExpressionComparisonType == ExpressionComparisonType.And) { returnExpression = Expression.AndAlso(returnExpression, expression); } else { returnExpression = Expression.OrElse(returnExpression, expression); } } } else { // error in parsing expression throw new Exception("Error in Where expression"); } } return(returnExpression); }
/// <summary> /// Renders the specified context. /// </summary> /// <param name="context">The context.</param> /// <param name="result">The result.</param> /// <exception cref="System.Exception">Your Lava command must contain at least one valid filter. If you configured a filter it's possible that the property or attribute you provided does not exist.</exception> public override void Render(Context context, TextWriter result) { // first ensure that entity commands are allowed in the context if (!this.IsAuthorized(context)) { result.Write(string.Format(RockLavaBlockBase.NotAuthorizedMessage, this.Name)); base.Render(context, result); return; } bool hasFilter = false; var modelName = string.Empty; // get a service for the entity based off it's friendly name if (_entityName == "business") { modelName = "Rock.Model.Person"; } else { modelName = "Rock.Model." + _entityName; } // Check first to see if this is a core model. use the createIfNotFound = false option var entityTypeCache = EntityTypeCache.Get(modelName, false); if (entityTypeCache == null) { var entityTypes = EntityTypeCache.All(); // If not, look for first plug-in model that has same friendly name entityTypeCache = entityTypes .Where(e => e.IsEntity && !e.Name.StartsWith("Rock.Model") && e.FriendlyName != null && e.FriendlyName.RemoveSpaces().ToLower() == _entityName) .OrderBy(e => e.Id) .FirstOrDefault(); // If still null check to see if this was a duplicate class and full class name was used as entity name if (entityTypeCache == null) { modelName = _entityName.Replace('_', '.'); entityTypeCache = entityTypes.Where(e => String.Equals(e.Name, modelName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); } } if (entityTypeCache != null) { Type entityType = entityTypeCache.GetEntityType(); if (entityType != null) { // Get the appropriate database context for this entity type. // Note that this may be different from the standard RockContext if the entity is sourced from a plug-in. var dbContext = Reflection.GetDbContextForEntityType(entityType); // Check if there is a RockContext in the Lava context. If so then use the RockContext passed in. if (dbContext is RockContext) { var lavaContext = context.Registers["RockContext"]; if (lavaContext.IsNotNull()) { dbContext = (DbContext)lavaContext; } } // Disable change-tracking for this data context to improve performance - objects supplied to a Lava context are read-only. dbContext.Configuration.AutoDetectChangesEnabled = false; // Create an instance of the entity's service IService serviceInstance = Reflection.GetServiceForEntityType(entityType, dbContext); ParameterExpression paramExpression = Expression.Parameter(entityType, "x"); Expression queryExpression = null; // the base expression we'll use to build our query from // Parse markup var parms = ParseMarkup(_markup, context); if (parms.Any(p => p.Key == "id")) { string propertyName = "Id"; List <string> selectionParms = new List <string>(); selectionParms.Add(PropertyComparisonConversion("==").ToString()); selectionParms.Add(parms["id"].ToString()); selectionParms.Add(propertyName); var entityProperty = entityType.GetProperty(propertyName); queryExpression = ExpressionHelper.PropertyFilterExpression(selectionParms, paramExpression, propertyName, entityProperty.PropertyType); hasFilter = true; } else { // where clause expression if (parms.Any(p => p.Key == "where")) { queryExpression = ParseWhere(parms["where"], entityType, serviceInstance, paramExpression, entityType, entityTypeCache); if (queryExpression != null) { hasFilter = true; } } // DataView expression if (parms.Any(p => p.Key == "dataview")) { var dataViewId = parms["dataview"].AsIntegerOrNull(); if (dataViewId.HasValue) { var dataViewExpression = GetDataViewExpression(dataViewId.Value, serviceInstance, paramExpression, entityTypeCache); if (queryExpression == null) { queryExpression = dataViewExpression; hasFilter = true; } else { queryExpression = Expression.AndAlso(queryExpression, dataViewExpression); } } } // process dynamic filter expressions (from the query string) if (parms.Any(p => p.Key == "dynamicparameters")) { var dynamicFilters = parms["dynamicparameters"].Split(',') .Select(x => x.Trim()) .Where(x => !string.IsNullOrWhiteSpace(x)) .ToList(); foreach (var dynamicFilter in dynamicFilters) { var dynamicFilterValue = HttpContext.Current.Request[dynamicFilter]; var dynamicFilterExpression = GetDynamicFilterExpression(dynamicFilter, dynamicFilterValue, entityType, serviceInstance, paramExpression); if (dynamicFilterExpression != null) { if (queryExpression == null) { queryExpression = dynamicFilterExpression; hasFilter = true; } else { queryExpression = Expression.AndAlso(queryExpression, dynamicFilterExpression); } } } } } // Make the query from the expression. /* [2020-10-08] DL * "Get" is intentionally used here rather than "GetNoTracking" to allow lazy-loading of navigation properties from the Lava context. * (Refer https://github.com/SparkDevNetwork/Rock/issues/4293) */ MethodInfo getMethod = serviceInstance.GetType().GetMethod("Get", new Type[] { typeof(ParameterExpression), typeof(Expression), typeof(Rock.Web.UI.Controls.SortProperty), typeof(int?) }); if (getMethod != null) { // get a listing of ids and build it into the query expression if (parms.Any(p => p.Key == "ids")) { List <int> value = parms["ids"].ToString().Split(',').Select(int.Parse).ToList(); MemberExpression propertyExpression = Expression.Property(paramExpression, "Id"); ConstantExpression constantExpression = Expression.Constant(value, typeof(List <int>)); Expression containsExpression = Expression.Call(constantExpression, typeof(List <int>).GetMethod("Contains", new Type[] { typeof(int) }), propertyExpression); if (queryExpression != null) { queryExpression = Expression.AndAlso(queryExpression, containsExpression); } else { queryExpression = containsExpression; } hasFilter = true; } var getResult = getMethod.Invoke(serviceInstance, new object[] { paramExpression, queryExpression, null, null }); var queryResult = getResult as IQueryable <IEntity>; // process entity specific filters switch (_entityName) { case "person": { queryResult = PersonFilters((IQueryable <Person>)queryResult, parms); break; } case "business": { queryResult = BusinessFilters((IQueryable <Person>)queryResult, parms); break; } } // if there was a dynamic expression add it now if (parms.Any(p => p.Key == "expression")) { queryResult = queryResult.Where(parms["expression"]); hasFilter = true; } var queryResultExpression = queryResult.Expression; // add sort expressions if (parms.Any(p => p.Key == "sort")) { string orderByMethod = "OrderBy"; foreach (var column in parms["sort"].Split(',').Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList()) { string propertyName; var direction = SortDirection.Ascending; if (column.EndsWith(" desc", StringComparison.OrdinalIgnoreCase)) { direction = SortDirection.Descending; propertyName = column.Left(column.Length - 5); } else { propertyName = column; } string methodName = direction == SortDirection.Descending ? orderByMethod + "Descending" : orderByMethod; if (entityType.GetProperty(propertyName) != null) { // sorting a entity property var memberExpression = Expression.Property(paramExpression, propertyName); LambdaExpression sortSelector = Expression.Lambda(memberExpression, paramExpression); queryResultExpression = Expression.Call(typeof(Queryable), methodName, new Type[] { queryResult.ElementType, sortSelector.ReturnType }, queryResultExpression, sortSelector); } else { // sorting on an attribute // get attribute id int?attributeId = null; foreach (var attribute in AttributeCache.GetByEntityType(entityTypeCache.Id)) { if (attribute.Key == propertyName) { attributeId = attribute.Id; break; } } if (attributeId.HasValue) { // get AttributeValue queryable and parameter if (dbContext is RockContext) { var attributeValues = new AttributeValueService(dbContext as RockContext).Queryable(); ParameterExpression attributeValueParameter = Expression.Parameter(typeof(AttributeValue), "v"); MemberExpression idExpression = Expression.Property(paramExpression, "Id"); var attributeExpression = Attribute.Helper.GetAttributeValueExpression(attributeValues, attributeValueParameter, idExpression, attributeId.Value); LambdaExpression sortSelector = Expression.Lambda(attributeExpression, paramExpression); queryResultExpression = Expression.Call(typeof(Queryable), methodName, new Type[] { queryResult.ElementType, sortSelector.ReturnType }, queryResultExpression, sortSelector); } else { throw new Exception(string.Format("The database context for type {0} does not support RockContext attribute value queries.", entityTypeCache.FriendlyName)); } } } orderByMethod = "ThenBy"; } } // check to ensure we had some form of filter (otherwise we'll return all results in the table) if (!hasFilter) { throw new Exception("Your Lava command must contain at least one valid filter. If you configured a filter it's possible that the property or attribute you provided does not exist."); } // reassemble the queryable with the sort expressions queryResult = queryResult.Provider.CreateQuery(queryResultExpression) as IQueryable <IEntity>; if (parms.GetValueOrNull("count").AsBoolean()) { int countResult = queryResult.Count(); context.Scopes.Last()["count"] = countResult; } else { // Run security check on each result if enabled and entity is not a person (we do not check security on people) if (parms["securityenabled"].AsBoolean() && _entityName != "person") { var items = queryResult.ToList(); var itemsSecured = new List <IEntity>(); Person person = GetCurrentPerson(context); foreach (IEntity item in items) { ISecured itemSecured = item as ISecured; if (itemSecured == null || itemSecured.IsAuthorized(Authorization.VIEW, person)) { itemsSecured.Add(item); /* * 8/13/2020 - JME * It might seem logical to break out of the loop if there is limit parameter provided once the * limit is reached. This though has two issues. * * FIRST * Depending how it was implemented it can have the effect of breaking when an offset is * provided. * {% contentchannelitem where:'ContentChannelId == 1' limit:'3' %} * {% for item in contentchannelitemItems %} * {{ item.Id }} - {{ item.Title }}<br> * {% endfor %} * {% endcontentchannelitem %} * Returns 3 items (correct) * * {% contentchannelitem where:'ContentChannelId == 1' limit:'3' offset:'1' %} * {% for item in contentchannelitemItems %} * {{ item.Id }} - {{ item.Title }}<br> * {% endfor %} * {% endcontentchannelitem %} * Returns only 2 items (incorrect) - because of the offset * * SECOND * If the limit is moved before the security check it's possible that the security checks * will remove items and will therefore not give you the amount of items that you asked for. * * Unfortunately this has to be an inefficent process to ensure pagination works. I will also * add a detailed note to the documentation to encourage people to disable security checks, * especially when used with pagination, in the Lava docs. */ } } queryResult = itemsSecured.AsQueryable(); } // offset if (parms.Any(p => p.Key == "offset")) { queryResult = queryResult.Skip(parms["offset"].AsInteger()); } // limit, default to 1000 if (parms.Any(p => p.Key == "limit")) { queryResult = queryResult.Take(parms["limit"].AsInteger()); } else { queryResult = queryResult.Take(1000); } var resultList = queryResult.ToList(); // if there is only one item to return set an alternative non-array based variable if (resultList.Count == 1) { context.Scopes.Last()[_entityName] = resultList.FirstOrDefault(); } context.Scopes.Last()[parms["iterator"]] = resultList; } } } } else { result.Write(string.Format("Could not find a model for {0}.", _entityName)); base.Render(context, result); } base.Render(context, result); }
/// <summary> /// Called during serialization to write an object of the specified type to the specified stream. /// </summary> /// <param name="type">The type of the object to write.</param> /// <param name="value">The object to write.</param> /// <param name="writeStream">The stream to write to.</param> /// <param name="effectiveEncoding">The encoding to use when writing.</param> public override void WriteToStream(Type type, object value, System.IO.Stream writeStream, Encoding effectiveEncoding) { bool isSelectAndExpand = false; List <object> selectAndExpandList = null; if (value is IQueryable <object> && typeof(IQueryable <Rock.Data.IEntity>).IsAssignableFrom(type)) { Type valueType = value.GetType(); // if this is an OData 'SelectAndExpand', we have convert stuff to Dictionary manually to get the stuff to serialize the way we want if (valueType.IsGenericType && valueType.GenericTypeArguments[0].Name == "SelectAllAndExpand`1") { isSelectAndExpand = true; var selectExpandQry = value as IQueryable <object>; selectAndExpandList = selectExpandQry.ToList(); } } var loadAttributesOptions = HttpContext.Current.Items[LoadAttributesOptions.ContextItemsKey] as LoadAttributesOptions; if (loadAttributesOptions == null) { // shouldn't happen, but just in case loadAttributesOptions = new LoadAttributesOptions(false, false, null, null); } // query should be filtered by now, so iterate thru items and load attributes before the response is serialized if (loadAttributesOptions.LoadAttributesEnabled) { IEnumerable <Attribute.IHasAttributes> items = null; if (value is IEnumerable <Attribute.IHasAttributes> ) { // if the REST call specified that Attributes should be loaded and we are returning a list of IHasAttributes.. // Also, do a ToList() to fetch the query into a list (instead re-querying multiple times) items = (value as IEnumerable <Attribute.IHasAttributes>).ToList(); // Assign the items list back to value value = items; } else if (value is Attribute.IHasAttributes) { // if the REST call specified that Attributes should be loaded and we are returning a single IHasAttributes.. items = new List <Attribute.IHasAttributes>(new Attribute.IHasAttributes[] { value as Attribute.IHasAttributes }); } else if (isSelectAndExpand && selectAndExpandList != null) { //// 'SelectAndExpand' buries the Entity in a private field called 'Instance', //// so use reflection to get that and load the attributes for each var itemsList = new List <Attribute.IHasAttributes>(); foreach (var selectExpandItem in selectAndExpandList) { var entityProperty = selectExpandItem.GetType().GetProperty("Instance"); var entity = entityProperty.GetValue(selectExpandItem) as Attribute.IHasAttributes; if (entity != null) { itemsList.Add(entity); } entityProperty.SetValue(selectExpandItem, entity); } items = itemsList; } List <AttributeCache> limitToAttributes = null; if (loadAttributesOptions.LimitToAttributeKeyList?.Any() == true && type.IsGenericType) { var entityTypeType = type.GenericTypeArguments[0]; if (entityTypeType != null) { var entityType = EntityTypeCache.Get(entityTypeType); var entityTypeAttributesList = AttributeCache.GetByEntityType(entityType.Id); limitToAttributes = entityTypeAttributesList?.Where(a => loadAttributesOptions.LimitToAttributeKeyList.Contains(a.Key, StringComparer.OrdinalIgnoreCase)).ToList(); } } if (items != null) { using (var rockContext = new Rock.Data.RockContext()) { foreach (var item in items) { Rock.Attribute.Helper.LoadAttributes(item, rockContext, limitToAttributes); } FilterAttributes(rockContext, items, loadAttributesOptions.Person); } } } // Special Code if an $expand clause is specified and a $select clause is NOT specified // This fixes a couple of issues: // 1) our special loadAttributes stuff didn't work if $expand is specified // 2) Only non-virtual,non-inherited fields were included (for example: Person.PrimaryAliasId, etc, wasn't getting included) if $expand was specified if (isSelectAndExpand) { using (var rockContext = new Rock.Data.RockContext()) { List <Dictionary <string, object> > valueAsDictionary = GetSelectAndExpandDictionaryObject(type, selectAndExpandList, rockContext, loadAttributesOptions); base.WriteToStream(type, valueAsDictionary, writeStream, effectiveEncoding); } return; } base.WriteToStream(type, value, writeStream, effectiveEncoding); }
/// <summary> /// Gets the entity fields for a specific Entity /// </summary> /// <param name="entityType">Type of the entity.</param> /// <param name="includeOnlyReportingFields">if set to <c>true</c> [include only reporting fields].</param> /// <param name="limitToFilterableFields">if set to <c>true</c> [limit to filterable fields].</param> /// <returns></returns> public static List <EntityField> GetEntityFields(Type entityType, bool includeOnlyReportingFields = true, bool limitToFilterableFields = true) { List <EntityField> entityFields = null; _workflowTypeNameLookup = null; if (HttpContext.Current != null) { entityFields = HttpContext.Current.Items[EntityHelper.GetCacheKey(entityType, includeOnlyReportingFields, limitToFilterableFields)] as List <EntityField>; if (entityFields != null) { return(entityFields); } } if (entityFields == null) { entityFields = new List <EntityField>(); } List <PropertyInfo> entityProperties = entityType.GetProperties().ToList(); // filter the properties to narrow down the ones that we want to include in EntityFields var filteredEntityProperties = entityProperties .Where((p) => { var includeForReportingAttribute = p.GetCustomAttribute <IncludeForReportingAttribute>() != null; // if the property has an IncludeForReportingAttribute, include it regardless if (includeForReportingAttribute) { return(true); } bool hideFromReporting = false; if (includeOnlyReportingFields) { hideFromReporting = p.GetCustomAttribute <HideFromReportingAttribute>() != null; } // if the property should be hidden from reporting, don't show it if (hideFromReporting) { return(false); } bool isMappedDatabaseField = Reflection.IsMappedDatabaseProperty(p); if (!isMappedDatabaseField) { return(false); } return(true); }).ToList(); // Go thru filteredEntityProperties and create the list of EntityFields that we can include foreach (var property in filteredEntityProperties) { EntityField entityField = new EntityField(property.Name, FieldKind.Property, property); entityField.IsPreviewable = property.GetCustomAttributes(typeof(PreviewableAttribute), true).Any(); var fieldTypeAttribute = property.GetCustomAttribute <Rock.Data.FieldTypeAttribute>(); // check if we can set it from the fieldTypeAttribute if ((fieldTypeAttribute != null) && SetEntityFieldFromFieldTypeAttribute(entityField, fieldTypeAttribute)) { // intentionally blank, entity field is already setup } // Enum Properties else if (property.PropertyType.IsEnum) { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.SINGLE_SELECT.AsGuid()); var list = new List <string>(); foreach (var value in Enum.GetValues(property.PropertyType)) { list.Add(string.Format("{0}^{1}", value, value.ToString().SplitCase())); } var listSource = string.Join(",", list); entityField.FieldConfig.Add("values", new Field.ConfigurationValue(listSource)); entityField.FieldConfig.Add("fieldtype", new Field.ConfigurationValue("rb")); } // Boolean properties else if (property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?)) { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.BOOLEAN.AsGuid()); } // Datetime properties else if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime? )) { var colAttr = property.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); if (colAttr != null && (( ColumnAttribute )colAttr).TypeName == "Date") { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.DATE.AsGuid()); } else { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.DATE_TIME.AsGuid()); } } // Decimal properties else if (property.PropertyType == typeof(decimal) || property.PropertyType == typeof(decimal? )) { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.DECIMAL.AsGuid()); } // Text Properties else if (property.PropertyType == typeof(string)) { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.TEXT.AsGuid()); } // Integer Properties (which may be a DefinedValue) else if (property.PropertyType == typeof(int) || property.PropertyType == typeof(int?)) { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.INTEGER.AsGuid()); var definedValueAttribute = property.GetCustomAttribute <Rock.Data.DefinedValueAttribute>(); if (definedValueAttribute != null) { // Defined Value Properties Guid?definedTypeGuid = definedValueAttribute.DefinedTypeGuid; if (definedTypeGuid.HasValue) { var definedType = DefinedTypeCache.Get(definedTypeGuid.Value); entityField.Title = definedType != null ? definedType.Name : property.Name.Replace("ValueId", string.Empty).SplitCase(); if (definedType != null) { entityField.FieldType = FieldTypeCache.Get(SystemGuid.FieldType.DEFINED_VALUE.AsGuid()); entityField.FieldConfig.Add("definedtype", new Field.ConfigurationValue(definedType.Id.ToString())); } } } } if (entityField != null && entityField.FieldType != null) { entityFields.Add(entityField); } } // Get Attributes var entityTypeCache = EntityTypeCache.Get(entityType, true); if (entityTypeCache != null) { int entityTypeId = entityTypeCache.Id; IEnumerable <AttributeCache> cacheAttributeList = AttributeCache.GetByEntityType(entityTypeCache.Id); if (entityType == typeof(Group) || entityType == typeof(GroupMember)) { // in the case of Group or GroupMember, show attributes that are entity global, but also ones that are qualified by GroupTypeId cacheAttributeList = cacheAttributeList .Where(a => a.EntityTypeQualifierColumn == null || a.EntityTypeQualifierColumn == string.Empty || a.EntityTypeQualifierColumn == "GroupTypeId"); } else if (entityType == typeof(ConnectionRequest)) { // in the case of Connection Requests, show attributes that are entity global, but also ones that are qualified by ConnectionOpportunityId or ConnectionTypeId cacheAttributeList = cacheAttributeList .Where(a => a.EntityTypeQualifierColumn == null || a.EntityTypeQualifierColumn == string.Empty || a.EntityTypeQualifierColumn == "ConnectionOpportunityId" || a.EntityTypeQualifierColumn == "ConnectionTypeId" ); } else if (entityType == typeof(Registration)) { // in the case of Registrations, show attributes that are entity global, but also ones that are qualified by RegistrationTemplateId cacheAttributeList = cacheAttributeList .Where(a => a.EntityTypeQualifierColumn == null || a.EntityTypeQualifierColumn == string.Empty || a.EntityTypeQualifierColumn == "RegistrationTemplateId" ); } else if (entityType == typeof(ContentChannelItem)) { // in the case of ContentChannelItem, show attributes that are entity global, but also ones that are qualified by ContentChannelTypeId or ContentChannelId cacheAttributeList = cacheAttributeList .Where(a => a.EntityTypeQualifierColumn == null || a.EntityTypeQualifierColumn == string.Empty || a.EntityTypeQualifierColumn == "ContentChannelTypeId" || a.EntityTypeQualifierColumn == "ContentChannelId" ); } else if (entityType == typeof(Rock.Model.Workflow)) { // in the case of Workflow, show attributes that are entity global, but also ones that are qualified by WorkflowTypeId (and have a valid WorkflowTypeId) var validWorkflowTypeIds = WorkflowTypeCache.All().Select(a => a.Id.ToString()).ToArray(); cacheAttributeList = cacheAttributeList .Where(a => a.EntityTypeQualifierColumn == null || a.EntityTypeQualifierColumn == string.Empty || (a.EntityTypeQualifierColumn == "WorkflowTypeId" && validWorkflowTypeIds.Contains(a.EntityTypeQualifierValue))).ToList(); } else if (entityType == typeof(Note)) { // in the case of notes, show attributes that are entity global, but also ones that are qualified by ConnectionOpportunityId cacheAttributeList = cacheAttributeList .Where(a => a.EntityTypeQualifierColumn == null || a.EntityTypeQualifierColumn == string.Empty || a.EntityTypeQualifierColumn == "NoteTypeId" ); } else { cacheAttributeList = cacheAttributeList.Where(a => string.IsNullOrEmpty(a.EntityTypeQualifierColumn) && string.IsNullOrEmpty(a.EntityTypeQualifierValue)).ToList(); } EntityHelper.AddEntityFieldsForAttributeList(entityFields, cacheAttributeList.ToList()); } // Order the fields by title, name int index = 0; var sortedFields = new List <EntityField>(); foreach (var entityField in entityFields.OrderBy(p => !string.IsNullOrEmpty(p.AttributeEntityTypeQualifierName)).ThenBy(p => p.Title).ThenBy(p => p.Name)) { entityField.Index = index; index++; sortedFields.Add(entityField); } if (HttpContext.Current != null) { HttpContext.Current.Items[EntityHelper.GetCacheKey(entityType, includeOnlyReportingFields, limitToFilterableFields)] = sortedFields; } return(sortedFields); }