Files
Umbraco-CMS/src/Umbraco.Core/Configuration/GlobalSettings.cs

770 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Configuration;
using System.Web.Routing;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Configuration
{
//NOTE: Do not expose this class ever until we cleanup all configuration including removal of static classes, etc...
// we have this two tasks logged:
// http://issues.umbraco.org/issue/U4-58
// http://issues.umbraco.org/issue/U4-115
//TODO: Replace checking for if the app settings exist and returning an empty string, instead return the defaults!
/// <summary>
/// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings
/// </summary>
internal class GlobalSettings
{
#region Private static fields
private static Version _version;
private static string _reservedUrlsCache;
private static string _reservedPathsCache;
private static StartsWithContainer _reservedList = new StartsWithContainer();
#endregion
/// <summary>
/// Gets the reserved urls from web.config.
/// </summary>
/// <value>The reserved urls.</value>
public static string ReservedUrls
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoReservedUrls")
? ConfigurationManager.AppSettings["umbracoReservedUrls"]
: string.Empty;
}
}
/// <summary>
/// Gets the reserved paths from web.config
/// </summary>
/// <value>The reserved paths.</value>
public static string ReservedPaths
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoReservedPaths")
? ConfigurationManager.AppSettings["umbracoReservedPaths"]
: string.Empty;
}
}
/// <summary>
/// Gets the name of the content XML file.
/// </summary>
/// <value>The content XML.</value>
public static string ContentXmlFile
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoContentXML")
? ConfigurationManager.AppSettings["umbracoContentXML"]
: string.Empty;
}
}
/// <summary>
/// Gets the path to the storage directory (/data by default).
/// </summary>
/// <value>The storage directory.</value>
public static string StorageDirectory
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoStorageDirectory")
? ConfigurationManager.AppSettings["umbracoStorageDirectory"]
: string.Empty;
}
}
/// <summary>
/// Gets the path to umbraco's root directory (/umbraco by default).
/// </summary>
/// <value>The path.</value>
public static string Path
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoPath")
? IOHelper.ResolveUrl(ConfigurationManager.AppSettings["umbracoPath"])
: string.Empty;
}
}
/// <summary>
/// This returns the string of the MVC Area route.
/// </summary>
/// <remarks>
/// THIS IS TEMPORARY AND SHOULD BE REMOVED WHEN WE MIGRATE/UPDATE THE CONFIG SETTINGS TO BE A REAL CONFIG SECTION
/// AND SHOULD PROBABLY BE HANDLED IN A MORE ROBUST WAY.
///
/// This will return the MVC area that we will route all custom routes through like surface controllers, etc...
/// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a path of ~/asdf/asdf/admin
/// we will convert the '/' to '-' and use that as the path. its a bit lame but will work.
/// </remarks>
internal static string UmbracoMvcArea
{
get
{
if (Path.IsNullOrWhiteSpace())
{
throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified");
}
return Path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim();
}
}
/// <summary>
/// Gets the path to umbraco's client directory (/umbraco_client by default).
/// This is a relative path to the Umbraco Path as it always must exist beside the 'umbraco'
/// folder since the CSS paths to images depend on it.
/// </summary>
/// <value>The path.</value>
public static string ClientPath
{
get
{
return Path + "/../umbraco_client";
}
}
/// <summary>
/// Gets the database connection string
/// </summary>
/// <value>The database connection string.</value>
[Obsolete("Use System.ConfigurationManager.ConnectionStrings to get the connection with the key Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName instead")]
public static string DbDsn
{
get
{
var settings = ConfigurationManager.ConnectionStrings[UmbracoConnectionName];
var connectionString = string.Empty;
if (settings != null)
{
connectionString = settings.ConnectionString;
// The SqlCe connectionString is formatted slightly differently, so we need to updat it
if (settings.ProviderName.Contains("SqlServerCe"))
connectionString = string.Format("datalayer=SQLCE4Umbraco.SqlCEHelper,SQLCE4Umbraco;{0}", connectionString);
}
return connectionString;
}
set
{
if (DbDsn != value)
{
if (value.ToLower().Contains("SQLCE4Umbraco.SqlCEHelper".ToLower()))
{
ApplicationContext.Current.DatabaseContext.ConfigureDatabaseConnection();
}
else
{
ApplicationContext.Current.DatabaseContext.ConfigureDatabaseConnection(value);
}
}
}
}
public const string UmbracoConnectionName = "umbracoDbDSN";
public const string UmbracoMigrationName = "Umbraco";
/// <summary>
/// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance.
/// </summary>
/// <value>The configuration status.</value>
public static string ConfigurationStatus
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoConfigurationStatus")
? ConfigurationManager.AppSettings["umbracoConfigurationStatus"]
: string.Empty;
}
set
{
SaveSetting("umbracoConfigurationStatus", value);
}
}
/// <summary>
/// Saves a setting into the configuration file.
/// </summary>
/// <param name="key">Key of the setting to be saved.</param>
/// <param name="value">Value of the setting to be saved.</param>
internal static void SaveSetting(string key, string value)
{
var fileName = GetFullWebConfigFileName();
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
var appSettings = xml.Root.Descendants("appSettings").Single();
// Update appSetting if it exists, or else create a new appSetting for the given key and value
var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key);
if (setting == null)
appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", value)));
else
setting.Attribute("value").Value = value;
xml.Save(fileName, SaveOptions.DisableFormatting);
ConfigurationManager.RefreshSection("appSettings");
}
/// <summary>
/// Removes a setting from the configuration file.
/// </summary>
/// <param name="key">Key of the setting to be removed.</param>
internal static void RemoveSetting(string key)
{
var fileName = GetFullWebConfigFileName();
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
var appSettings = xml.Root.Descendants("appSettings").Single();
var setting = appSettings.Descendants("add").FirstOrDefault(s => s.Attribute("key").Value == key);
if (setting != null)
{
setting.Remove();
xml.Save(fileName, SaveOptions.DisableFormatting);
ConfigurationManager.RefreshSection("appSettings");
}
}
private static string GetFullWebConfigFileName()
{
var webConfig = new WebConfigurationFileMap();
var vDir = FullpathToRoot;
foreach (VirtualDirectoryMapping v in webConfig.VirtualDirectories)
{
if (v.IsAppRoot)
vDir = v.PhysicalDirectory;
}
var fileName = System.IO.Path.Combine(vDir, "web.config");
return fileName;
}
/// <summary>
/// Gets the full path to root.
/// </summary>
/// <value>The fullpath to root.</value>
public static string FullpathToRoot
{
get { return IOHelper.GetRootDirectorySafe(); }
}
/// <summary>
/// Gets a value indicating whether umbraco is running in [debug mode].
/// </summary>
/// <value><c>true</c> if [debug mode]; otherwise, <c>false</c>.</value>
public static bool DebugMode
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoDebugMode"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets a value indicating whether the current version of umbraco is configured.
/// </summary>
/// <value><c>true</c> if configured; otherwise, <c>false</c>.</value>
public static bool Configured
{
get
{
try
{
string configStatus = ConfigurationStatus;
string currentVersion = UmbracoVersion.Current.ToString(3);
if (currentVersion != configStatus)
{
LogHelper.Debug<GlobalSettings>("CurrentVersion different from configStatus: '" + currentVersion + "','" + configStatus + "'");
}
return (configStatus == currentVersion);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets the time out in minutes.
/// </summary>
/// <value>The time out in minutes.</value>
public static int TimeOutInMinutes
{
get
{
try
{
return int.Parse(ConfigurationManager.AppSettings["umbracoTimeOutInMinutes"]);
}
catch
{
return 20;
}
}
}
/// <summary>
/// Gets a value indicating whether umbraco uses directory urls.
/// </summary>
/// <value><c>true</c> if umbraco uses directory urls; otherwise, <c>false</c>.</value>
public static bool UseDirectoryUrls
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoUseDirectoryUrls"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Returns a string value to determine if umbraco should skip version-checking.
/// </summary>
/// <value>The version check period in days (0 = never).</value>
public static int VersionCheckPeriod
{
get
{
try
{
return int.Parse(ConfigurationManager.AppSettings["umbracoVersionCheckPeriod"]);
}
catch
{
return 7;
}
}
}
/// <summary>
/// Returns a string value to determine if umbraco should disbable xslt extensions
/// </summary>
/// <value><c>"true"</c> if version xslt extensions are disabled, otherwise, <c>"false"</c></value>
public static string DisableXsltExtensions
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoDisableXsltExtensions")
? ConfigurationManager.AppSettings["umbracoDisableXsltExtensions"]
: string.Empty;
}
}
/// <summary>
/// Returns a string value to determine if umbraco should use Xhtml editing mode in the wysiwyg editor
/// </summary>
/// <value><c>"true"</c> if Xhtml mode is enable, otherwise, <c>"false"</c></value>
public static string EditXhtmlMode
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoEditXhtmlMode")
? ConfigurationManager.AppSettings["umbracoEditXhtmlMode"]
: string.Empty;
}
}
/// <summary>
/// Gets the default UI language.
/// </summary>
/// <value>The default UI language.</value>
public static string DefaultUILanguage
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoDefaultUILanguage")
? ConfigurationManager.AppSettings["umbracoDefaultUILanguage"]
: string.Empty;
}
}
/// <summary>
/// Gets the profile URL.
/// </summary>
/// <value>The profile URL.</value>
public static string ProfileUrl
{
get
{
return ConfigurationManager.AppSettings.ContainsKey("umbracoProfileUrl")
? ConfigurationManager.AppSettings["umbracoProfileUrl"]
: string.Empty;
}
}
/// <summary>
/// Gets a value indicating whether umbraco should hide top level nodes from generated urls.
/// </summary>
/// <value>
/// <c>true</c> if umbraco hides top level nodes from urls; otherwise, <c>false</c>.
/// </value>
public static bool HideTopLevelNodeFromPath
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoHideTopLevelNodeFromPath"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets the current version.
/// </summary>
/// <value>The current version.</value>
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
public static string CurrentVersion
{
get
{
return UmbracoVersion.Current.ToString(3);
}
}
/// <summary>
/// Gets the major version number.
/// </summary>
/// <value>The major version number.</value>
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
public static int VersionMajor
{
get
{
return UmbracoVersion.Current.Major;
}
}
/// <summary>
/// Gets the minor version number.
/// </summary>
/// <value>The minor version number.</value>
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
public static int VersionMinor
{
get
{
return UmbracoVersion.Current.Minor;
}
}
/// <summary>
/// Gets the patch version number.
/// </summary>
/// <value>The patch version number.</value>
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
public static int VersionPatch
{
get
{
return UmbracoVersion.Current.Build;
}
}
/// <summary>
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
[Obsolete("Use Umbraco.Core.Configuration.UmbracoVersion.Current instead", false)]
public static string VersionComment
{
get
{
return Umbraco.Core.Configuration.UmbracoVersion.CurrentComment;
}
}
/// <summary>
/// Requests the is in umbraco application directory structure.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
public static bool RequestIsInUmbracoApplication(HttpContext context)
{
return context.Request.Path.ToLower().IndexOf(IOHelper.ResolveUrl(SystemDirectories.Umbraco).ToLower()) > -1;
}
public static bool RequestIsLiveEditRedirector(HttpContext context)
{
return context.Request.Path.ToLower().IndexOf(SystemDirectories.Umbraco.ToLower() + "/liveediting.aspx") > -1;
}
/// <summary>
/// Gets a value indicating whether umbraco should force a secure (https) connection to the backoffice.
/// </summary>
/// <value><c>true</c> if [use SSL]; otherwise, <c>false</c>.</value>
public static bool UseSSL
{
get
{
try
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoUseSSL"]);
}
catch
{
return false;
}
}
}
/// <summary>
/// Gets the umbraco license.
/// </summary>
/// <value>The license.</value>
public static string License
{
get
{
string license =
"<A href=\"http://umbraco.org/redir/license\" target=\"_blank\">the open source license MIT</A>. The umbraco UI is freeware licensed under the umbraco license.";
var versionDoc = new XmlDocument();
var versionReader = new XmlTextReader(IOHelper.MapPath(SystemDirectories.Umbraco + "/version.xml"));
versionDoc.Load(versionReader);
versionReader.Close();
// check for license
try
{
string licenseUrl =
versionDoc.SelectSingleNode("/version/licensing/licenseUrl").FirstChild.Value;
string licenseValidation =
versionDoc.SelectSingleNode("/version/licensing/licenseValidation").FirstChild.Value;
string licensedTo =
versionDoc.SelectSingleNode("/version/licensing/licensedTo").FirstChild.Value;
if (licensedTo != "" && licenseUrl != "")
{
license = "umbraco Commercial License<br/><b>Registered to:</b><br/>" +
licensedTo.Replace("\n", "<br/>") + "<br/><b>For use with domain:</b><br/>" +
licenseUrl;
}
}
catch
{
}
return license;
}
}
/// <summary>
/// Determines whether the current request is reserved based on the route table and
/// whether the specified URL is reserved or is inside a reserved path.
/// </summary>
/// <param name="url"></param>
/// <param name="httpContext"></param>
/// <param name="routes">The route collection to lookup the request in</param>
/// <returns></returns>
public static bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
if (routes == null) throw new ArgumentNullException("routes");
//check if the current request matches a route, if so then it is reserved.
var route = routes.GetRouteData(httpContext);
if (route != null)
return true;
//continue with the standard ignore routine
return IsReservedPathOrUrl(url);
}
/// <summary>
/// Determines whether the specified URL is reserved or is inside a reserved path.
/// </summary>
/// <param name="url">The URL to check.</param>
/// <returns>
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
/// </returns>
public static bool IsReservedPathOrUrl(string url)
{
// check if GlobalSettings.ReservedPaths and GlobalSettings.ReservedUrls are unchanged
if (!object.ReferenceEquals(_reservedPathsCache, GlobalSettings.ReservedPaths)
|| !object.ReferenceEquals(_reservedUrlsCache, GlobalSettings.ReservedUrls))
{
// store references to strings to determine changes
_reservedPathsCache = GlobalSettings.ReservedPaths;
_reservedUrlsCache = GlobalSettings.ReservedUrls;
string _root = SystemDirectories.Root.Trim().ToLower();
// add URLs and paths to a new list
StartsWithContainer _newReservedList = new StartsWithContainer();
foreach (string reservedUrl in _reservedUrlsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries))
{
//resolves the url to support tilde chars
string reservedUrlTrimmed = IOHelper.ResolveUrl(reservedUrl).Trim().ToLower();
if (reservedUrlTrimmed.Length > 0)
_newReservedList.Add(reservedUrlTrimmed);
}
foreach (string reservedPath in _reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries))
{
bool trimEnd = !reservedPath.EndsWith("/");
//resolves the url to support tilde chars
string reservedPathTrimmed = IOHelper.ResolveUrl(reservedPath).Trim().ToLower();
if (reservedPathTrimmed.Length > 0)
_newReservedList.Add(reservedPathTrimmed + (reservedPathTrimmed.EndsWith("/") ? "" : "/"));
}
// use the new list from now on
_reservedList = _newReservedList;
}
//The url should be cleaned up before checking:
// * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
// * We shouldn't be comparing the query at all
var pathPart = url.Split('?')[0];
if (!pathPart.Contains(".") && !pathPart.EndsWith("/"))
{
pathPart += "/";
}
// return true if url starts with an element of the reserved list
return _reservedList.StartsWith(pathPart.ToLowerInvariant());
}
/// <summary>
/// Structure that checks in logarithmic time
/// if a given string starts with one of the added keys.
/// </summary>
private class StartsWithContainer
{
/// <summary>Internal sorted list of keys.</summary>
public SortedList<string, string> _list
= new SortedList<string, string>(StartsWithComparator.Instance);
/// <summary>
/// Adds the specified new key.
/// </summary>
/// <param name="newKey">The new key.</param>
public void Add(string newKey)
{
// if the list already contains an element that begins with newKey, return
if (String.IsNullOrEmpty(newKey) || StartsWith(newKey))
return;
// create a new collection, so the old one can still be accessed
SortedList<string, string> newList
= new SortedList<string, string>(_list.Count + 1, StartsWithComparator.Instance);
// add only keys that don't already start with newKey, others are unnecessary
foreach (string key in _list.Keys)
if (!key.StartsWith(newKey))
newList.Add(key, null);
// add the new key
newList.Add(newKey, null);
// update the list (thread safe, _list was never in incomplete state)
_list = newList;
}
/// <summary>
/// Checks if the given string starts with any of the added keys.
/// </summary>
/// <param name="target">The target.</param>
/// <returns>true if a key is found that matches the start of target</returns>
/// <remarks>
/// Runs in O(s*log(n)), with n the number of keys and s the length of target.
/// </remarks>
public bool StartsWith(string target)
{
return _list.ContainsKey(target);
}
/// <summary>Comparator that tests if a string starts with another.</summary>
/// <remarks>Not a real comparator, since it is not reflexive. (x==y does not imply y==x)</remarks>
private sealed class StartsWithComparator : IComparer<string>
{
/// <summary>Default string comparer.</summary>
private readonly static Comparer<string> _stringComparer = Comparer<string>.Default;
/// <summary>Gets an instance of the StartsWithComparator.</summary>
public static readonly StartsWithComparator Instance = new StartsWithComparator();
/// <summary>
/// Tests if whole begins with all characters of part.
/// </summary>
/// <param name="part">The part.</param>
/// <param name="whole">The whole.</param>
/// <returns>
/// Returns 0 if whole starts with part, otherwise performs standard string comparison.
/// </returns>
public int Compare(string part, string whole)
{
// let the default string comparer deal with null or when part is not smaller then whole
if (part == null || whole == null || part.Length >= whole.Length)
return _stringComparer.Compare(part, whole);
////ensure both have a / on the end
//part = part.EndsWith("/") ? part : part + "/";
//whole = whole.EndsWith("/") ? whole : whole + "/";
//if (part.Length >= whole.Length)
// return _stringComparer.Compare(part, whole);
// loop through all characters that part and whole have in common
int pos = 0;
bool match;
do
{
match = (part[pos] == whole[pos]);
} while (match && ++pos < part.Length);
// return result of last comparison
return match ? 0 : (part[pos] < whole[pos] ? -1 : 1);
}
}
}
}
}