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