PageRenderTime 28ms CodeModel.GetById 2ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Umbraco.Core/Models/ContentExtensions.cs

https://github.com/ekolicic/Umbraco-CMS
C# | 743 lines | 387 code | 87 blank | 269 comment | 67 complexity | 87e50673e6d84f53989febc22d4ed576 MD5 | raw file
  1using System;
  2using System.Collections.Generic;
  3using System.Drawing;
  4using System.Drawing.Drawing2D;
  5using System.Drawing.Imaging;
  6using System.Globalization;
  7using System.IO;
  8using System.Linq;
  9using System.Web;
 10using System.Xml;
 11using System.Xml.Linq;
 12using Newtonsoft.Json;
 13using Newtonsoft.Json.Linq;
 14using Umbraco.Core.Configuration;
 15using Umbraco.Core.Configuration.UmbracoSettings;
 16using Umbraco.Core.IO;
 17using Umbraco.Core.Media;
 18using Umbraco.Core.Models.EntityBase;
 19using Umbraco.Core.Models.Membership;
 20using Umbraco.Core.Strings;
 21using Umbraco.Core.Persistence;
 22using Umbraco.Core.Persistence.UnitOfWork;
 23using Umbraco.Core.Services;
 24
 25namespace Umbraco.Core.Models
 26{
 27    public static class ContentExtensions
 28    {
 29        #region IContent
 30
 31        /// <summary>
 32        /// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published)
 33        /// </summary>
 34        /// <param name="entity"></param>
 35        /// <returns></returns>
 36        /// <remarks>
 37        /// This is helpful for determining if the published event will execute during the saved event for a content item.
 38        /// </remarks>
 39        internal static bool JustPublished(this IContent entity)
 40        {
 41            var dirty = (IRememberBeingDirty)entity;
 42            return dirty.WasPropertyDirty("Published") && entity.Published;
 43        }
 44
 45        /// <summary>
 46        /// Determines if a new version should be created
 47        /// </summary>
 48        /// <param name="entity"></param>
 49        /// <returns></returns>
 50        /// <remarks>
 51        /// A new version needs to be created when:
 52        /// * The publish status is changed
 53        /// * The language is changed
 54        /// * The item is already published and is being published again and any property value is changed (to enable a rollback)
 55        /// </remarks>
 56        internal static bool ShouldCreateNewVersion(this IContent entity)
 57        {
 58            var publishedState = ((Content)entity).PublishedState;
 59            return ShouldCreateNewVersion(entity, publishedState);
 60        }
 61
 62        /// <summary>
 63        /// Determines if a new version should be created
 64        /// </summary>
 65        /// <param name="entity"></param>
 66        /// <param name="publishedState"></param>
 67        /// <returns></returns>
 68        /// <remarks>
 69        /// A new version needs to be created when:
 70        /// * The publish status is changed
 71        /// * The language is changed
 72        /// * The item is already published and is being published again and any property value is changed (to enable a rollback)
 73        /// </remarks>
 74        internal static bool ShouldCreateNewVersion(this IContent entity, PublishedState publishedState)
 75        {
 76            var dirtyEntity = (ICanBeDirty)entity;
 77
 78            //check if the published state has changed or the language
 79            var contentChanged =
 80                (dirtyEntity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished)
 81                || dirtyEntity.IsPropertyDirty("Language");
 82
 83            //return true if published or language has changed
 84            if (contentChanged)
 85            {
 86                return true;
 87            }
 88
 89            //check if any user prop has changed
 90            var propertyValueChanged = ((Content)entity).IsAnyUserPropertyDirty();
 91            //check if any content prop has changed
 92            var contentDataChanged = ((Content)entity).IsEntityDirty();
 93
 94            //return true if the item is published and a property has changed or if any content property has changed
 95            return (propertyValueChanged && publishedState == PublishedState.Published) || contentDataChanged;
 96        }
 97
 98        /// <summary>
 99        /// Determines if the published db flag should be set to true for the current entity version and all other db
100        /// versions should have their flag set to false.
101        /// </summary>
102        /// <param name="entity"></param>
103        /// <returns></returns>
104        /// <remarks>
105        /// This is determined by:
106        /// * If a new version is being created and the entity is published
107        /// * If the published state has changed and the entity is published OR the entity has been un-published.
108        /// </remarks>
109        internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity)
110        {
111            var publishedState = ((Content)entity).PublishedState;
112            return entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, entity.ShouldCreateNewVersion(publishedState));
113        }
114
115        /// <summary>
116        /// Determines if the published db flag should be set to true for the current entity version and all other db
117        /// versions should have their flag set to false.
118        /// </summary>
119        /// <param name="entity"></param>
120        /// <param name="publishedState"></param>
121        /// <param name="isCreatingNewVersion"></param>
122        /// <returns></returns>
123        /// <remarks>
124        /// This is determined by:
125        /// * If a new version is being created and the entity is published
126        /// * If the published state has changed and the entity is published OR the entity has been un-published.
127        /// </remarks>
128        internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity, PublishedState publishedState, bool isCreatingNewVersion)
129        {
130            if (isCreatingNewVersion && entity.Published)
131            {
132                return true;
133            }
134
135            //If Published state has changed then previous versions should have their publish state reset.
136            //If state has been changed to unpublished the previous versions publish state should also be reset.
137            if (((ICanBeDirty)entity).IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished))
138            {
139                return true;
140            }
141
142            return false;
143        }
144
145        /// <summary>
146        /// Returns a list of the current contents ancestors, not including the content itself.
147        /// </summary>
148        /// <param name="content">Current content</param>
149        /// <returns>An enumerable list of <see cref="IContent"/> objects</returns>
150        public static IEnumerable<IContent> Ancestors(this IContent content)
151        {
152            return ApplicationContext.Current.Services.ContentService.GetAncestors(content);
153        }
154
155        /// <summary>
156        /// Returns a list of the current contents children.
157        /// </summary>
158        /// <param name="content">Current content</param>
159        /// <returns>An enumerable list of <see cref="IContent"/> objects</returns>
160        public static IEnumerable<IContent> Children(this IContent content)
161        {
162            return ApplicationContext.Current.Services.ContentService.GetChildren(content.Id);
163        }
164
165        /// <summary>
166        /// Returns a list of the current contents descendants, not including the content itself.
167        /// </summary>
168        /// <param name="content">Current content</param>
169        /// <returns>An enumerable list of <see cref="IContent"/> objects</returns>
170        public static IEnumerable<IContent> Descendants(this IContent content)
171        {
172            return ApplicationContext.Current.Services.ContentService.GetDescendants(content);
173        }
174
175        /// <summary>
176        /// Returns the parent of the current content.
177        /// </summary>
178        /// <param name="content">Current content</param>
179        /// <returns>An <see cref="IContent"/> object</returns>
180        public static IContent Parent(this IContent content)
181        {
182            return ApplicationContext.Current.Services.ContentService.GetById(content.ParentId);
183        }
184        #endregion
185
186        #region IMedia
187        /// <summary>
188        /// Returns a list of the current medias ancestors, not including the media itself.
189        /// </summary>
190        /// <param name="media">Current media</param>
191        /// <returns>An enumerable list of <see cref="IMedia"/> objects</returns>
192        public static IEnumerable<IMedia> Ancestors(this IMedia media)
193        {
194            return ApplicationContext.Current.Services.MediaService.GetAncestors(media);
195        }
196
197        /// <summary>
198        /// Returns a list of the current medias children.
199        /// </summary>
200        /// <param name="media">Current media</param>
201        /// <returns>An enumerable list of <see cref="IMedia"/> objects</returns>
202        public static IEnumerable<IMedia> Children(this IMedia media)
203        {
204            return ApplicationContext.Current.Services.MediaService.GetChildren(media.Id);
205        }
206
207        /// <summary>
208        /// Returns a list of the current medias descendants, not including the media itself.
209        /// </summary>
210        /// <param name="media">Current media</param>
211        /// <returns>An enumerable list of <see cref="IMedia"/> objects</returns>
212        public static IEnumerable<IMedia> Descendants(this IMedia media)
213        {
214            return ApplicationContext.Current.Services.MediaService.GetDescendants(media);
215        }
216
217        /// <summary>
218        /// Returns the parent of the current media.
219        /// </summary>
220        /// <param name="media">Current media</param>
221        /// <returns>An <see cref="IMedia"/> object</returns>
222        public static IMedia Parent(this IMedia media)
223        {
224            return ApplicationContext.Current.Services.MediaService.GetById(media.ParentId);
225        }
226        #endregion
227
228        internal static bool IsInRecycleBin(this IContent content)
229        {
230            return IsInRecycleBin(content, Constants.System.RecycleBinContent);
231        }
232
233        internal static bool IsInRecycleBin(this IMedia media)
234        {
235            return IsInRecycleBin(media, Constants.System.RecycleBinMedia);
236        }
237
238        internal static bool IsInRecycleBin(this IContentBase content, int recycleBinId)
239        {
240            return content.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
241                          .Contains(recycleBinId.ToInvariantString());
242        }
243
244        /// <summary>
245        /// Checks if the IContentBase has children
246        /// </summary>
247        /// <param name="content"></param>
248        /// <param name="services"></param>
249        /// <returns></returns>
250        /// <remarks>
251        /// This is a bit of a hack because we need to type check!
252        /// </remarks>
253        internal static bool HasChildren(IContentBase content, ServiceContext services)
254        {
255            if (content is IContent)
256            {
257                return services.ContentService.HasChildren(content.Id);
258            }
259            if (content is IMedia)
260            {
261                return services.MediaService.HasChildren(content.Id);
262            }
263            return false;
264        }
265
266        /// <summary>
267        /// Returns the children for the content base item
268        /// </summary>
269        /// <param name="content"></param>
270        /// <param name="services"></param>
271        /// <returns></returns>
272        /// <remarks>
273        /// This is a bit of a hack because we need to type check!
274        /// </remarks>
275        internal static IEnumerable<IContentBase> Children(IContentBase content, ServiceContext services)
276        {
277            if (content is IContent)
278            {
279                return services.ContentService.GetChildren(content.Id);
280            }
281            if (content is IMedia)
282            {
283                return services.MediaService.GetChildren(content.Id);
284            }
285            return null;
286        }
287
288        /// <summary>
289        /// Returns properties that do not belong to a group
290        /// </summary>
291        /// <param name="content"></param>
292        /// <returns></returns>
293        public static IEnumerable<Property> GetNonGroupedProperties(this IContentBase content)
294        {
295            var propertyIdsInTabs = content.PropertyGroups.SelectMany(pg => pg.PropertyTypes);
296            return content.Properties
297                          .Where(property => propertyIdsInTabs.Contains(property.PropertyType) == false)
298                          .OrderBy(x => x.PropertyType.SortOrder);
299        }
300
301        /// <summary>
302        /// Returns the Property object for the given property group
303        /// </summary>
304        /// <param name="content"></param>
305        /// <param name="propertyGroup"></param>
306        /// <returns></returns>
307        public static IEnumerable<Property> GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup)
308        {
309            //get the properties for the current tab
310            return content.Properties
311                          .Where(property => propertyGroup.PropertyTypes
312                                                          .Select(propertyType => propertyType.Id)
313                                                          .Contains(property.PropertyTypeId))
314                          .OrderBy(x => x.PropertyType.SortOrder);
315        }
316
317        /// <summary>
318        /// Set property values by alias with an annonymous object
319        /// </summary>
320        public static void PropertyValues(this IContent content, object value)
321        {
322            if (value == null)
323                throw new Exception("No properties has been passed in");
324
325            var propertyInfos = value.GetType().GetProperties();
326            foreach (var propertyInfo in propertyInfos)
327            {
328                //Check if a PropertyType with alias exists thus being a valid property
329                var propertyType = content.PropertyTypes.FirstOrDefault(x => x.Alias == propertyInfo.Name);
330                if (propertyType == null)
331                    throw new Exception(
332                        string.Format(
333                            "The property alias {0} is not valid, because no PropertyType with this alias exists",
334                            propertyInfo.Name));
335
336                //Check if a Property with the alias already exists in the collection thus being updated or inserted
337                var item = content.Properties.FirstOrDefault(x => x.Alias == propertyInfo.Name);
338                if (item != null)
339                {
340                    item.Value = propertyInfo.GetValue(value, null);
341                    //Update item with newly added value
342                    content.Properties.Add(item);
343                }
344                else
345                {
346                    //Create new Property to add to collection
347                    var property = propertyType.CreatePropertyFromValue(propertyInfo.GetValue(value, null));
348                    content.Properties.Add(property);
349                }
350            }
351        }
352
353        #region SetValue for setting file contents
354
355        /// <summary>
356        /// Sets and uploads the file from a HttpPostedFileBase object as the property value
357        /// </summary>
358        /// <param name="content"><see cref="IContentBase"/> to add property value to</param>
359        /// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
360        /// <param name="value">The <see cref="HttpPostedFileBase"/> containing the file that will be uploaded</param>
361        public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value)
362        {
363            // Ensure we get the filename without the path in IE in intranet mode 
364            // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
365            var fileName = value.FileName;
366            if (fileName.LastIndexOf(@"\") > 0)
367                fileName = fileName.Substring(fileName.LastIndexOf(@"\") + 1);
368
369            var name =
370                IOHelper.SafeFileName(
371                    fileName.Substring(fileName.LastIndexOf(IOHelper.DirSepChar) + 1,
372                                       fileName.Length - fileName.LastIndexOf(IOHelper.DirSepChar) - 1)
373                            .ToLower());
374
375            if (string.IsNullOrEmpty(name) == false)
376                SetFileOnContent(content, propertyTypeAlias, name, value.InputStream);
377        }
378
379        /// <summary>
380        /// Sets and uploads the file from a HttpPostedFile object as the property value
381        /// </summary>
382        /// <param name="content"><see cref="IContentBase"/> to add property value to</param>
383        /// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
384        /// <param name="value">The <see cref="HttpPostedFile"/> containing the file that will be uploaded</param>
385        public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value)
386        {
387            SetValue(content, propertyTypeAlias, (HttpPostedFileBase)new HttpPostedFileWrapper(value));
388        }
389
390        /// <summary>
391        /// Sets and uploads the file from a HttpPostedFileWrapper object as the property value
392        /// </summary>
393        /// <param name="content"><see cref="IContentBase"/> to add property value to</param>
394        /// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
395        /// <param name="value">The <see cref="HttpPostedFileWrapper"/> containing the file that will be uploaded</param>
396        [Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")]
397        public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value)
398        {
399            SetValue(content, propertyTypeAlias, (HttpPostedFileBase)value);
400        }
401
402        /// <summary>
403        /// Sets and uploads the file from a <see cref="Stream"/> as the property value
404        /// </summary>
405        /// <param name="content"><see cref="IContentBase"/> to add property value to</param>
406        /// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
407        /// <param name="fileName">Name of the file</param>
408        /// <param name="fileStream"><see cref="Stream"/> to save to disk</param>
409        public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream)
410        {
411            var name = IOHelper.SafeFileName(fileName);
412
413            if (string.IsNullOrEmpty(name) == false && fileStream != null)
414                SetFileOnContent(content, propertyTypeAlias, name, fileStream);
415        }
416
417        private static void SetFileOnContent(IContentBase content, string propertyTypeAlias, string filename, Stream fileStream)
418        {
419            var property = content.Properties.FirstOrDefault(x => x.Alias == propertyTypeAlias);
420            if (property == null)
421                return;
422
423            //TODO: ALl of this naming logic needs to be put into the ImageHelper and then we need to change FileUploadPropertyValueEditor to do the same!
424
425            var numberedFolder = MediaSubfolderCounter.Current.Increment();
426            var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
427                                              ? Path.Combine(numberedFolder.ToString(CultureInfo.InvariantCulture), filename)
428                                              : numberedFolder + "-" + filename;
429
430            var extension = Path.GetExtension(filename).Substring(1).ToLowerInvariant();
431
432            //the file size is the length of the stream in bytes
433            var fileSize = fileStream.Length;
434
435            var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
436            fs.AddFile(fileName, fileStream);
437
438            //Check if file supports resizing and create thumbnails
439            var supportsResizing = UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension);
440
441            //the config section used to auto-fill properties
442            IImagingAutoFillUploadField uploadFieldConfigNode = null;
443
444            //Check for auto fill of additional properties
445            if (UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties != null)
446            {
447                uploadFieldConfigNode = UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties
448                                    .FirstOrDefault(x => x.Alias == propertyTypeAlias);
449
450            }
451
452            if (supportsResizing)
453            {
454                //get the original image from the original stream
455                if (fileStream.CanSeek) fileStream.Seek(0, 0);
456                using (var originalImage = Image.FromStream(fileStream))
457                {
458                    var additionalSizes = new List<int>();
459
460                    //Look up Prevalues for this upload datatype - if it is an upload datatype - get additional configured sizes
461                    if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
462                    {
463                        //Get Prevalues by the DataType's Id: property.PropertyType.DataTypeId
464                        var values = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId);
465                        var thumbnailSizes = values.FirstOrDefault();
466                        //Additional thumbnails configured as prevalues on the DataType
467                        if (thumbnailSizes != null)
468                        {
469                            var sep = (thumbnailSizes.Contains("") == false && thumbnailSizes.Contains(",")) ? ',' : ';';
470                            foreach (var thumb in thumbnailSizes.Split(sep))
471                            {
472                                int thumbSize;
473                                if (thumb != "" && int.TryParse(thumb, out thumbSize))
474                                {
475                                    additionalSizes.Add(thumbSize);
476                                }
477                            }
478                        }
479                    }
480
481                    ImageHelper.GenerateMediaThumbnails(fs, fileName, extension, originalImage, additionalSizes);
482
483                    //while the image is still open, we'll check if we need to auto-populate the image properties
484                    if (uploadFieldConfigNode != null)
485                    {
486                        content.SetValue(uploadFieldConfigNode.WidthFieldAlias, originalImage.Width.ToString(CultureInfo.InvariantCulture));
487                        content.SetValue(uploadFieldConfigNode.HeightFieldAlias, originalImage.Height.ToString(CultureInfo.InvariantCulture));
488                    }
489
490                }
491            }
492
493            //if auto-fill is true, then fill the remaining, non-image properties
494            if (uploadFieldConfigNode != null)
495            {
496                content.SetValue(uploadFieldConfigNode.LengthFieldAlias, fileSize.ToString(CultureInfo.InvariantCulture));
497                content.SetValue(uploadFieldConfigNode.ExtensionFieldAlias, extension);
498            }
499
500            //Set the value of the property to that of the uploaded file's url
501            property.Value = fs.GetUrl(fileName);
502        }
503
504        #endregion
505
506        #region User/Profile methods
507        /// <summary>
508        /// Gets the <see cref="IProfile"/> for the Creator of this media item.
509        /// </summary>
510        public static IProfile GetCreatorProfile(this IMedia media)
511        {
512            return ApplicationContext.Current.Services.UserService.GetProfileById(media.CreatorId);
513        }
514
515        /// <summary>
516        /// Gets the <see cref="IProfile"/> for the Creator of this content item.
517        /// </summary>
518        public static IProfile GetCreatorProfile(this IContentBase content)
519        {
520            return ApplicationContext.Current.Services.UserService.GetProfileById(content.CreatorId);
521        }
522
523        /// <summary>
524        /// Gets the <see cref="IProfile"/> for the Writer of this content.
525        /// </summary>
526        public static IProfile GetWriterProfile(this IContent content)
527        {
528            return ApplicationContext.Current.Services.UserService.GetProfileById(content.WriterId);
529        }
530        #endregion
531
532        /// <summary>
533        /// Checks whether an <see cref="IContent"/> item has any published versions
534        /// </summary>
535        /// <param name="content"></param>
536        /// <returns>True if the content has any published versiom otherwise False</returns>
537        public static bool HasPublishedVersion(this IContent content)
538        {
539            if (content.HasIdentity == false)
540                return false;
541
542            return ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id);
543        }
544
545        #region Tag methods
546
547        ///// <summary>
548        ///// Returns the tags for the given property
549        ///// </summary>
550        ///// <param name="content"></param>
551        ///// <param name="propertyTypeAlias"></param>
552        ///// <param name="tagGroup"></param>
553        ///// <returns></returns>
554        ///// <remarks>
555        ///// The tags returned are only relavent for published content & saved media or members 
556        ///// </remarks>
557        //public static IEnumerable<ITag> GetTags(this IContentBase content, string propertyTypeAlias, string tagGroup = "default")
558        //{
559
560        //}
561
562        /// <summary>
563        /// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags.
564        /// </summary>
565        /// <param name="content">The content item to assign the tags to</param>
566        /// <param name="propertyTypeAlias">The property alias to assign the tags to</param>
567        /// <param name="tags">The tags to assign</param>
568        /// <param name="replaceTags">True to replace the tags on the current property with the tags specified or false to merge them with the currently assigned ones</param>
569        /// <param name="tagGroup">The group/category to assign the tags, the default value is "default"</param>
570        /// <returns></returns>
571        public static void SetTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
572        {
573            content.SetTags(TagCacheStorageType.Csv, propertyTypeAlias, tags, replaceTags, tagGroup);
574        }
575
576        /// <summary>
577        /// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags.
578        /// </summary>
579        /// <param name="content">The content item to assign the tags to</param>
580        /// <param name="storageType">The tag storage type in cache (default is csv)</param>
581        /// <param name="propertyTypeAlias">The property alias to assign the tags to</param>
582        /// <param name="tags">The tags to assign</param>
583        /// <param name="replaceTags">True to replace the tags on the current property with the tags specified or false to merge them with the currently assigned ones</param>
584        /// <param name="tagGroup">The group/category to assign the tags, the default value is "default"</param>
585        /// <returns></returns>
586        public static void SetTags(this IContentBase content, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
587        {
588            var property = content.Properties[propertyTypeAlias];
589            if (property == null)
590            {
591                throw new IndexOutOfRangeException("No property exists with name " + propertyTypeAlias);
592            }
593            property.SetTags(storageType, propertyTypeAlias, tags, replaceTags, tagGroup);
594        }
595
596        internal static void SetTags(this Property property, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
597        {
598            if (property == null) throw new ArgumentNullException("property");
599
600            var trimmedTags = tags.Select(x => x.Trim()).ToArray();
601
602            property.TagSupport.Enable = true;
603            property.TagSupport.Tags = trimmedTags.Select(x => new Tuple<string, string>(x, tagGroup));
604            property.TagSupport.Behavior = replaceTags ? PropertyTagBehavior.Replace : PropertyTagBehavior.Merge;
605
606            //ensure the property value is set to the same thing
607            if (replaceTags)
608            {
609                switch (storageType)
610                {
611                    case TagCacheStorageType.Csv:
612                        property.Value = string.Join(",", trimmedTags);
613                        break;
614                    case TagCacheStorageType.Json:
615                        //json array
616                        property.Value = JsonConvert.SerializeObject(trimmedTags);
617                        break;
618                }
619
620            }
621            else
622            {
623                switch (storageType)
624                {
625                    case TagCacheStorageType.Csv:
626                        var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
627                                       .Select(x => x.Trim());
628                        property.Value = string.Join(",", trimmedTags.Union(currTags));
629                        break;
630                    case TagCacheStorageType.Json:
631                        var currJson = JsonConvert.DeserializeObject<JArray>(property.Value.ToString());
632                        //need to append the new ones
633                        foreach (var tag in trimmedTags)
634                        {
635                            currJson.Add(tag);
636                        }
637                        //json array
638                        property.Value = JsonConvert.SerializeObject(currJson);
639                        break;
640                }
641            }
642        }
643
644        /// <summary>
645        /// Remove any of the tags specified in the collection from the property if they are currently assigned.
646        /// </summary>
647        /// <param name="content"></param>
648        /// <param name="propertyTypeAlias"></param>
649        /// <param name="tags"></param>
650        /// <param name="tagGroup">The group/category that the tags are currently assigned to, the default value is "default"</param>
651        public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, string tagGroup = "default")
652        {
653            var property = content.Properties[propertyTypeAlias];
654            if (property == null)
655            {
656                throw new IndexOutOfRangeException("No property exists with name " + propertyTypeAlias);
657            }
658
659            var trimmedTags = tags.Select(x => x.Trim()).ToArray();
660
661            property.TagSupport.Behavior = PropertyTagBehavior.Remove;
662            property.TagSupport.Enable = true;
663            property.TagSupport.Tags = trimmedTags.Select(x => new Tuple<string, string>(x, tagGroup));
664
665            //set the property value
666            var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
667                                       .Select(x => x.Trim());
668
669            property.Value = string.Join(",", currTags.Except(trimmedTags));
670        }
671
672        #endregion
673
674        #region XML methods
675        /// <summary>
676        /// Creates the full xml representation for the <see cref="IContent"/> object and all of it's descendants
677        /// </summary>
678        /// <param name="content"><see cref="IContent"/> to generate xml for</param>
679        /// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
680        internal static XElement ToDeepXml(this IContent content)
681        {
682            return ApplicationContext.Current.Services.PackagingService.Export(content, true, raiseEvents: false);
683        }
684
685        /// <summary>
686        /// Creates the xml representation for the <see cref="IContent"/> object
687        /// </summary>
688        /// <param name="content"><see cref="IContent"/> to generate xml for</param>
689        /// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
690        public static XElement ToXml(this IContent content)
691        {
692            return ApplicationContext.Current.Services.PackagingService.Export(content, raiseEvents: false);
693        }
694
695        /// <summary>
696        /// Creates the xml representation for the <see cref="IMedia"/> object
697        /// </summary>
698        /// <param name="media"><see cref="IContent"/> to generate xml for</param>
699        /// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
700        public static XElement ToXml(this IMedia media)
701        {
702            return ApplicationContext.Current.Services.PackagingService.Export(media, raiseEvents: false);
703        }
704
705        /// <summary>
706        /// Creates the full xml representation for the <see cref="IMedia"/> object and all of it's descendants
707        /// </summary>
708        /// <param name="media"><see cref="IMedia"/> to generate xml for</param>
709        /// <returns>Xml representation of the passed in <see cref="IMedia"/></returns>
710        internal static XElement ToDeepXml(this IMedia media)
711        {
712            return ApplicationContext.Current.Services.PackagingService.Export(media, true, raiseEvents: false);
713        }
714
715        /// <summary>
716        /// Creates the xml representation for the <see cref="IContent"/> object
717        /// </summary>
718        /// <param name="content"><see cref="IContent"/> to generate xml for</param>
719        /// <param name="isPreview">Boolean indicating whether the xml should be generated for preview</param>
720        /// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
721        public static XElement ToXml(this IContent content, bool isPreview)
722        {
723            //TODO Do a proper implementation of this
724            //If current IContent is published we should get latest unpublished version
725            return content.ToXml();
726        }
727
728        /// <summary>
729        /// Creates the xml representation for the <see cref="IMember"/> object
730        /// </summary>
731        /// <param name="member"><see cref="IMember"/> to generate xml for</param>
732        /// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
733        public static XElement ToXml(this IMember member)
734        {
735            return ((PackagingService)(ApplicationContext.Current.Services.PackagingService)).Export(member);
736        }
737
738        #endregion
739    }
740
741
742
743}