Files
Umbraco-CMS/src/Umbraco.Web/Editors/BackOfficeController.cs
Nicholas-Westby db824d8daf Fixes U4-6665 (Localization Bug)
Fix for localization not working some of the time: http://issues.umbraco.org/issue/U4-6665
2015-05-29 21:02:07 -07:00

512 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using System.Web.UI;
using dotless.Core.Parser.Tree;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Manifest;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Security;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.Trees;
using Umbraco.Web.UI.JavaScript;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.Models;
using Umbraco.Web.WebServices;
using Umbraco.Web.WebApi.Filters;
namespace Umbraco.Web.Editors
{
/// <summary>
/// A controller to render out the default back office view and JS results
/// </summary>
[UmbracoUseHttps]
[DisableClientCache]
public class BackOfficeController : UmbracoController
{
/// <summary>
/// Render the default view
/// </summary>
/// <returns></returns>
public ActionResult Default()
{
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml");
}
/// <summary>
/// This Action is used by the installer when an upgrade is detected but the admin user is not logged in. We need to
/// ensure the user is authenticated before the install takes place so we redirect here to show the standard login screen.
/// </summary>
/// <returns></returns>
[HttpGet]
public ActionResult AuthorizeUpgrade()
{
return View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml");
}
/// <summary>
/// Get the json localized text for a given culture or the culture for the current user
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
[HttpGet]
public JsonNetResult LocalizedText(string culture = null)
{
var cultureInfo = string.IsNullOrWhiteSpace(culture)
//if the user is logged in, get their culture, otherwise default to 'en'
? User.Identity.IsAuthenticated && User.Identity is UmbracoBackOfficeIdentity
? Security.CurrentUser.GetUserCulture(Services.TextService)
: CultureInfo.GetCultureInfo("en")
: CultureInfo.GetCultureInfo(culture);
var textForCulture = Services.TextService.GetAllStoredValues(cultureInfo)
//the dictionary returned is fine but the delimiter between an 'area' and a 'value' is a '/' but the javascript
// in the back office requres the delimiter to be a '_' so we'll just replace it
.ToDictionary(key => key.Key.Replace("/", "_"), val => val.Value);
return new JsonNetResult { Data = textForCulture, Formatting = Formatting.Indented };
}
/// <summary>
/// Returns the JavaScript main file including all references found in manifests
/// </summary>
/// <returns></returns>
[MinifyJavaScriptResult(Order = 0)]
[OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)]
public JavaScriptResult Application()
{
var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins"));
var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache);
var initJs = new JsInitialization(parser);
var initCss = new CssInitialization(parser);
//get the legacy ActionJs file references to append as well
var legacyActionJsRef = new JArray(GetLegacyActionJs(LegacyJsActionType.JsUrl));
var result = initJs.GetJavascriptInitialization(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef);
result += initCss.GetStylesheetInitialization(HttpContext);
return JavaScript(result);
}
/// <summary>
/// Returns a js array of all of the manifest assets
/// </summary>
/// <returns></returns>
[UmbracoAuthorize(Order = 0)]
[HttpGet]
public JsonNetResult GetManifestAssetList()
{
Func<JArray> getResult = () =>
{
var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins"));
var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache);
var initJs = new JsInitialization(parser);
var initCss = new CssInitialization(parser);
var jsResult = initJs.GetJavascriptInitializationArray(HttpContext, new JArray());
var cssResult = initCss.GetStylesheetInitializationArray(HttpContext);
ManifestParser.MergeJArrays(jsResult, cssResult);
return jsResult;
};
//cache the result if debugging is disabled
var result = HttpContext.IsDebuggingEnabled
? getResult()
: ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem<JArray>(
typeof (BackOfficeController) + "GetManifestAssetList",
() => getResult(),
new TimeSpan(0, 10, 0));
return new JsonNetResult { Data = result, Formatting = Formatting.Indented };
}
[UmbracoAuthorize(Order = 0)]
[HttpGet]
public JsonNetResult GetGridConfig()
{
Func<List<GridEditor>> getResult = () =>
{
var editors = new List<GridEditor>();
var gridConfig = Server.MapPath("~/Config/grid.editors.config.js");
if (System.IO.File.Exists(gridConfig))
{
try
{
var arr = JArray.Parse(System.IO.File.ReadAllText(gridConfig));
//ensure the contents parse correctly to objects
var parsed = ManifestParser.GetGridEditors(arr);
editors.AddRange(parsed);
}
catch (Exception ex)
{
LogHelper.Error<BackOfficeController>("Could not parse the contents of grid.editors.config.js into a JSON array", ex);
}
}
var plugins = new DirectoryInfo(Server.MapPath("~/App_Plugins"));
var parser = new ManifestParser(plugins, ApplicationContext.ApplicationCache.RuntimeCache);
var builder = new ManifestBuilder(ApplicationContext.ApplicationCache.RuntimeCache, parser);
foreach (var gridEditor in builder.GridEditors)
{
//no duplicates! (based on alias)
if (editors.Contains(gridEditor) == false)
{
editors.Add(gridEditor);
}
}
return editors;
};
//cache the result if debugging is disabled
var result = HttpContext.IsDebuggingEnabled
? getResult()
: ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem<List<GridEditor>>(
typeof(BackOfficeController) + "GetGridConfig",
() => getResult(),
new TimeSpan(0, 10, 0));
return new JsonNetResult { Data = result, Formatting = Formatting.Indented };
}
/// <summary>
/// Returns the JavaScript object representing the static server variables javascript object
/// </summary>
/// <returns></returns>
[UmbracoAuthorize(Order = 0)]
[MinifyJavaScriptResult(Order = 1)]
public JavaScriptResult ServerVariables()
{
Func<string> getResult = () =>
{
var defaultVals = new Dictionary<string, object>
{
{
"umbracoUrls", new Dictionary<string, object>
{
{"legacyTreeJs", Url.Action("LegacyTreeJs", "BackOffice")},
{"manifestAssetList", Url.Action("GetManifestAssetList", "BackOffice")},
{"gridConfig", Url.Action("GetGridConfig", "BackOffice")},
{"serverVarsJs", Url.Action("Application", "BackOffice")},
//API URLs
{
"embedApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RteEmbedController>(
controller => controller.GetEmbed("", 0, 0))
},
{
"userApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<UserController>(
controller => controller.PostDisableUser(0))
},
{
"contentApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<ContentController>(
controller => controller.PostSave(null))
},
{
"mediaApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MediaController>(
controller => controller.GetRootMedia())
},
{
"imagesApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<ImagesController>(
controller => controller.GetBigThumbnail(0))
},
{
"sectionApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<SectionController>(
controller => controller.GetSections())
},
{
"treeApplicationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<ApplicationTreeController>(
controller => controller.GetApplicationTrees(null, null, null))
},
{
"contentTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<ContentTypeController>(
controller => controller.GetAllowedChildren(0))
},
{
"mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MediaTypeController>(
controller => controller.GetAllowedChildren(0))
},
{
"macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MacroController>(
controller => controller.GetMacroParameters(0))
},
{
"authenticationApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<AuthenticationController>(
controller => controller.PostLogin(null))
},
{
"currentUserApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<CurrentUserController>(
controller => controller.GetMembershipProviderConfig())
},
{
"legacyApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<LegacyController>(
controller => controller.DeleteLegacyItem(null, null, null))
},
{
"entityApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<EntityController>(
controller => controller.GetById(0, UmbracoEntityTypes.Media))
},
{
"dataTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<DataTypeController>(
controller => controller.GetById(0))
},
{
"dashboardApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<DashboardController>(
controller => controller.GetDashboard(null))
},
{
"logApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<LogController>(
controller => controller.GetEntityLog(0))
},
{
"memberApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MemberController>(
controller => controller.GetByKey(Guid.Empty))
},
{
"packageInstallApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<PackageInstallController>(
controller => controller.Fetch(string.Empty))
},
{
"rteApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<RichTextPreValueController>(
controller => controller.GetConfiguration())
},
{
"stylesheetApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<StylesheetController>(
controller => controller.GetAll())
},
{
"memberTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MemberTypeController>(
controller => controller.GetAllTypes())
},
{
"updateCheckApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<UpdateCheckController>(
controller => controller.GetCheck())
},
{
"tagApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<TagsController>(
controller => controller.GetAllTags(null))
},
{
"memberTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MemberTreeController>(
controller => controller.GetNodes("-1", null))
},
{
"mediaTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MediaTreeController>(
controller => controller.GetNodes("-1", null))
},
{
"contentTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl<ContentTreeController>(
controller => controller.GetNodes("-1", null))
},
{
"tagsDataBaseUrl", Url.GetUmbracoApiServiceBaseUrl<TagsDataController>(
controller => controller.GetTags(""))
},
{
"examineMgmtBaseUrl", Url.GetUmbracoApiServiceBaseUrl<ExamineManagementApiController>(
controller => controller.GetIndexerDetails())
},
{
"xmlDataIntegrityBaseUrl", Url.GetUmbracoApiServiceBaseUrl<XmlDataIntegrityController>(
controller => controller.CheckContentXmlTable())
}
}
},
{
"umbracoSettings", new Dictionary<string, object>
{
{"umbracoPath", GlobalSettings.Path},
{"mediaPath", IOHelper.ResolveUrl(SystemDirectories.Media).TrimEnd('/')},
{"appPluginsPath", IOHelper.ResolveUrl(SystemDirectories.AppPlugins).TrimEnd('/')},
{
"imageFileTypes",
string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes)
},
{"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn},
}
},
{
"umbracoPlugins", new Dictionary<string, object>
{
{"trees", GetTreePluginsMetaData()}
}
},
{"isDebuggingEnabled", HttpContext.IsDebuggingEnabled},
{"application", GetApplicationState()}
};
//Parse the variables to a string
return ServerVariablesParser.Parse(defaultVals);
};
//cache the result if debugging is disabled
var result = HttpContext.IsDebuggingEnabled
? getResult()
: ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem<string>(
typeof(BackOfficeController) + "ServerVariables",
() => getResult(),
new TimeSpan(0, 10, 0));
return JavaScript(result);
}
/// <summary>
/// Returns the server variables regarding the application state
/// </summary>
/// <returns></returns>
private Dictionary<string, object> GetApplicationState()
{
if (ApplicationContext.IsConfigured == false)
return null;
var app = new Dictionary<string, object>
{
{"assemblyVersion", UmbracoVersion.AssemblyVersion}
};
var version = string.IsNullOrEmpty(UmbracoVersion.CurrentComment)
? UmbracoVersion.Current.ToString(3)
: string.Format("{0}-{1}", UmbracoVersion.Current.ToString(3), UmbracoVersion.CurrentComment);
app.Add("version", version);
app.Add("cdf", ClientDependency.Core.Config.ClientDependencySettings.Instance.Version);
//useful for dealing with virtual paths on the client side when hosted in virtual directories especially
app.Add("applicationPath", HttpContext.Request.ApplicationPath.EnsureEndsWith('/'));
return app;
}
private IEnumerable<Dictionary<string, string>> GetTreePluginsMetaData()
{
var treeTypes = PluginManager.Current.ResolveAttributedTreeControllers();
//get all plugin trees with their attributes
var treesWithAttributes = treeTypes.Select(x => new
{
tree = x, attributes =
x.GetCustomAttributes(false)
}).ToArray();
var pluginTreesWithAttributes = treesWithAttributes
//don't resolve any tree decorated with CoreTreeAttribute
.Where(x => x.attributes.All(a => (a is CoreTreeAttribute) == false))
//we only care about trees with the PluginControllerAttribute
.Where(x => x.attributes.Any(a => a is PluginControllerAttribute))
.ToArray();
return (from p in pluginTreesWithAttributes
let treeAttr = p.attributes.OfType<TreeAttribute>().Single()
let pluginAttr = p.attributes.OfType<PluginControllerAttribute>().Single()
select new Dictionary<string, string>
{
{"alias", treeAttr.Alias}, {"packageFolder", pluginAttr.AreaName}
}).ToArray();
}
/// <summary>
/// Returns the JavaScript blocks for any legacy trees declared
/// </summary>
/// <returns></returns>
[UmbracoAuthorize(Order = 0)]
[MinifyJavaScriptResult(Order = 1)]
public JavaScriptResult LegacyTreeJs()
{
Func<string> getResult = () =>
{
var javascript = new StringBuilder();
javascript.AppendLine(LegacyTreeJavascript.GetLegacyTreeJavascript());
javascript.AppendLine(LegacyTreeJavascript.GetLegacyIActionJavascript());
//add all of the menu blocks
foreach (var file in GetLegacyActionJs(LegacyJsActionType.JsBlock))
{
javascript.AppendLine(file);
}
return javascript.ToString();
};
//cache the result if debugging is disabled
var result = HttpContext.IsDebuggingEnabled
? getResult()
: ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem<string>(
typeof(BackOfficeController) + "LegacyTreeJs",
() => getResult(),
new TimeSpan(0, 10, 0));
return JavaScript(result);
}
/// <summary>
/// Renders out all JavaScript references that have bee declared in IActions
/// </summary>
private static IEnumerable<string> GetLegacyActionJs(LegacyJsActionType type)
{
var blockList = new List<string>();
var urlList = new List<string>();
foreach (var jsFile in global::umbraco.BusinessLogic.Actions.Action.GetJavaScriptFileReferences())
{
//validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text
//block instead.
var isValid = true;
if (Uri.IsWellFormedUriString(jsFile, UriKind.RelativeOrAbsolute))
{
//ok it validates, but so does alert('hello'); ! so we need to do more checks
//here are the valid chars in a url without escaping
if (Regex.IsMatch(jsFile, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]"))
isValid = false;
//we'll have to be smarter and just check for certain js patterns now too!
var jsPatterns = new string[] {@"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"=="};
if (jsPatterns.Any(p => Regex.IsMatch(jsFile, p)))
{
isValid = false;
}
if (isValid)
{
//it is a valid URL add to Url list
urlList.Add(jsFile);
}
}
else
{
isValid = false;
}
if (isValid == false)
{
//it isn't a valid URL, must be a js block
blockList.Add(jsFile);
}
}
switch (type)
{
case LegacyJsActionType.JsBlock:
return blockList;
case LegacyJsActionType.JsUrl:
return urlList;
}
return blockList;
}
private enum LegacyJsActionType
{
JsBlock,
JsUrl
}
}
}