Example #1
0
		/// <summary>
		/// Constructs an section.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="properties">The descriptor of this section.</param>
		/// <param name="expression">The expressio of this section.</param>
		public Section(IMansionContext context, IPropertyBag properties, IExpressionScript expression)
		{
			// validate arguments
			if (context == null)
				throw new ArgumentNullException("context");
			if (properties == null)
				throw new ArgumentNullException("properties");
			if (expression == null)
				throw new ArgumentNullException("expression");

			// set values
			Properties = properties;
			Expression = expression;
			Id = Guid.NewGuid().ToString();
			Name = Properties.Get<string>(context, "name");
			ShouldBeRenderedOnce = !Properties.Get(context, "repeatable", true);
			TargetField = Properties.Get(context, "field", Name);

			// check if there is a requires property
			string requiresExpressionString;
			if (Properties.TryGet(context, "requires", out requiresExpressionString))
			{
				// assemble the expression
				var expressionService = context.Nucleus.ResolveSingle<IExpressionScriptService>();
				var requiresExpression = expressionService.Parse(context, new LiteralResource(requiresExpressionString));

				// execute the expression
				areRequirementsSatisfied = requiresExpression.Execute<bool>;
			}
			else
				areRequirementsSatisfied = mansionContext => true;
		}
		/// <summary>
		/// Initializes the given <paramref name="column"/>.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="column">The <see cref="PropertyColumn"/> which to initialze.</param>
		/// <param name="properties">The properties.</param>
		/// <exception cref="ArgumentNullException">Thrown if one of the parameters is null.</exception>
		protected static void Initialize(IMansionContext context, PropertyColumn column, IPropertyBag properties)
		{
			// validate arguments
			if (context == null)
				throw new ArgumentNullException("context");
			if (column == null)
				throw new ArgumentNullException("column");
			if (properties == null)
				throw new ArgumentNullException("properties");

			// get the allow null flag
			column.AllowNullValue = properties.Get(context, "allowNullValue", false);

			// check if there is an expression
			string expressionString;
			if (properties.TryGet(context, "expression", out expressionString))
			{
				var expressionService = context.Nucleus.ResolveSingle<IExpressionScriptService>();
				column.HasExpression = true;
				column.Expression = expressionService.Parse(context, new LiteralResource(expressionString));
			}

			// get the default value
			string defaultValue;
			if (properties.TryGet(context, "defaultValue", out defaultValue))
			{
				var expressionService = context.Nucleus.ResolveSingle<IExpressionScriptService>();
				var defaultValueExpression = expressionService.Parse(context, new LiteralResource(defaultValue));
				column.DefaultValue = defaultValueExpression.Execute<object>(context);
				column.HasDefaultValue = true;
			}
		}
		/// <summary>
		/// Populates the fullText property.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="modifiedProperties">The modified <see cref="IPropertyBag"/>.</param>
		/// <param name="originalProperties">The original <see cref="IPropertyBag"/>.</param>
		/// <exception cref="ArgumentNullException">Thrown if one of the parameters is null.</exception>
		public void Populate(IMansionContext context, IPropertyBag modifiedProperties, IPropertyBag originalProperties)
		{
			//validate arguments
			if (context == null)
				throw new ArgumentNullException("context");
			if (modifiedProperties == null)
				throw new ArgumentNullException("modifiedProperties");
			if (originalProperties == null)
				throw new ArgumentNullException("originalProperties");

			// get all the properties over which to loop
			var properties = Properties.Get<string>(context, "properties").Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(property => property.Trim());

			// assemble the content
			var buffer = new StringBuilder();
			foreach (var property in properties)
			{
				//  get the content for the given property
				String content;
				if (!modifiedProperties.TryGet(context, property, out content))
				{
					if (!originalProperties.TryGet(context, property, out content))
						continue;
				}

				// strip the HTML
				content = content.StripHtml();

				// add it to the buffer
				buffer.AppendLine(content);
			}

			// if there is full-text content, add it to the full-text property, otherwise set it to null
			modifiedProperties.Set("fullText", buffer.Length > 0 ? buffer.ToString() : null);
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="queryBuilder"></param>
		/// <param name="record"> </param>
		/// <param name="modifiedProperties"></param>
		protected override void DoToUpdateStatement(IMansionContext context, ModificationQueryBuilder queryBuilder, Record record, IPropertyBag modifiedProperties)
		{
			// check if the property is not modified
			int newOrder;
			if (!modifiedProperties.TryGet(context, PropertyName, out newOrder))
				return;

			// check if the record contains a pointer
			NodePointer pointer;
			if (!record.TryGet(context, "pointer", out pointer))
				throw new InvalidOperationException("Could not update this record because it did not contain a pointer");

			// don't update order for root  nodes
			if (pointer.Depth == 1)
				return;

			// do not allow values smaller than 1
			if (newOrder < 1)
				throw new InvalidOperationException("Can not set orders smaller than 1");

			// assemble parameter
			var newOrderParameterName = queryBuilder.AddParameter("newOrder", newOrder, DbType.Int32);
			var oldOrderParameterName = queryBuilder.AddParameter("oldOrder", record.Get<int>(context, PropertyName), DbType.Int32);
			var parentIdParameterName = queryBuilder.AddParameter("parentId", pointer.Parent.Id, DbType.Int32);

			// update the orders before updating the order of the current node
			queryBuilder.PrependQuery(string.Format("UPDATE [Nodes] SET [order] = [order] + 1 WHERE (parentId = {0}) AND ([order] < {1} AND [order] >= {2})", parentIdParameterName, oldOrderParameterName, newOrderParameterName));
			queryBuilder.PrependQuery(string.Format("UPDATE [Nodes] SET [order] = [order] - 1 WHERE (parentId = {0}) AND ([order] > {1} AND [order] <= {2})", parentIdParameterName, oldOrderParameterName, newOrderParameterName));

			// update the column
			queryBuilder.AddColumnValue(PropertyName, newOrderParameterName);
		}
        /// <summary>
        /// This method is called just before a node is updated by the repository.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="node">The node which will be modified.</param>
        /// <param name="modifiedProperties">The updated properties of the node.</param>
        protected override void DoBeforeUpdate(IMansionContext context, Node node, IPropertyBag modifiedProperties)
        {
            // if the name has not changed we are not interested
            string newName;
            if (!modifiedProperties.TryGet(context, "name", out newName))
                return;

            // if the name has not changed after normalization we are not interested
            newName = TagUtilities.Normalize(newName);
            if (node.Pointer.Name.Equals(newName))
            {
                modifiedProperties.Remove("name");
                return;
            }
            modifiedProperties.Set("name", newName);

            // if the tag is renamed to another already existing tag, move all content to that existing tag and delete this one
            Node existingTag;
            var tagIndexNode = TagUtilities.RetrieveTagIndexNode(context);
            if (TagUtilities.TryRetrieveTagNode(context, tagIndexNode, newName, out existingTag))
            {
                // TODO: move all content to the existing tag

                // TODO: delete this tag
            }
        }
Example #6
0
		/// <summary>
		/// Creates a filled nodeset.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="properties"></param>
		/// <param name="records"></param>
		public RecordSet(IMansionContext context, IPropertyBag properties, IEnumerable<Record> records) : base(properties)
		{
			// validate arguments
			if (records == null)
				throw new ArgumentNullException("records");
			if (properties == null)
				throw new ArgumentNullException("properties");

			// set values
			foreach (var node in records)
				RowCollection.Add(node);
			Set("count", RowCollection.Count);

			// check for paging
			var totalRowCount = properties.Get(context, "totalCount", -1);
			var pageNumber = properties.Get(context, "pageNumber", -1);
			var rowsPerPage = properties.Get(context, "pageSize", -1);
			if (totalRowCount != -1 && pageNumber != -1 && rowsPerPage != -1)
				SetPaging(totalRowCount, pageNumber, rowsPerPage);

			// check for sort
			string sortString;
			if (properties.TryGet(context, "sort", out sortString))
			{
				foreach (var sort in Collections.Sort.Parse(sortString))
					AddSort(sort);
			}
		}
        /// <summary>
        /// Tries to revive the user based on the revival properties.
        /// </summary>
        /// <param name="context">The security context.</param>
        /// <param name="revivalProperties">The revival properties.</param>
        /// <returns>Returns the revived <see cref="UserState"/> or null.</returns>
        public override UserState ReviveUser(IMansionContext context, IPropertyBag revivalProperties)
        {
            // validate arguments
            if (context == null)
                throw new ArgumentNullException("context");
            if (revivalProperties == null)
                throw new ArgumentNullException("revivalProperties");

            // get the id of the from the revival properties
            Guid id;
            if (!revivalProperties.TryGet(context, "id", out id))
                return null;

            // retrieve the user by guid
            var userNode = context.Repository.RetrieveSingleNode(context, new PropertyBag {
                {"baseType", "User"},
                {"guid", id},
                {"status", "any"},
                {"bypassAuthorization", true},
                {"cache", false},
                {StorageOnlyQueryComponent.PropertyKey, true}
            });
            if (userNode == null)
                return null;

            // create and return the user state
            return CreateUserState(context, userNode);
        }
        /// <summary>
        /// This method is called just before a node is updated by the repository.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="record"> </param>
        /// <param name="properties">The updated properties of the node.</param>
        protected override void DoBeforeUpdate(IMansionContext context, Record record, IPropertyBag properties)
        {
            // get the variables
            string currentTheme;
            var hasCurrentTheme = record.TryGet(context, "theme", out currentTheme) && !string.IsNullOrEmpty(currentTheme);
            string newTheme;
            var hasNewTheme = properties.TryGet(context, "theme", out newTheme) && !string.IsNullOrEmpty(newTheme);

            // do nothing when the page does not have a theme yet
            if (!hasCurrentTheme)
                return;

            // retrieve the schema of the current theme
            var currentThemeSchema = ColumnSchema.GetSchema(context, currentTheme);

            // retrieve the blocks of this page
            var repository = context.Repository;
            var blockNodeset = repository.RetrieveNodeset(context, new PropertyBag
                                                                   {
                                                                   	{"baseType", "Block"},
                                                                   	{"parentSource", record}
                                                                   });

            // check if a new theme is selected
            if (hasNewTheme)
            {
                // retrieve the schema of the new theme
                var newThemeSchema = ColumnSchema.GetSchema(context, newTheme);

                // loop through the blocks to find obsolete ones
                foreach (var blockNode in blockNodeset.Nodes)
                {
                    // get the column of this block
                    var column = blockNode.Get<string>(context, "column");

                    // check if this block lived in the old theme
                    if (!currentThemeSchema.ContainsColumn(column))
                        continue;

                    // check if the column exists in the new theme as well
                    if (newThemeSchema.ContainsColumn(column))
                        continue;

                    // block is obsolete delete it
                    repository.DeleteNode(context, blockNode);
                }
            }
            else
            {
                // theme is removed, delete all the theme blocks
                foreach (var blockNode in blockNodeset.Nodes.Where(candidate => currentThemeSchema.ContainsColumn(candidate.Get<string>(context, "column"))))
                    repository.DeleteNode(context, blockNode);
            }

            base.DoBeforeUpdate(context, record, properties);
        }
        /// <summary>
        /// Gets the row.
        /// </summary>
        /// <param name="context">The request context.</param>
        /// <param name="attributes">The attributes of this tag.</param>
        /// <returns>Returns the result.</returns>
        protected override IPropertyBag Get(IMansionContext context, IPropertyBag attributes)
        {
            // get the url
            Url url;
            if (!attributes.TryGet(context, "url", out url))
                url = context.Cast<IMansionWebContext>().Request.RequestUrl;

            // return the property bag representation
            return url.ToPropertyBag();
        }
        /// <summary>
        /// This method is called just before a node is created by the repository.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="properties">The new properties of the node.</param>
        protected override void DoBeforeCreate(IMansionContext context, IPropertyBag properties)
        {
            // check if the layout is set
            string layout;
            if (properties.TryGet(context, "layout", out layout))
                return;

            // set the layout
            properties.TrySet("layout", layout);
        }
        /// <summary>
        /// This method is called just before a node is created by the repository.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="parent">The parent node to which the new child will be added.</param>
        /// <param name="newProperties">The new properties of the node.</param>
        protected override void DoBeforeCreate(IMansionContext context, Node parent, IPropertyBag newProperties)
        {
            // check if the layout is set
            string layout;
            if (newProperties.TryGet(context, "layout", out layout))
                return;
            if (!parent.TryGet(context, "layout", out layout))
                layout = "OneColumnLayout";

            // set the layout
            newProperties.TrySet("layout", layout);
        }
Example #12
0
		/// <summary>
		/// Gets a value from the script stack.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="propertyBag">The <see cref="IPropertyBag"/> from which to get the property.</param>
		/// <param name="propertyName">The name of the property from which to get the value.</param>
		/// <param name="defaultValue">The default value which to return in case the <paramref name="propertyBag"/> does not contain <paramref name="propertyName"/>.</param>
		/// <returns>Returns the value of the specified property.</returns>
		public object Evaluate(IMansionContext context, IPropertyBag propertyBag, string propertyName, object defaultValue)
		{
			// validate arguments
			if (context == null)
				throw new ArgumentNullException("context");
			if (propertyBag == null)
				throw new ArgumentNullException("propertyBag");
			if (string.IsNullOrEmpty(propertyName))
				throw new ArgumentNullException("propertyName");

			// get the property
			object obj;
			return !propertyBag.TryGet(context, propertyName, out obj) ? defaultValue : obj;
		}
Example #13
0
        public bool TryGet(string name, out Jint.Native.Descriptor descriptor)
        {
            if (lastAccessed != null && lastAccessed.Name == name)
            {
                descriptor = lastAccessed;
                return(true);
            }
            bool result = bag.TryGet(name, out descriptor);

            if (result)
            {
                lastAccessed = descriptor;
            }
            return(result);
        }
Example #14
0
		/// <summary>
		/// Gets a value from the given propertybag.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="propertyBag">The <see cref="IPropertyBag"/> from which to get the property.</param>
		/// <param name="propertyName">The name of the property from which to get the value.</param>
		/// <returns>Returns the value of the specified property.</returns>
		public object Evaluate(IMansionContext context, IPropertyBag propertyBag, string propertyName)
		{
			// validate arguments
			if (context == null)
				throw new ArgumentNullException("context");
			if (propertyBag == null)
				throw new ArgumentNullException("propertyBag");
			if (string.IsNullOrEmpty(propertyName))
				throw new ArgumentNullException("propertyName");

			// get the property
			object obj;
			if (!propertyBag.TryGet(context, propertyName, out obj))
				throw new InvalidOperationException(string.Format("Does not contain property '{0}'", propertyName));
			return obj;
		}
        /// <summary>
        /// Builds and executes the query.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="arguments">The arguments from which to build the query.</param>
        /// <param name="repository">The <see cref="IRepository"/>.</param>
        /// <param name="parser">The <see cref="IQueryParser"/>.</param>
        /// <returns>Returns the result.</returns>
        protected override Record Retrieve(IMansionContext context, IPropertyBag arguments, IRepository repository, IQueryParser parser)
        {
            // get the url
            Url url;
            if (!arguments.TryGet(context, "url", out url))
                url = context.Cast<IMansionWebContext>().Request.RequestUrl;

            // parse the query
            var query = parser.Parse(context, new PropertyBag
                                              {
                                              	{"baseType", "Site"},
                                              	{"hostHeaders", url.HostName}
                                              });

            // execute the query
            return repository.RetrieveSingleNode(context, query);
        }
        /// <summary>
        /// Builds and executes the query.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="arguments">The arguments from which to build the query.</param>
        /// <param name="repository">The <see cref="IRepository"/>.</param>
        /// <param name="parser">The <see cref="IQueryParser"/>.</param>
        /// <returns>Returns the result.</returns>
        protected override Record Retrieve(IMansionContext context, IPropertyBag arguments, IRepository repository, IQueryParser parser)
        {
            // get the url
            Url url;
            if (!arguments.TryGet(context, "url", out url))
                url = context.Cast<IMansionWebContext>().Request.RequestUrl;

            // parse the URL for identifiers
            IPropertyBag queryAttributes;
            if (!nodeUrlService.TryExtractQueryParameters(context.Cast<IMansionWebContext>(), url, out queryAttributes))
                return null;

            // parse the query
            var query = parser.Parse(context, queryAttributes);

            // execute the query
            return repository.RetrieveSingleNode(context, query);
        }
		/// <summary>
		/// This method is called just before a node is updated by the repository.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="record"> </param>
		/// <param name="properties">The updated properties of the node.</param>
		protected override void DoBeforeUpdate(IMansionContext context, Record record, IPropertyBag properties)
		{
			base.DoBeforeUpdate(context, record, properties);

			// check if the identifier was updated
			string identifier;
			if (!properties.TryGet(context, "identifier", out identifier))
				return;
			identifier = identifier.Trim().ToLower();
			properties.Set("identifier", identifier);

			// only update the name with the identifier if the previous name was also derived from the identifier
			if (record.Get(context, "name", string.Empty).Equals(record.Get(context, "identifier", string.Empty)))
			{
				var name = properties.Get(context, "name", identifier).Trim().ToLower();
				properties.Set("name", name);
			}
		}
        /// <summary>
        /// This method is called just before a node is updated by the repository.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="node">The node which will be modified.</param>
        /// <param name="modifiedProperties">The updated properties of the node.</param>
        protected override void DoBeforeUpdate(IMansionContext context, Node node, IPropertyBag modifiedProperties)
        {
            // check if the layout was not modified
            string newLayoutName;
            if (!modifiedProperties.TryGet(context, "layout", out newLayoutName))
                return;

            // check if there was no old layout
            string oldLayoutName;
            if (!node.TryGet(context, "layout", out oldLayoutName))
                return;

            // get the schemas
            var newColumnSchema = ColumnSchema.GetSchema(context, newLayoutName);
            var oldColumnSchema = ColumnSchema.GetSchema(context, oldLayoutName);

            // retrieve the blocks of this page
            var repository = context.Repository;
            var blockNodeset = repository.Retrieve(context, repository.ParseQuery(context, new PropertyBag
                                                                                           {
                                                                                           	{"baseType", "Block"},
                                                                                           	{"parentSource", node}
                                                                                           }));

            // loop through all the nodes
            foreach (var blockNode in blockNodeset.Nodes)
            {
                // check if this block was not in a column of the old schema so it wont have to move to the new schema
                var columnName = blockNode.Get<string>(context, "column");
                if (!oldColumnSchema.ContainsColumn(columnName))
                    continue;

                // check if the column is in the new schema as well so it wont have to move
                if (newColumnSchema.ContainsColumn(columnName))
                    continue;

                // move the block to the default column
                repository.Update(context, blockNode, new PropertyBag
                                                      {
                                                      	{"column", newColumnSchema.DefaultColumn}
                                                      });
            }
        }
		/// <summary>
		/// This method is called just before a node is updated by the repository.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="record"> </param>
		/// <param name="properties">The updated properties of the node.</param>
		protected override void DoBeforeUpdate(IMansionContext context, Record record, IPropertyBag properties)
		{
			base.DoBeforeUpdate(context, record, properties);

			// check if the preferred term has changed
			Guid newPreferredTermGuid;
			if (!properties.TryGet(context, "preferredTermGuid", out newPreferredTermGuid))
				return;

			// delete the old link if it exists
			var recordGuidString = record.Get<string>(context, "guid");
			Guid currentPreferredTermGuid;
			if (record.TryGet(context, "preferredTermGuid", out currentPreferredTermGuid) && currentPreferredTermGuid != Guid.Empty)
			{
				// retrieve the old record
				var currentPreferredTermNode = context.Repository.RetrieveSingleNode(context, currentPreferredTermGuid);

				// remove the link, if the target was found
				if (currentPreferredTermNode != null)
				{
					// store the guid in the synonymGuids field
					context.Repository.UpdateNode(context, currentPreferredTermNode, new PropertyBag {
						{"synonymGuids", currentPreferredTermNode.Get(context, "synonymGuids", string.Empty).RemoveNeedle(recordGuidString)}
					});
				}
			}

			// check if there is no new synonym
			if (newPreferredTermGuid == Guid.Empty)
				return;

			// retrieve the preferredTermNode
			var newPreferredTermNode = context.Repository.RetrieveSingleNode(context, newPreferredTermGuid);
			if (newPreferredTermNode == null)
				return;

			// store the guid in the synonymGuids field
			context.Repository.UpdateNode(context, newPreferredTermNode, new PropertyBag {
				{"synonymGuids", newPreferredTermNode.Get(context, "synonymGuids", string.Empty).AppendNeedle(recordGuidString)}
			});
		}
        /// <summary>
        /// Renders the specified <paramref name="blockProperties"/> to the output pipe.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="blockProperties">The <see cref="IPropertyBag"/> of the block which to render.</param>
        /// <param name="targetField">The name of the field to which to render.</param>
        protected override void DoRender(IMansionContext context, IPropertyBag blockProperties, string targetField)
        {
            // first retrieve the block node to display
            Guid displayedBlockGuid;
            if (!blockProperties.TryGet(context, "blockGuid", out displayedBlockGuid))
                throw new InvalidOperationException("Block guid not found for shared block display");
            var displayedBlockNode = context.Repository.RetrieveSingleNode(context, new PropertyBag
                                                                                    {
                                                                                        {"guid", displayedBlockGuid}
                                                                                    });
            if (displayedBlockNode == null)
                throw new InvalidOperationException(string.Format("Could not find block with guid '{0}'", displayedBlockGuid));

            // second, merge the two block properties together
            var mergedBlockProperties = new PropertyBag();
            mergedBlockProperties.Merge(blockProperties);
            mergedBlockProperties.Merge(displayedBlockNode);
            mergedBlockProperties.Set("id", blockProperties.Get<int>(context, "id"));

            // finally re-render the combined block using the portal service
            PortalService.RenderBlock(context, mergedBlockProperties, targetField);
        }
        /// <summary>
        /// Retrieves the properties as a dictionary.
        /// </summary>
        /// <param name="propertyBag">The property bag.</param>
        /// <returns>A dictionary containing all of the properties in the bag.</returns>
        /// <remarks>
        /// <para>
        /// The types of the individual entries will either be .NET primitive types (e.g. int, string, etc) or further
        /// <see cref="IPropertyBag"/>s. Any arrays in the source <see cref="IPropertyBag"/> will be added to the
        /// dictionary as arrays of <see cref="object"/>, with array elements being further .NET primitive types of
        /// <see cref="IPropertyBag"/>s.
        /// </para>
        /// </remarks>
        public static IReadOnlyDictionary <string, object> AsDictionary(this IPropertyBag propertyBag)
        {
            if (propertyBag is not IEnumerable <(string Key, PropertyBagEntryType Type)> items)
            {
                throw new ArgumentException($"Only property bags that implement {nameof(IEnumerable<(string Key, PropertyBagEntryType Type)>)} can be converted to dictionaries");
            }

            var dictionary = new Dictionary <string, object>();

            // Now for each item in the dictionary we need to check that it's either
            // 1. A scalar type
            // 2. An IPropertyBag
            // 3. An array of 1. or 2.
            // The initial call to properties.ToObject<T>() will have given us a dictionary containing either scalars
            // or JTokens. We need to look for the JTokens and process them appropriately.
            foreach ((string key, PropertyBagEntryType type) in items)
            {
                T RequireItem <T>(string key) => propertyBag.TryGet <T>(key, out T result)
                    ? result
                    : throw new InvalidOperationException($"Property bag advertised entry {key} of type {typeof(T).Name} during enumeration, but TryGet for that entry failed");

                object value = type switch
                {
                    PropertyBagEntryType.Null => throw new ArgumentException("Cannot return a dictionary with non-null value type when the bag contains null values"),
                          PropertyBagEntryType.String => RequireItem <string>(key),
                          PropertyBagEntryType.Boolean => RequireItem <bool>(key),
                          PropertyBagEntryType.Integer => RequireItem <long>(key),
                          PropertyBagEntryType.Decimal => RequireItem <double>(key),
                          PropertyBagEntryType.Array => RequireItem <object[]>(key),
                          PropertyBagEntryType.Object => RequireItem <IPropertyBag>(key),

                          _ => throw new InvalidOperationException($"Bag reported entry of unrecognized type: {type}"),
                };
                dictionary.Add(key, value);
            }

            return(dictionary);
        }
        public virtual bool HasOwnProperty(string key)
        {
            Descriptor desc;

            return(properties.TryGet(key, out desc));
        }
		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="queryBuilder"></param>
		/// <param name="record"> </param>
		/// <param name="modifiedProperties"></param>
		protected override void DoToUpdateStatement(IMansionContext context, ModificationQueryBuilder queryBuilder, Record record, IPropertyBag modifiedProperties)
		{
			// allow update of relational column on special cases, most likely used when fixing the repository integrity
			if (modifiedProperties.Get(context, "_allowRelationPropertiesUpdate", false))
			{
				string name;
				if (modifiedProperties.TryGet(context, "name", out name))
					queryBuilder.AddColumnValue("name", name, DbType.String);
				string type;
				if (modifiedProperties.TryGet(context, "type", out type))
					queryBuilder.AddColumnValue("type", type, DbType.String);
				int depth;
				if (modifiedProperties.TryGet(context, "depth", out depth))
					queryBuilder.AddColumnValue("depth", depth, DbType.Int32);
				int parentId;
				if (modifiedProperties.TryGet(context, "parentId", out parentId))
					queryBuilder.AddColumnValue("parentId", parentId, DbType.Int32);
				string parentPointer;
				if (modifiedProperties.TryGet(context, "parentPointer", out parentPointer))
					queryBuilder.AddColumnValue("parentPointer", parentPointer, DbType.String);
				string parentPath;
				if (modifiedProperties.TryGet(context, "parentPath", out parentPath))
					queryBuilder.AddColumnValue("parentPath", parentPath, DbType.String);
				string parentStructure;
				if (modifiedProperties.TryGet(context, "parentStructure", out parentStructure))
					queryBuilder.AddColumnValue("parentStructure", parentStructure, DbType.String);
				return;
			}

			// make sure the relational intgrety is not comprimised
			if (modifiedProperties.Names.Intersect(ReservedPropertyName, StringComparer.OrdinalIgnoreCase).Any())
				throw new InvalidOperationException("The relational properties can not be changed");

			// get the pointer
			NodePointer pointer;
			if (!record.TryGet(context, "pointer", out pointer))
				throw new InvalidOperationException("Could not update this record because it did not contain a pointer");

			//  add the id an pointer parameters
			var idParameterName = queryBuilder.AddParameter("id", pointer.Id, DbType.Int32);
			var pointerParameterName = queryBuilder.AddParameter("pointer", pointer.PointerString + "-%", DbType.String);

			// check if the name changed
			string newName;
			if (modifiedProperties.TryGetAndRemove(context, "name", out newName))
			{
				newName = newName.Trim();
				if (string.IsNullOrEmpty(newName))
					throw new InvalidOperationException("Can not update column name with empty string");
				if (newName.Contains(NodePointer.PathSeparator))
					throw new InvalidOperationException(string.Format("Name '{0}' contains invalid characters", newName));
				if (!pointer.Name.Equals(newName))
				{
					// add the name column modification
					queryBuilder.AddColumnValue("name", newName, DbType.String);

					// update the paths
					var oldPathLengthParameterName = queryBuilder.AddParameter("oldPathLength", pointer.PathString.Length + 1, DbType.String);
					var newPathParameterName = queryBuilder.AddParameter("newPath", NodePointer.Rename(pointer, newName).PathString + NodePointer.PathSeparator, DbType.String);
					queryBuilder.AppendQuery(string.Format(@" UPDATE [Nodes] SET [parentPath] = {0} + RIGHT( [parentPath], LEN( [parentPath] ) - {1} ) WHERE ( [parentId] = {2} OR [parentPointer] LIKE {3} )", newPathParameterName, oldPathLengthParameterName, idParameterName, pointerParameterName));
				}
			}

			// check if the type changed
			string newType;
			if (modifiedProperties.TryGetAndRemove(context, "type", out newType))
			{
				newType = newType.Trim();
				if (string.IsNullOrEmpty(newType))
					throw new InvalidOperationException("Can not update column type with empty string");
				if (newType.Contains(NodePointer.StructureSeparator))
					throw new InvalidOperationException(string.Format("Type '{0}' contains invalid characters", newType));
				if (!string.IsNullOrEmpty(newType) && !pointer.Type.Equals(newType, StringComparison.OrdinalIgnoreCase))
				{
					// add the name column modification
					queryBuilder.AddColumnValue("type", newType, DbType.String);

					// update the structures
					var newStructureParameterName = queryBuilder.AddParameter("newStructure", NodePointer.ChangeType(pointer, newType).StructureString + NodePointer.StructureSeparator, DbType.String);
					var oldStructureLengthParameterName = queryBuilder.AddParameter("oldStructureLength", pointer.StructureString.Length + 1, DbType.Int32);
					queryBuilder.AppendQuery(string.Format("UPDATE [Nodes] SET [parentStructure] = {0} + RIGHT( [parentStructure], LEN( [parentStructure] ) - {1} ) WHERE ( [parentId] = {2} OR [parentPointer] LIKE {3} )", newStructureParameterName, oldStructureLengthParameterName, idParameterName, pointerParameterName));
				}
			}
		}
        /// <summary>
        /// Manages the tags.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="properties">The new properties.</param>
        /// <returns>A CSV of all the tag names.</returns>
        public static string ToNames(IMansionContext context, IPropertyBag properties)
        {
            // validate arguments
            if (context == null)
                throw new ArgumentNullException("context");
            if (properties == null)
                throw new ArgumentNullException("properties");

            var tagNames = string.Empty;

            // retrieve the guids
            string tagGuids;
            if (properties.TryGet(context, "tagGuids", out tagGuids) && !string.IsNullOrEmpty(tagGuids))
            {
                // retrieve the tag index node
                var tagIndexNode = RetrieveTagIndexNode(context);

                // retrieve the tags by their guid
                var tagNodes = context.Repository.RetrieveNodeset(context, new PropertyBag
                                                                    {
                                                                        {"parentSource", tagIndexNode},
                                                                        {"guid", tagGuids}
                                                                    });

                // assemble the tag names
                tagNames = string.Join(", ", tagNodes.Nodes.Select(x => x.Pointer.Name));
            }

            return tagNames;
        }
		/// <summary>
		/// Writes <paramref name="values"/> to the output.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="values">The <see cref="IPropertyBag"/> containing the values which to write.</param>
		public void Write(IMansionContext context, IPropertyBag values)
		{
			// validate arguments
			if (context == null)
				throw new ArgumentNullException("context");
			if (values == null)
				throw new ArgumentNullException("values");
			CheckDisposed();

			// write out the values
			for (var index = 0; index < format.ColumnProperties.Length; index++)
			{
				// write out the current value when there is one
				string value;
				if (values.TryGet(context, format.ColumnProperties[index], out value))
				{
					outputPipe.Writer.Write(format.TextQualifier);
					outputPipe.Writer.Write(value);
					outputPipe.Writer.Write(format.TextQualifier);
				}

				// write out the column or row delimitor
				outputPipe.Writer.Write(index == (format.ColumnProperties.Length - 1) ? format.RowDelimitor : format.ColumnDelimitor);
			}
		}
		/// <summary>
		/// Requests this voter to cast a vote.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="subject">The subject.</param>
		/// <returns>Returns the result of the vote.</returns>
		protected override VoteResult DoVote(IMansionContext context, IPropertyBag subject)
		{
			// check if a path is specified
			string type;
			return subject.TryGet(context, "type", out type) ? VoteResult.MediumInterest : VoteResult.Refrain;
		}
		/// <summary>
		/// Gets the row.
		/// </summary>
		/// <param name="context">The request context.</param>
		/// <param name="attributes">The attributes of this tag.</param>
		/// <returns>Returns the result.</returns>
		protected override IPropertyBag Get(IMansionContext context, IPropertyBag attributes)
		{
			// get the web request context
			var webRequest = context.Cast<IMansionWebContext>();

			// get the url
			Url url;
			if (!attributes.TryGet(context, "url", out url))
				url = webRequest.Request.RequestUrl;

			// get the area prefix
			var routeProperties = new PropertyBag();

			// if the request was forwarded from a 404, do not parse the route
			string forwardedFrom404;
			if (webRequest.Request.Headers.TryGetValue(Constants.ForwardedFrom404HeaderKey, out forwardedFrom404))
			{
				routeProperties.Set("controller", "404");
				routeProperties.Set("action", "NotFound");
				return routeProperties;
			}

			// get the route index
			var routeUrlIndex = Array.FindIndex(url.PathSegments, x => x.Equals(Constants.RouteUrlPrefix, StringComparison.OrdinalIgnoreCase));

			// check if this is no route url
			if (routeUrlIndex == -1)
			{
				// this is no route URL, default controller and action
				routeProperties.Set("area", GetAttribute<string>(context, "defaultArea"));
				routeProperties.Set("controller", GetAttribute<string>(context, "defaultController"));
				routeProperties.Set("action", GetAttribute<string>(context, "defaultAction"));
				return routeProperties;
			}

			// determine the number of route url parts
			var parameterRouteUrlIndex = Array.FindIndex(url.PathSegments, x => x.Equals(Constants.RouteParameterPrefix, StringComparison.OrdinalIgnoreCase));
			var routeUrlPartLength = (parameterRouteUrlIndex != -1 ? parameterRouteUrlIndex : url.PathSegments.Length) - routeUrlIndex;

			// parse the route parts
			if (routeUrlIndex != -1 && routeUrlPartLength == 4)
			{
				// route url with area
				routeProperties.Set("area", GetSegment(url, routeUrlIndex + 1));
				routeProperties.Set("controller", GetSegment(url, routeUrlIndex + 2));
				routeProperties.Set("action", GetSegment(url, routeUrlIndex + 3));
			}
			else if (routeUrlIndex != -1 && routeUrlPartLength == 3)
			{
				// route url without area
				routeProperties.Set("controller", GetSegment(url, routeUrlIndex + 1));
				routeProperties.Set("action", GetSegment(url, routeUrlIndex + 2));
			}
			else
			{
				// unknown route type
				throw new InvalidOperationException(string.Format("'{0}' is an invalid route url", url));
			}

			// parse parameters if any
			if (parameterRouteUrlIndex > -1)
			{
				// set all the parameters
				for (var paremeterIndex = parameterRouteUrlIndex + 1; paremeterIndex < url.PathSegments.Length; paremeterIndex++)
					routeProperties.Set("routeParameter" + ((paremeterIndex - parameterRouteUrlIndex) - 1), GetSegment(url, paremeterIndex));
			}

			// return the route
			return routeProperties;
		}
		/// <summary>
		/// Generates the update statement for this table.
		/// </summary>
		/// <param name="context"></param>
		/// <param name="queryBuilder"></param>
		/// <param name="record"> </param>
		/// <param name="modifiedProperties"></param>
		protected override void DoToUpdateStatement(IMansionContext context, ModificationQueryBuilder queryBuilder, Record record, IPropertyBag modifiedProperties)
		{
			// create identity parameter
			var idParameterName = queryBuilder.AddParameter("id", record.Id, DbType.Int32);

			// loop through all the properties
			foreach (var propertyName in Columns.Select(column => column.PropertyName))
			{
				// check if the property is modified
				string rawModifiedValue;
				if (!modifiedProperties.TryGet(context, propertyName, out rawModifiedValue))
					continue;

				// get the current values
				var currentValues = GetCurrentValues(queryBuilder.Command, record, propertyName).ToList();

				// check if there are new properties
				var modifiedValues = (rawModifiedValue ?? string.Empty).Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray();

				// get the deleted values
				var deletedValues = currentValues.Except(modifiedValues, StringComparer.OrdinalIgnoreCase);
				var newValues = modifiedValues.Except(currentValues, StringComparer.OrdinalIgnoreCase);

				// create property parameter
				var propertyParameterName = queryBuilder.AddParameter(propertyName, propertyName, DbType.String);

				// generate the delete statements
				foreach (var deletedValue in deletedValues)
				{
					// build the query
					var valueModificationQuery = new ModificationQueryBuilder(queryBuilder);

					// build clause
					var valueParameterName = valueModificationQuery.AddParameter("value", deletedValue, DbType.String);
					valueModificationQuery.AppendWhereClause("[id] = " + idParameterName + " AND [name] = " + propertyParameterName + " AND [value] = " + valueParameterName);

					// append the query
					queryBuilder.AppendQuery(valueModificationQuery.ToDeleteStatement(Name));
				}

				// generate the insert statements
				foreach (var newValue in newValues)
				{
					// build the query
					var valueModificationQuery = new ModificationQueryBuilder(queryBuilder);

					// set column values
					valueModificationQuery.AddColumnValue("id", idParameterName);
					valueModificationQuery.AddColumnValue("name", propertyParameterName);
					valueModificationQuery.AddColumnValue("value", newValue, DbType.String);

					// append the query
					queryBuilder.AppendQuery(valueModificationQuery.ToInsertStatement(Name));
				}
			}
		}
		/// <summary>
		/// Gets the <see cref="RecordListener"/>s for type <paramref name="record"/>.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="record">The <see cref="Record"/> for which to get the type.</param>
		/// <param name="overrideProperties">The overriding properties, might be null.</param>
		/// <returns>Returns the listeners for this <paramref name="record"/>.</returns>
		private List<RecordListener> GetListeners(IMansionContext context, Record record, IPropertyBag overrideProperties = null)
		{
			// retrieve the record type
			var recordListeners = GetListeners(context, record.Type);

			// retrieve the type override, if any
			ITypeDefinition overrideType;
			if (overrideProperties == null || !overrideProperties.TryGet(context, "type", out overrideType))
				return recordListeners.ToList();

			// retrieve the override listeners
			var overrideListeners = GetListeners(overrideType);

			// merge the listeners
			return recordListeners.Union(overrideListeners).ToList();
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="queryBuilder"></param>
		/// <param name="record"> </param>
		/// <param name="modifiedProperties"></param>
		protected override void DoToUpdateStatement(IMansionContext context, ModificationQueryBuilder queryBuilder, Record record, IPropertyBag modifiedProperties)
		{
			// check if the property is not modified
			object input;
			if (!modifiedProperties.TryGet(context, PropertyName, out input))
				return;

			// determine the value
			var value = GetValue(context, input);

			// add the parameter
			var parameterName = queryBuilder.AddParameter(ColumnName, value);

			// set the column value
			queryBuilder.AddColumnValue(ColumnName, parameterName);
		}
		/// <summary>
		/// Parses the <paramref name="properties"/> into a <see cref="IResourcePath"/>.
		/// </summary>
		/// <param name="context">The <see cref="IMansionContext"/>.</param>
		/// <param name="properties">The properties which to parse.</param>
		/// <returns>Returns the parsed <see cref="IResourcePath"/>.</returns>
		public IResourcePath ParsePath(IMansionContext context, IPropertyBag properties)
		{
			// validate arguments
			if (context == null)
				throw new ArgumentNullException("context");
			if (properties == null)
				throw new ArgumentNullException("properties");

			// get the resource base path
			var categoryBasePath = properties.Get(context, "category", "Temp");

			// check if it is an existing resource
			string relativePath;
			if (properties.TryGet(context, "relativePath", out relativePath))
				return new ContentResourcePath(relativePath);

			// check if it is a new file name
			string fileName;
			if (properties.TryGet(context, "fileName", out fileName))
			{
				// get the current date
				var today = DateTime.Today;

				// get the file base name and extension
				var fileBaseName = Path.GetFileNameWithoutExtension(fileName);
				var fileExtension = Path.GetExtension(fileName);

				// make sure the file name is unique
				var index = 0;
				while (File.Exists(ResourceUtils.Combine(physicalBasePath, relativeBasePath, categoryBasePath, today.Year.ToString(CultureInfo.InvariantCulture), today.Month.ToString(CultureInfo.InvariantCulture), fileBaseName + index + fileExtension)))
					index++;

				// create the resource path
				return new ContentResourcePath(ResourceUtils.Combine(categoryBasePath, today.Year.ToString(CultureInfo.InvariantCulture), today.Month.ToString(CultureInfo.InvariantCulture), fileBaseName + index + fileExtension));
			}

			// unkonwn type
			throw new InvalidOperationException("Could not identify resource path");
		}
        /// <summary>
        /// Manages the tags.
        /// </summary>
        /// <param name="context">The <see cref="IMansionContext"/>.</param>
        /// <param name="properties">The new properties.</param>
        public static void ToGuids(IMansionContext context, IPropertyBag properties)
        {
            // validate arguments
            if (context == null)
                throw new ArgumentNullException("context");
            if (properties == null)
                throw new ArgumentNullException("properties");

            // get the tag name string
            string tagNameString;
            if (!properties.TryGet(context, "_tags", out tagNameString))
                return;

            // normalize the tag names
            var tagNames = NormalizeNames(tagNameString).ToList();

            // retrieve the tag index node
            var tagIndexNode = RetrieveTagIndexNode(context);

            // loop over all the tag names
            var tagNodes = tagNames.Select(tagName => RetrieveTagNode(context, tagIndexNode, tagName));

            // set the new tag guids
            properties.Set("tagGuids", string.Join(",", tagNodes.Select(x => x.PermanentId)));
            properties.Remove("_tags");
        }