PageRenderTime 78ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/umbraco_6f33e2b81175/umbraco/cms/businesslogic/ContentType.cs

https://github.com/jracabado/justEdit-
C# | 1273 lines | 743 code | 173 blank | 357 comment | 67 complexity | 7c08d054cd9735db7b72f12f8a04e562 MD5 | raw file
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Threading;
  6. using System.Web;
  7. using System.Runtime.CompilerServices;
  8. using System.Linq;
  9. using umbraco.cms.businesslogic.cache;
  10. using umbraco.cms.businesslogic.datatype;
  11. using umbraco.cms.businesslogic.language;
  12. using umbraco.cms.businesslogic.propertytype;
  13. using umbraco.cms.businesslogic.web;
  14. using umbraco.DataLayer;
  15. using umbraco.BusinessLogic;
  16. [assembly: InternalsVisibleTo("Umbraco.Test")]
  17. namespace umbraco.cms.businesslogic
  18. {
  19. /// <summary>
  20. /// ContentTypes defines the datafields of Content objects of that type, it's similar to defining columns
  21. /// in a database table, where the PropertyTypes on the ContentType each responds to a Column, and the Content
  22. /// objects is similar to a row of data, where the Properties on the Content object corresponds to the PropertyTypes
  23. /// on the ContentType.
  24. ///
  25. /// Besides data definition, the ContentType also defines the sorting and grouping (in tabs) of Properties/Datafields
  26. /// on the Content and which Content (by ContentType) can be created as child to the Content of the ContentType.
  27. /// </summary>
  28. public class ContentType : CMSNode
  29. {
  30. #region Constructors
  31. /// <summary>
  32. ///
  33. /// </summary>
  34. /// <param name="id"></param>
  35. public ContentType(int id) : base(id) { }
  36. /// <summary>
  37. /// Initializes a new instance of the <see cref="ContentType"/> class.
  38. /// </summary>
  39. /// <param name="id">The id.</param>
  40. public ContentType(Guid id) : base(id) { }
  41. public ContentType(int id, bool noSetup) : base(id, noSetup) { }
  42. public ContentType(Guid id, bool noSetup) : base(id, noSetup) { }
  43. ///// <summary>
  44. ///// Initializes a new instance of the <see cref="ContentType"/> class.
  45. ///// </summary>
  46. ///// <param name="id">The id.</param>
  47. ///// <param name="UseOptimizedMode">if set to <c>true</c> [use optimized mode] which loads in the data from the
  48. ///// database in an optimized manner (less queries)
  49. ///// </param>
  50. //public ContentType(bool optimizedMode, int id)
  51. // : base(id, optimizedMode)
  52. //{
  53. // this._optimizedMode = optimizedMode;
  54. // if (optimizedMode)
  55. // {
  56. // }
  57. //}
  58. /// <summary>
  59. /// Creates a new content type object manually.
  60. /// </summary>
  61. /// <param name="id"></param>
  62. /// <param name="alias"></param>
  63. /// <param name="icon"></param>
  64. /// <param name="thumbnail"></param>
  65. /// <param name="masterContentType"></param>
  66. /// <remarks>
  67. /// This is like creating a ContentType node using optimized mode but this lets you set
  68. /// all of the properties that are initialized normally from the database.
  69. /// This is used for performance reasons.
  70. /// </remarks>
  71. internal ContentType(int id, string alias, string icon, string thumbnail, int? masterContentType)
  72. : base(id, true)
  73. {
  74. _alias = alias;
  75. _iconurl = icon;
  76. _thumbnail = thumbnail;
  77. if (masterContentType.HasValue)
  78. m_masterContentType = masterContentType.Value;
  79. }
  80. #endregion
  81. #region Constants and static members
  82. protected internal const string m_SQLOptimizedGetAll = @"
  83. SELECT id, createDate, trashed, parentId, nodeObjectType, nodeUser, level, path, sortOrder, uniqueID, text,
  84. masterContentType,Alias,icon,thumbnail,description
  85. FROM umbracoNode INNER JOIN cmsContentType ON umbracoNode.id = cmsContentType.nodeId
  86. WHERE nodeObjectType = @nodeObjectType";
  87. private static readonly object m_Locker = new object();
  88. #endregion
  89. #region Static Methods
  90. private static Dictionary<Tuple<string, string>, Guid> _propertyTypeCache = new Dictionary<Tuple<string, string>, Guid>();
  91. public static void RemoveFromDataTypeCache(string contentTypeAlias)
  92. {
  93. lock (_propertyTypeCache)
  94. {
  95. List<Tuple<string, string>> toDelete = new List<Tuple<string, string>>();
  96. foreach (Tuple<string, string> key in _propertyTypeCache.Keys)
  97. {
  98. if (string.Equals(key.first, contentTypeAlias))
  99. {
  100. toDelete.Add(key);
  101. }
  102. }
  103. foreach (Tuple<string, string> key in toDelete)
  104. {
  105. _propertyTypeCache.Remove(key);
  106. }
  107. }
  108. }
  109. public static Guid GetDataType(string contentTypeAlias, string propertyTypeAlias)
  110. {
  111. Tuple<string, string> key = new Tuple<string, string>()
  112. {
  113. first = contentTypeAlias,
  114. second = propertyTypeAlias
  115. };
  116. //The property type is not on IProperty (it's not stored in NodeFactory)
  117. //first check the cache
  118. if (_propertyTypeCache != null && _propertyTypeCache.ContainsKey(key))
  119. {
  120. return _propertyTypeCache[key];
  121. }
  122. //Instead of going via API, run this query to find the control type
  123. //by-passes a lot of queries just to determine if this is a true/false data type
  124. //This SQL returns a larger recordset than intended
  125. //causing controlId to sometimes be null instead of correct
  126. //because all properties for the type are returned
  127. //side effect of changing inner join to left join when adding masterContentType
  128. //string sql = "select " +
  129. // "cmsDataType.controlId, masterContentType.alias as masterAlias " +
  130. // "from " +
  131. // "cmsContentType " +
  132. // "inner join cmsPropertyType on (cmsContentType.nodeId = cmsPropertyType.contentTypeId) " +
  133. // "left join cmsDataType on (cmsPropertyType.dataTypeId = cmsDataType.nodeId) and cmsPropertyType.Alias = @propertyAlias " +
  134. // "left join cmsContentType masterContentType on masterContentType.nodeid = cmsContentType.masterContentType " +
  135. // "where cmsContentType.alias = @contentTypeAlias";
  136. //this SQL correctly returns a single row when the property exists, but still returns masterAlias if it doesn't
  137. string sql = "select cmsDataType.controlId, masterContentType.alias as masterAlias " +
  138. "from " +
  139. "cmsContentType " +
  140. "left join cmsPropertyType on (cmsContentType.nodeId = cmsPropertyType.contentTypeId and cmsPropertyType.Alias = @propertyAlias) " +
  141. "left join cmsDataType on (cmsPropertyType.dataTypeId = cmsDataType.nodeId) " +
  142. "left join cmsContentType masterContentType on masterContentType.nodeid = cmsContentType.masterContentType " +
  143. "where " +
  144. "cmsContentType.alias = @contentTypeAlias";
  145. //Ensure that getdatatype doesn't throw an exception
  146. //http://our.umbraco.org/forum/developers/razor/18085-Access-custom-node-properties-with-Razor
  147. //grab the controlid or test for parent
  148. Guid controlId = Guid.Empty;
  149. IRecordsReader reader = null;
  150. try
  151. {
  152. reader = Application.SqlHelper.ExecuteReader(sql,
  153. Application.SqlHelper.CreateParameter("@contentTypeAlias", contentTypeAlias),
  154. Application.SqlHelper.CreateParameter("@propertyAlias", propertyTypeAlias)
  155. );
  156. if (reader.Read())
  157. {
  158. if (!reader.IsNull("controlId"))
  159. controlId = reader.GetGuid("controlId");
  160. else if (!reader.IsNull("masterAlias") && !String.IsNullOrEmpty(reader.GetString("masterAlias")))
  161. {
  162. controlId = GetDataType(reader.GetString("masterAlias"), propertyTypeAlias);
  163. }
  164. }
  165. }
  166. catch (UmbracoException)
  167. {
  168. _propertyTypeCache.Add(key, controlId);
  169. }
  170. finally
  171. {
  172. reader.Close();
  173. }
  174. //add to cache
  175. _propertyTypeCache.Add(key, controlId);
  176. return controlId;
  177. }
  178. /// <summary>
  179. /// Gets the type of the content.
  180. /// </summary>
  181. /// <param name="id">The id.</param>
  182. /// <returns></returns>
  183. public static ContentType GetContentType(int id)
  184. {
  185. return Cache.GetCacheItem<ContentType>(string.Format("UmbracoContentType{0}", id.ToString()),
  186. m_Locker,
  187. TimeSpan.FromMinutes(30),
  188. delegate
  189. {
  190. return new ContentType(id);
  191. });
  192. }
  193. /// <summary>
  194. /// If true, this instance uses default umbraco data only.
  195. /// </summary>
  196. /// <param name="ct">The ct.</param>
  197. /// <returns></returns>
  198. private static bool usesUmbracoDataOnly(ContentType ct)
  199. {
  200. bool retVal = true;
  201. foreach (PropertyType pt in ct.PropertyTypes)
  202. {
  203. if (!DataTypeDefinition.IsDefaultData(pt.DataTypeDefinition.DataType.Data))
  204. {
  205. retVal = false;
  206. break;
  207. }
  208. }
  209. return retVal;
  210. }
  211. // This is needed, because the Tab class is protected and as such it's not possible for
  212. // the PropertyType class to easily access the cache flusher
  213. /// <summary>
  214. /// Flushes the tab cache.
  215. /// </summary>
  216. /// <param name="TabId">The tab id.</param>
  217. public static void FlushTabCache(int TabId, int ContentTypeId)
  218. {
  219. Tab.FlushCache(TabId, ContentTypeId);
  220. }
  221. /// <summary>
  222. /// Creates a new ContentType
  223. /// </summary>
  224. /// <param name="NodeId">The CMSNode Id of the ContentType</param>
  225. /// <param name="Alias">The Alias of the ContentType</param>
  226. /// <param name="IconUrl">The Iconurl of Contents of this ContentType</param>
  227. protected static void Create(int NodeId, string Alias, string IconUrl)
  228. {
  229. SqlHelper.ExecuteNonQuery(
  230. "Insert into cmsContentType (nodeId,alias,icon) values (" + NodeId + ",'" + helpers.Casing.SafeAliasWithForcingCheck(Alias) +
  231. "','" + IconUrl + "')");
  232. }
  233. /// <summary>
  234. /// Initializes a ContentType object given the Alias.
  235. /// </summary>
  236. /// <param name="Alias">Alias of the content type</param>
  237. /// <returns>
  238. /// The ContentType with the corrosponding Alias
  239. /// </returns>
  240. public static ContentType GetByAlias(string Alias)
  241. {
  242. return new ContentType(SqlHelper.ExecuteScalar<int>("SELECT nodeid FROM cmsContentType WHERE alias = @alias",
  243. SqlHelper.CreateParameter("@alias", Alias)));
  244. }
  245. /// <summary>
  246. /// Helper method for getting the Tab id from a given PropertyType
  247. /// </summary>
  248. /// <param name="pt">The PropertyType from which to get the Tab Id</param>
  249. /// <returns>The Id of the Tab on which the PropertyType is placed</returns>
  250. public static int getTabIdFromPropertyType(PropertyType pt)
  251. {
  252. object tmp = SqlHelper.ExecuteScalar<object>("Select tabId from cmsPropertyType where id = " + pt.Id.ToString());
  253. if (tmp == DBNull.Value)
  254. return 0;
  255. else return int.Parse(tmp.ToString());
  256. }
  257. #endregion
  258. #region Private Members
  259. //private bool _optimizedMode = false;
  260. private string _alias;
  261. private string _iconurl;
  262. private string _description;
  263. private string _thumbnail;
  264. private int m_masterContentType = 0;
  265. private List<int> m_AllowedChildContentTypeIDs = null;
  266. private List<TabI> m_VirtualTabs = null;
  267. private static readonly object propertyTypesCacheSyncLock = new object();
  268. #endregion
  269. #region Public Properties
  270. /// <summary>
  271. /// Gets or sets the description.
  272. /// </summary>
  273. /// <value>The description.</value>
  274. public string Description
  275. {
  276. get
  277. {
  278. if (_description != null)
  279. {
  280. if (!_description.StartsWith("#"))
  281. return _description;
  282. else
  283. {
  284. Language lang = Language.GetByCultureCode(Thread.CurrentThread.CurrentCulture.Name);
  285. if (lang != null)
  286. {
  287. if (Dictionary.DictionaryItem.hasKey(_description.Substring(1, _description.Length - 1)))
  288. {
  289. var di =
  290. new Dictionary.DictionaryItem(_description.Substring(1, _description.Length - 1));
  291. return di.Value(lang.id);
  292. }
  293. }
  294. }
  295. return "[" + _description + "]";
  296. }
  297. return _description;
  298. }
  299. set
  300. {
  301. _description = value;
  302. SqlHelper.ExecuteNonQuery(
  303. "update cmsContentType set description = @description where nodeId = @id",
  304. SqlHelper.CreateParameter("@description", value),
  305. SqlHelper.CreateParameter("@id", Id));
  306. FlushFromCache(Id);
  307. }
  308. }
  309. /// <summary>
  310. /// Gets or sets the thumbnail.
  311. /// </summary>
  312. /// <value>The thumbnail.</value>
  313. public string Thumbnail
  314. {
  315. get { return _thumbnail; }
  316. set
  317. {
  318. _thumbnail = value;
  319. SqlHelper.ExecuteNonQuery(
  320. "update cmsContentType set thumbnail = @thumbnail where nodeId = @id",
  321. SqlHelper.CreateParameter("@thumbnail", value),
  322. SqlHelper.CreateParameter("@id", Id));
  323. FlushFromCache(Id);
  324. }
  325. }
  326. ///// <summary>
  327. ///// Gets or sets a value indicating whether [optimized mode].
  328. ///// </summary>
  329. ///// <value><c>true</c> if [optimized mode]; otherwise, <c>false</c>.</value>
  330. //public bool OptimizedMode
  331. //{
  332. // get { return _optimizedMode; }
  333. // set { _optimizedMode = value; }
  334. //}
  335. /// <summary>
  336. /// Human readable name/label
  337. /// </summary>
  338. /// <value></value>
  339. public override string Text
  340. {
  341. get
  342. {
  343. string tempText = base.Text;
  344. if (!tempText.StartsWith("#"))
  345. return tempText;
  346. else
  347. {
  348. Language lang =
  349. Language.GetByCultureCode(Thread.CurrentThread.CurrentCulture.Name);
  350. if (lang != null)
  351. {
  352. if (Dictionary.DictionaryItem.hasKey(tempText.Substring(1, tempText.Length - 1)))
  353. {
  354. Dictionary.DictionaryItem di =
  355. new Dictionary.DictionaryItem(tempText.Substring(1, tempText.Length - 1));
  356. return di.Value(lang.id);
  357. }
  358. }
  359. return "[" + tempText + "]";
  360. }
  361. }
  362. set
  363. {
  364. base.Text = value;
  365. // Remove from cache
  366. FlushFromCache(Id);
  367. }
  368. }
  369. //THIS SHOULD BE IENUMERABLE<PROPERTYTYPE> NOT LIST!
  370. /// <summary>
  371. /// The "datafield/column" definitions, a Content object of this type will have an equivalent
  372. /// list of Properties.
  373. /// </summary>
  374. /// <remarks>
  375. /// Property types are are cached so any calls to this property will returne cached versions
  376. /// </remarks>
  377. public List<PropertyType> PropertyTypes
  378. {
  379. get
  380. {
  381. string cacheKey = GetPropertiesCacheKey();
  382. return Cache.GetCacheItem<List<PropertyType>>(cacheKey, propertyTypesCacheSyncLock,
  383. TimeSpan.FromMinutes(15),
  384. delegate
  385. {
  386. List<PropertyType> result = new List<PropertyType>();
  387. using (IRecordsReader dr =
  388. SqlHelper.ExecuteReader(
  389. "select id from cmsPropertyType where contentTypeId = @ctId order by sortOrder",
  390. SqlHelper.CreateParameter("@ctId", Id)))
  391. {
  392. while (dr.Read())
  393. {
  394. int id = dr.GetInt("id");
  395. PropertyType pt = PropertyType.GetPropertyType(id);
  396. if (pt != null)
  397. result.Add(pt);
  398. }
  399. }
  400. // Get Property Types from the master content type
  401. if (MasterContentType != 0)
  402. {
  403. foreach (PropertyType pt in ContentType.GetContentType(MasterContentType).PropertyTypes)
  404. {
  405. result.Add(pt);
  406. }
  407. }
  408. return result;
  409. });
  410. }
  411. }
  412. /// <summary>
  413. /// The Alias of the ContentType, is used for import/export and more human readable initialization see: GetByAlias
  414. /// method.
  415. /// </summary>
  416. public string Alias
  417. {
  418. get { return _alias; }
  419. set
  420. {
  421. _alias = helpers.Casing.SafeAliasWithForcingCheck(value);
  422. // validate if alias is empty
  423. if (String.IsNullOrEmpty(_alias))
  424. {
  425. throw new ArgumentOutOfRangeException("An Alias cannot be empty");
  426. }
  427. SqlHelper.ExecuteNonQuery(
  428. "update cmsContentType set alias = @alias where nodeId = @id",
  429. SqlHelper.CreateParameter("@alias", _alias),
  430. SqlHelper.CreateParameter("@id", Id));
  431. // Remove from cache
  432. FlushFromCache(Id);
  433. }
  434. }
  435. /// <summary>
  436. /// A Content object is often (always) represented in the treeview in the Umbraco console, the ContentType defines
  437. /// which Icon the Content of this type is representated with.
  438. /// </summary>
  439. public string IconUrl
  440. {
  441. get { return _iconurl; }
  442. set
  443. {
  444. _iconurl = value;
  445. SqlHelper.ExecuteNonQuery(
  446. "update cmsContentType set icon='" + value + "' where nodeid = " + Id);
  447. // Remove from cache
  448. FlushFromCache(Id);
  449. }
  450. }
  451. /// <summary>
  452. /// Gets or sets the Master Content Type for inheritance of tabs and properties.
  453. /// </summary>
  454. /// <value>The ID of the Master Content Type</value>
  455. public int MasterContentType
  456. {
  457. get
  458. {
  459. return m_masterContentType;
  460. }
  461. set
  462. {
  463. m_masterContentType = value;
  464. SqlHelper.ExecuteNonQuery("update cmsContentType set masterContentType = @masterContentType where nodeId = @nodeId",
  465. SqlHelper.CreateParameter("@masterContentType", value),
  466. SqlHelper.CreateParameter("@nodeId", Id));
  467. // Remove from cache
  468. FlushFromCache(Id);
  469. }
  470. }
  471. /// <summary>
  472. /// Retrieve a list of all Tabs on the current ContentType
  473. /// </summary>
  474. public TabI[] getVirtualTabs
  475. {
  476. get
  477. {
  478. EnsureVirtualTabs();
  479. return m_VirtualTabs.ToArray();
  480. }
  481. }
  482. /// <summary>
  483. /// Clears the locally loaded tabs which forces them to be reloaded next time they requested
  484. /// </summary>
  485. public void ClearVirtualTabs()
  486. {
  487. // zb-00040 #29889 : clear the right cache! t.contentType is the ctype which _defines_ the tab, not the current one.
  488. foreach (TabI t in getVirtualTabs)
  489. Tab.FlushCache(t.Id, Id);
  490. m_VirtualTabs = null;
  491. }
  492. /// <summary>
  493. /// The list of ContentType Id's that defines which Content (by ContentType) can be created as child
  494. /// to the Content of this ContentType
  495. /// </summary>
  496. public int[] AllowedChildContentTypeIDs
  497. {
  498. get
  499. {
  500. //optimize this property so it lazy loads the data only one time.
  501. if (m_AllowedChildContentTypeIDs == null)
  502. {
  503. m_AllowedChildContentTypeIDs = new List<int>();
  504. using (IRecordsReader dr = SqlHelper.ExecuteReader(
  505. "Select AllowedId from cmsContentTypeAllowedContentType where id=" +
  506. Id))
  507. {
  508. while (dr.Read())
  509. {
  510. m_AllowedChildContentTypeIDs.Add(dr.GetInt("AllowedId"));
  511. }
  512. }
  513. }
  514. return m_AllowedChildContentTypeIDs.ToArray();
  515. }
  516. set
  517. {
  518. m_AllowedChildContentTypeIDs = value.ToList();
  519. SqlHelper.ExecuteNonQuery(
  520. "delete from cmsContentTypeAllowedContentType where id=" + Id);
  521. foreach (int i in value)
  522. {
  523. SqlHelper.ExecuteNonQuery(
  524. "insert into cmsContentTypeAllowedContentType (id,AllowedId) values (" +
  525. Id + "," + i + ")");
  526. }
  527. }
  528. }
  529. #endregion
  530. #region Public Methods
  531. /// <summary>
  532. /// Gets the raw text.
  533. /// </summary>
  534. /// <returns></returns>
  535. public string GetRawText()
  536. {
  537. return base.Text;
  538. }
  539. public string GetRawDescription()
  540. {
  541. return _description;
  542. }
  543. /// <summary>
  544. /// Used to persist object changes to the database. In Version3.0 it's just a stub for future compatibility
  545. /// </summary>
  546. public override void Save()
  547. {
  548. base.Save();
  549. // Remove from all doctypes from cache
  550. FlushAllFromCache();
  551. }
  552. /// <summary>
  553. /// Retrieve a list of all ContentTypes
  554. /// </summary>
  555. /// <returns>The list of all ContentTypes</returns>
  556. public ContentType[] GetAll()
  557. {
  558. var contentTypes = new List<ContentType>();
  559. using (IRecordsReader dr =
  560. SqlHelper.ExecuteReader(m_SQLOptimizedGetAll.Trim(), SqlHelper.CreateParameter("@nodeObjectType", base.nodeObjectType)))
  561. {
  562. while (dr.Read())
  563. {
  564. //create the ContentType object without setting up
  565. ContentType ct = new ContentType(dr.Get<int>("id"), true);
  566. //populate it's CMSNode properties
  567. ct.PopulateCMSNodeFromReader(dr);
  568. //populate it's ContentType properties
  569. ct.PopulateContentTypeNodeFromReader(dr);
  570. contentTypes.Add(ct);
  571. }
  572. }
  573. return contentTypes.ToArray();
  574. }
  575. /// <summary>
  576. /// Adding a PropertyType to the ContentType, will add a new datafield/Property on all Documents of this Type.
  577. /// </summary>
  578. /// <param name="dt">The DataTypeDefinition of the PropertyType</param>
  579. /// <param name="Alias">The Alias of the PropertyType</param>
  580. /// <param name="Name">The userfriendly name</param>
  581. public PropertyType AddPropertyType(DataTypeDefinition dt, string Alias, string Name)
  582. {
  583. PropertyType pt = PropertyType.MakeNew(dt, this, Name, Alias);
  584. // Optimized call
  585. populatePropertyData(pt, this.Id);
  586. // Inherited content types (document types only)
  587. populateMasterContentTypes(pt, this.Id);
  588. // foreach (Content c in Content.getContentOfContentType(this))
  589. // c.addProperty(pt,c.Version);
  590. // Remove from cache
  591. FlushFromCache(Id);
  592. return pt;
  593. }
  594. /// <summary>
  595. /// Adding a PropertyType to a Tab, the Tabs are primarily used for making the
  596. /// editing interface more userfriendly.
  597. /// </summary>
  598. /// <param name="pt">The PropertyType</param>
  599. /// <param name="TabId">The Id of the Tab</param>
  600. public void SetTabOnPropertyType(PropertyType pt, int TabId)
  601. {
  602. // This is essentially just a wrapper for the property
  603. pt.TabId = TabId;
  604. //flush the content type cache, the the tab cache (why so much cache?! argh!)
  605. pt.FlushCacheBasedOnTab();
  606. }
  607. /// <summary>
  608. /// Removing a PropertyType from the associated Tab
  609. /// </summary>
  610. /// <param name="pt">The PropertyType which should be freed from its tab</param>
  611. public void removePropertyTypeFromTab(PropertyType pt)
  612. {
  613. pt.TabId = 0; //this will set to null in the database.
  614. // Remove from cache
  615. FlushFromCache(Id);
  616. }
  617. /// <summary>
  618. /// Creates a new Tab on the Content
  619. /// </summary>
  620. /// <param name="Caption">Returns the Id of the new Tab</param>
  621. /// <returns></returns>
  622. [MethodImpl(MethodImplOptions.Synchronized)]
  623. public int AddVirtualTab(string Caption)
  624. {
  625. // Get tab count
  626. int tabCount = SqlHelper.ExecuteScalar<int>("SELECT COUNT(*) FROM cmsTab WHERE contenttypeNodeId = @nodeId",
  627. SqlHelper.CreateParameter("@nodeId", Id));
  628. // The method is synchronized
  629. SqlHelper.ExecuteNonQuery("INSERT INTO cmsTab (contenttypeNodeId,text,sortorder) VALUES (@nodeId,@text,@sortorder)",
  630. SqlHelper.CreateParameter("@nodeId", Id),
  631. SqlHelper.CreateParameter("@text", Caption),
  632. SqlHelper.CreateParameter("@sortorder", tabCount + 1));
  633. // Remove from cache
  634. FlushFromCache(Id);
  635. return SqlHelper.ExecuteScalar<int>("SELECT MAX(id) FROM cmsTab");
  636. }
  637. /// <summary>
  638. /// Releases all PropertyTypes on tab (this does not delete the PropertyTypes) and then Deletes the Tab
  639. /// </summary>
  640. /// <param name="id">The Id of the Tab to be deleted.</param>
  641. public void DeleteVirtualTab(int id)
  642. {
  643. //set each property on the tab to have a tab id of zero
  644. // zb-00036 #29889 : fix property types getter
  645. this.getVirtualTabs.ToList()
  646. .Where(x => x.Id == id)
  647. .Single()
  648. .GetAllPropertyTypes()
  649. .ForEach(x =>
  650. {
  651. x.TabId = 0;
  652. });
  653. SqlHelper.ExecuteNonQuery("delete from cmsTab where id =" + id);
  654. // Remove from cache
  655. FlushFromCache(Id);
  656. }
  657. /// <summary>
  658. /// Updates the caption of the Tab
  659. /// </summary>
  660. /// <param name="tabId">The Id of the Tab to be updated</param>
  661. /// <param name="Caption">The new Caption</param>
  662. public void SetTabName(int tabId, string Caption)
  663. {
  664. SqlHelper.ExecuteNonQuery(
  665. "Update cmsTab set text = '" + Caption + "' where id = " + tabId);
  666. // Remove from cache
  667. FlushFromCache(Id);
  668. }
  669. /// <summary>
  670. /// Updates the sort order of the Tab
  671. /// </summary>
  672. /// <param name="tabId">The Id of the Tab to be updated</param>
  673. /// <param name="Caption">The new order number</param>
  674. public void SetTabSortOrder(int tabId, int sortOrder)
  675. {
  676. SqlHelper.ExecuteNonQuery(
  677. "Update cmsTab set sortOrder = " + sortOrder + " where id = " + tabId);
  678. // Remove from cache
  679. FlushFromCache(Id);
  680. }
  681. /// <summary>
  682. /// Retrieve a PropertyType by it's alias
  683. /// </summary>
  684. /// <param name="alias">PropertyType alias</param>
  685. /// <returns>The PropertyType with the given Alias</returns>
  686. public PropertyType getPropertyType(string alias)
  687. {
  688. // NH 22-08-08, Get from the property type stack to ensure support of master document types
  689. object o = this.PropertyTypes.Find(delegate(PropertyType pt) { return pt.Alias == alias; });
  690. //object o = SqlHelper.ExecuteScalar<object>(
  691. // "Select id from cmsPropertyType where contentTypeId=@contentTypeId And Alias=@alias",
  692. // SqlHelper.CreateParameter("@contentTypeId", this.Id),
  693. // SqlHelper.CreateParameter("@alias", alias));
  694. if (o == null)
  695. {
  696. return null;
  697. }
  698. else
  699. {
  700. return (PropertyType)o;
  701. }
  702. //int propertyTypeId;
  703. //if (!int.TryParse(o.ToString(), out propertyTypeId))
  704. // return null;
  705. //return PropertyType.GetPropertyType(propertyTypeId);
  706. }
  707. /// <summary>
  708. /// Deletes the current ContentType
  709. /// </summary>
  710. public override void delete()
  711. {
  712. // Remove from cache
  713. FlushFromCache(Id);
  714. // Delete all propertyTypes
  715. foreach (PropertyType pt in PropertyTypes)
  716. {
  717. if (pt.ContentTypeId == this.Id)
  718. {
  719. pt.delete();
  720. }
  721. }
  722. // delete all tabs
  723. foreach (Tab t in getVirtualTabs.ToList())
  724. {
  725. if (t.ContentType == this.Id)
  726. {
  727. t.Delete();
  728. }
  729. }
  730. //need to delete the allowed relationships between content types
  731. SqlHelper.ExecuteNonQuery("delete from cmsContentTypeAllowedContentType where AllowedId=@allowedId or Id=@id",
  732. SqlHelper.CreateParameter("@allowedId", Id),
  733. SqlHelper.CreateParameter("@id", Id));
  734. // delete contenttype entrance
  735. SqlHelper.ExecuteNonQuery("Delete from cmsContentType where NodeId = " + Id);
  736. // delete CMSNode entrance
  737. base.delete();
  738. }
  739. #endregion
  740. #region Protected Methods
  741. protected void PopulateContentTypeNodeFromReader(IRecordsReader dr)
  742. {
  743. _alias = dr.GetString("Alias");
  744. _iconurl = dr.GetString("icon");
  745. if (!dr.IsNull("masterContentType"))
  746. m_masterContentType = dr.GetInt("masterContentType");
  747. if (!dr.IsNull("thumbnail"))
  748. _thumbnail = dr.GetString("thumbnail");
  749. if (!dr.IsNull("description"))
  750. _description = dr.GetString("description");
  751. }
  752. /// <summary>
  753. /// Set up the internal data of the ContentType
  754. /// </summary>
  755. protected override void setupNode()
  756. {
  757. base.setupNode();
  758. using (IRecordsReader dr =
  759. SqlHelper.ExecuteReader("Select masterContentType,Alias,icon,thumbnail,description from cmsContentType where nodeid=" + Id)
  760. )
  761. {
  762. if (dr.Read())
  763. {
  764. PopulateContentTypeNodeFromReader(dr);
  765. }
  766. else
  767. {
  768. throw new ArgumentException("No Contenttype with id: " + Id);
  769. }
  770. }
  771. }
  772. /// <summary>
  773. /// Set up the internal data of the ContentType
  774. /// </summary>
  775. [Obsolete("Use the overriden setupNode method instead. This method now calls that method")]
  776. protected void setupContentType()
  777. {
  778. setupNode();
  779. }
  780. /// <summary>
  781. /// Flushes the cache.
  782. /// </summary>
  783. /// <param name="Id">The id.</param>
  784. public static void FlushFromCache(int id)
  785. {
  786. ContentType ct = new ContentType(id);
  787. Cache.ClearCacheItem(string.Format("UmbracoContentType{0}", id));
  788. Cache.ClearCacheItem(ct.GetPropertiesCacheKey());
  789. ct.ClearVirtualTabs();
  790. //clear the content type from the property datatype cache used by razor
  791. RemoveFromDataTypeCache(ct.Alias);
  792. // clear anything that uses this as master content type
  793. if (ct.nodeObjectType == DocumentType._objectType)
  794. {
  795. List<DocumentType> cacheToFlush = DocumentType.GetAllAsList().FindAll(dt => dt.MasterContentType == id);
  796. foreach (DocumentType dt in cacheToFlush)
  797. FlushFromCache(dt.Id);
  798. }
  799. }
  800. protected internal void FlushAllFromCache()
  801. {
  802. Cache.ClearCacheByKeySearch("UmbracoContentType");
  803. Cache.ClearCacheByKeySearch("ContentType_PropertyTypes_Content");
  804. //clear the property datatype cache used by razor
  805. _propertyTypeCache = new Dictionary<Tuple<string, string>, Guid>();
  806. ClearVirtualTabs();
  807. }
  808. #endregion
  809. #region Private Methods
  810. /// <summary>
  811. /// The cache key used to cache the properties for the content type
  812. /// </summary>
  813. /// <returns></returns>
  814. private string GetPropertiesCacheKey()
  815. {
  816. return "ContentType_PropertyTypes_Content:" + this.Id;
  817. }
  818. /// <summary>
  819. /// Checks if we've loaded the virtual tabs into memory and if not gets them from the databse.
  820. /// </summary>
  821. private void EnsureVirtualTabs()
  822. {
  823. //optimize, lazy load the data only one time
  824. if (m_VirtualTabs == null || m_VirtualTabs.Count == 0)
  825. {
  826. InitializeVirtualTabs();
  827. }
  828. }
  829. /// <summary>
  830. /// Loads the tabs into memory from the database and stores them in a local list for retreival
  831. /// </summary>
  832. private void InitializeVirtualTabs()
  833. {
  834. m_VirtualTabs = new List<TabI>();
  835. using (IRecordsReader dr = SqlHelper.ExecuteReader(
  836. string.Format(
  837. "Select Id,text,sortOrder from cmsTab where contenttypeNodeId = {0} order by sortOrder",
  838. Id)))
  839. {
  840. while (dr.Read())
  841. {
  842. m_VirtualTabs.Add(new Tab(dr.GetInt("id"), dr.GetString("text"), dr.GetInt("sortOrder"), this));
  843. }
  844. }
  845. // Master Content Type
  846. if (MasterContentType != 0)
  847. {
  848. foreach (TabI t in ContentType.GetContentType(MasterContentType).getVirtualTabs.ToList())
  849. {
  850. m_VirtualTabs.Add(t);
  851. }
  852. }
  853. // sort all tabs
  854. m_VirtualTabs.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder));
  855. }
  856. private void populateMasterContentTypes(PropertyType pt, int docTypeId)
  857. {
  858. foreach (web.DocumentType docType in web.DocumentType.GetAllAsList())
  859. {
  860. if (docType.MasterContentType == docTypeId)
  861. {
  862. populatePropertyData(pt, docType.Id);
  863. populateMasterContentTypes(pt, docType.Id);
  864. }
  865. }
  866. }
  867. private void populatePropertyData(PropertyType pt, int contentTypeId)
  868. {
  869. // NH: PropertyTypeId inserted directly into SQL instead of as a parameter for SQL CE 4 compatibility
  870. SqlHelper.ExecuteNonQuery(
  871. "insert into cmsPropertyData (contentNodeId, versionId, propertyTypeId) select contentId, versionId, " + pt.Id + " from cmsContent inner join cmsContentVersion on cmsContent.nodeId = cmsContentVersion.contentId where contentType = @contentTypeId",
  872. SqlHelper.CreateParameter("@contentTypeId", contentTypeId));
  873. }
  874. #endregion
  875. #region Public TabI Interface
  876. /// <summary>
  877. /// An interface for the tabs, should be refactored
  878. /// </summary>
  879. public interface TabI
  880. {
  881. /// <summary>
  882. /// Public identifier
  883. /// </summary>
  884. int Id { get; }
  885. /// <summary>
  886. /// The text on the tab
  887. /// </summary>
  888. string Caption { get; }
  889. /// <summary>
  890. /// The sortorder of the tab
  891. /// </summary>
  892. int SortOrder { get; }
  893. // zb-00036 #29889 : removed PropertyTypes property (not making sense), replaced with methods
  894. /// <summary>
  895. /// Gets a list of all PropertyTypes on the Tab for a given ContentType.
  896. /// </summary>
  897. /// <remarks>This includes properties inherited from master content types.</remarks>
  898. /// <param name="contentTypeId">The unique identifier of the ContentType.</param>
  899. /// <returns>An array of PropertyType.</returns>
  900. PropertyType[] GetPropertyTypes(int contentTypeId);
  901. [Obsolete("Please use GetPropertyTypes() instead", false)]
  902. PropertyType[] PropertyTypes { get; }
  903. /// <summary>
  904. /// Gets a list of all PropertyTypes on the Tab for a given ContentType.
  905. /// </summary>
  906. /// <param name="contentTypeId">The unique identifier of the ContentType.</param>
  907. /// <param name="includeInheritedProperties">Indicates whether properties inherited from master content types should be included.</param>
  908. /// <returns>An array of PropertyType.</returns>
  909. PropertyType[] GetPropertyTypes(int contentTypeId, bool includeInheritedProperties);
  910. /// <summary>
  911. /// Gets a list if all PropertyTypes on the Tab for all ContentTypes.
  912. /// </summary>
  913. /// <returns>An IEnumerable of all the PropertyTypes.</returns>
  914. List<PropertyType> GetAllPropertyTypes();
  915. /// <summary>
  916. /// The contenttype
  917. /// </summary>
  918. int ContentType { get; }
  919. /// <summary>
  920. /// Method for moving the tab up
  921. /// </summary>
  922. void MoveUp();
  923. /// <summary>
  924. /// Method for retrieving the original, non processed name from the db
  925. /// </summary>
  926. /// <returns>The original, non processed name from the db</returns>
  927. string GetRawCaption();
  928. /// <summary>
  929. /// Method for moving the tab down
  930. /// </summary>
  931. void MoveDown();
  932. }
  933. #endregion
  934. #region Protected Tab Class
  935. /// <summary>
  936. /// A tab is merely a way to organize data on a ContentType to make it more
  937. /// human friendly
  938. /// </summary>
  939. public class Tab : TabI
  940. {
  941. private ContentType _contenttype;
  942. private static object propertyTypesCacheSyncLock = new object();
  943. public static readonly string CacheKey = "Tab_PropertyTypes_Content:";
  944. /// <summary>
  945. /// Initializes a new instance of the <see cref="Tab"/> class.
  946. /// </summary>
  947. /// <param name="id">The id.</param>
  948. /// <param name="caption">The caption.</param>
  949. /// <param name="sortOrder">The sort order.</param>
  950. /// <param name="cType">Type of the c.</param>
  951. public Tab(int id, string caption, int sortOrder, ContentType cType)
  952. {
  953. _id = id;
  954. _caption = caption;
  955. _sortOrder = sortOrder;
  956. _contenttype = cType;
  957. }
  958. public static Tab GetTab(int id)
  959. {
  960. Tab tab = null;
  961. using (IRecordsReader dr = SqlHelper.ExecuteReader(
  962. string.Format(
  963. "Select Id, text, contenttypeNodeId, sortOrder from cmsTab where Id = {0} order by sortOrder",
  964. id)))
  965. {
  966. if (dr.Read())
  967. {
  968. tab = new Tab(id, dr.GetString("text"), dr.GetInt("sortOrder"), new ContentType(dr.GetInt("contenttypeNodeId")));
  969. }
  970. dr.Close();
  971. }
  972. return tab;
  973. }
  974. // zb-00036 #29889 : Fix content tab properties.
  975. public PropertyType[] GetPropertyTypes(int contentTypeId)
  976. {
  977. return GetPropertyTypes(contentTypeId, true);
  978. }
  979. // zb-00036 #29889 : Fix content tab properties. Replaces getPropertyTypes, which was
  980. // loading all properties for the tab, causing exceptions here and there... we want
  981. // the properties for the tab for a given content type, and we want them in the right order.
  982. // Also this is public now because we removed the PropertyTypes property (not making sense).
  983. public PropertyType[] GetPropertyTypes(int contentTypeId, bool includeInheritedProperties)
  984. {
  985. // zb-00040 #29889 : fix cache key issues!
  986. // now maintaining a cache of local properties per contentTypeId, then merging when required
  987. // another way would be to maintain a cache of *all* properties, then filter when required
  988. // however it makes it more difficult to figure out what to clear (ie needs to find all children...)
  989. var tmp = new List<PropertyType>();
  990. var ctypes = new List<int>();
  991. if (includeInheritedProperties)
  992. {
  993. // start from contentTypeId and list all ctypes, going up
  994. int c = contentTypeId;
  995. while (c != 0)
  996. {
  997. ctypes.Add(c);
  998. c = umbraco.cms.businesslogic.ContentType.GetContentType(c).MasterContentType;
  999. }
  1000. ctypes.Reverse(); // order from the top
  1001. }
  1002. else
  1003. {
  1004. // just that one
  1005. ctypes.Add(contentTypeId);
  1006. }
  1007. foreach (var ctype in ctypes)
  1008. {
  1009. var ptypes = Cache.GetCacheItem<List<PropertyType>>(
  1010. generateCacheKey(Id, ctype), propertyTypesCacheSyncLock, TimeSpan.FromMinutes(10),
  1011. delegate
  1012. {
  1013. var tmp1 = new List<PropertyType>();
  1014. using (IRecordsReader dr = SqlHelper.ExecuteReader(string.Format(
  1015. @"select id from cmsPropertyType where tabId = {0} and contentTypeId = {1}
  1016. order by sortOrder", _id, ctype)))
  1017. {
  1018. while (dr.Read())
  1019. tmp1.Add(PropertyType.GetPropertyType(dr.GetInt("id")));
  1020. }
  1021. return tmp1;
  1022. });
  1023. tmp.AddRange(ptypes);
  1024. }
  1025. return tmp.ToArray();
  1026. }
  1027. // zb-00036 #29889 : yet we may want to be able to get *all* property types
  1028. public List<PropertyType> GetAllPropertyTypes()
  1029. {
  1030. var tmp = new List<PropertyType>();
  1031. using (IRecordsReader dr = SqlHelper.ExecuteReader(string.Format(
  1032. @"select id from cmsPropertyType where tabId = {0}", _id)))
  1033. {
  1034. while (dr.Read())
  1035. tmp.Add(PropertyType.GetPropertyType(dr.GetInt("id")));
  1036. }
  1037. return tmp;
  1038. }
  1039. /// <summary>
  1040. /// A list of PropertyTypes on the Tab
  1041. /// </summary>
  1042. [Obsolete("Please use GetPropertyTypes() instead", false)]
  1043. public PropertyType[] PropertyTypes
  1044. {
  1045. get { return GetPropertyTypes(this.ContentType, true); }
  1046. }
  1047. /// <summary>
  1048. /// Flushes the cache.
  1049. /// </summary>
  1050. /// <param name="id">The id.</param>
  1051. /// <param name="contentTypeId"></param>
  1052. public static void FlushCache(int id, int contentTypeId)
  1053. {
  1054. Cache.ClearCacheItem(generateCacheKey(id, contentTypeId));
  1055. }
  1056. private static string generateCacheKey(int tabId, int contentTypeId)
  1057. {
  1058. return String.Format("{0}_{1}_{2}", CacheKey, tabId, contentTypeId);
  1059. }
  1060. /// <summary>
  1061. /// Deletes this instance.
  1062. /// </summary>
  1063. public void Delete()
  1064. {
  1065. SqlHelper.ExecuteNonQuery("update cmsPropertyType set tabId = NULL where tabid = @id",
  1066. SqlHelper.CreateParameter("@id", Id));
  1067. SqlHelper.ExecuteNonQuery("delete from cmsTab where id = @id",
  1068. SqlHelper.CreateParameter("@id", Id));
  1069. }
  1070. /// <summary>
  1071. /// Gets the tab caption by id.
  1072. /// </summary>
  1073. /// <param name="id">The id.</param>
  1074. /// <returns></returns>
  1075. public static string GetCaptionById(int id)
  1076. {
  1077. try
  1078. {
  1079. string tempCaption = SqlHelper.ExecuteScalar<string>("Select text from cmsTab where id = " + id.ToString());
  1080. if (!tempCaption.StartsWith("#"))
  1081. return tempCaption;
  1082. else
  1083. {
  1084. Language lang =
  1085. Language.GetByCultureCode(Thread.CurrentThread.CurrentCulture.Name);
  1086. if (lang != null)
  1087. return
  1088. new Dictionary.DictionaryItem(tempCaption.Substring(1, tempCaption.Length - 1)).Value(
  1089. lang.id);
  1090. else
  1091. return "[" + tempCaption + "]";
  1092. }
  1093. }
  1094. catch
  1095. {
  1096. return null;
  1097. }
  1098. }
  1099. private int _id;
  1100. private int? _sortOrder = null;