/// <summary> /// Gets the folder to. /// </summary> /// <param name="destPhysicalFolder">The destination physical folder.</param> /// <param name="sourceFolder">The source folder.</param> protected virtual void GetFolderTo(string destPhysicalFolder, ContentModel sourceFolder) { var result = this.List(sourceFolder); foreach (var content in result) { // a folder, do recursive call if (content.Path.EndsWith("/", StringComparison.OrdinalIgnoreCase)) { this.GetFolderTo(destPhysicalFolder, content); } else { string contentPhysicalPath = System.IO.Path.Combine( destPhysicalFolder, content.Path.TrimStart('/').Replace("/", "\\")); string directoryName = System.IO.Path.GetDirectoryName(contentPhysicalPath); this.Retrieve(content, true); if (!System.IO.Directory.Exists(directoryName)) { System.IO.Directory.CreateDirectory(directoryName); } System.IO.File.WriteAllBytes(contentPhysicalPath, content.Data); } } }
public void TestContentModelCorrectlyParseFileName() { // Arrange var model = new ContentModel(); var expected = "three"; // Act model.Path = "/test/one/two/three"; // Assert Assert.AreEqual(expected, model.FileName); }
public void TestPathSetMethodCorrectlyNormalizePath() { // Arrange var model = new ContentModel(); var expected = "/test/one/two/three"; // Act model.Path = "test\\one//two/three"; // Assert Assert.AreEqual(expected, model.Path); }
public void TestListRootPathReturnsTwoItem() { // Arrange var repo = new FileContentRepository(System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "CmsContent")); var model = new ContentModel() { Host = "localhost", Path = "/" }; // Act var result = repo.List(model); // Assert Assert.IsTrue(result.Count() == 2); }
public void TestDelete() { // Arrange var repo = new SqlContentRepository(new SqlDataRepository(), "DefaultDatabase", "CmsContent", string.Empty); var model = new ContentModel() { Host = "localhost", Path = "/test/test/article" }; // Act repo.Remove(model); var result = repo.Exists(model); // Assert Assert.IsFalse(result); }
public void TestDeleteFile() { // Arrange var repo = new FileContentRepository(System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "CmsContent")); var model = new ContentModel() { Host = "localhost", Path = "/test/test/article" }; // Act repo.Remove(model); var result = repo.Exists(model); // Assert Assert.IsFalse(result); }
/// <summary> /// Populates the data. /// </summary> /// <param name="context">The context.</param> /// <param name="content">The content.</param> /// <param name="tableName">Name of the table.</param> /// <param name="cachePath">The cache path.</param> /// <returns> /// Content model with populated data stream. /// </returns> public virtual ContentModel PopulateData(DapperContext context, ContentModel content, string tableName, string cachePath) { this.CacheData(context, content, tableName, cachePath); if (!string.IsNullOrEmpty(cachePath)) { var localPath = this.ResolvePath(content, cachePath); // return a stream content.DataStream = new System.IO.FileStream( localPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } return content; }
public void TestContentModelCorrectlyParseParentDirectory() { // Arrange var model = new ContentModel(); var expected = "/test/one/two/"; // Act model.Path = "/test/one/two/three"; // Assert Assert.AreEqual(expected, model.ParentPath); // Arrange expected = "/"; // Act model.Path = string.Empty; // Assert Assert.AreEqual("/", model.ParentPath); }
/// <summary> /// Caches the data. /// </summary> /// <param name="context">The context.</param> /// <param name="content">The content.</param> /// <param name="tableName">Name of the table.</param> /// <param name="cachePath">The cache path.</param> public virtual void CacheData(DapperContext context, ContentModel content, string tableName, string cachePath) { if (!string.IsNullOrEmpty(content.DataIdString)) { var data = context.Query<ContentModel>( string.Format("SELECT Data, DataLength FROM {0} WHERE IdString = @DataIdString", tableName), content).FirstOrDefault(); if (data != null) { content.Data = data.Data; content.DataLength = data.DataLength; } } if (string.IsNullOrEmpty(cachePath)) { return; } // determine if local content exists or is out of date var localPath = this.ResolvePath(content, cachePath); var canCache = !File.Exists(localPath); if (!canCache) { var lastWriteTime = File.GetLastWriteTime(localPath); canCache = lastWriteTime < (content.ModifyDate ?? content.CreateDate); } if (canCache) { var localDir = System.IO.Path.GetDirectoryName(localPath); if (!System.IO.Directory.Exists(localDir)) { System.IO.Directory.CreateDirectory(localDir); } System.IO.File.WriteAllBytes(localPath, content.Data ?? new byte[0]); } }
/// <summary> /// Gets the folder. /// </summary> /// <param name="folder">The folder.</param> /// <returns> /// Path to temp file that is a zip of the folder content. /// </returns> public virtual string GetFolder(ContentModel folder) { var fileName = Guid.NewGuid().ToString(); var tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), fileName); System.IO.Directory.CreateDirectory(tempPath); this.GetFolderTo(tempPath, folder); // zip up folder var tempFile = tempPath + ".zip"; ZipFile.CreateFromDirectory(tempPath, tempFile, CompressionLevel.Fastest, false); try { System.IO.Directory.Delete(tempPath, true); } catch { // just try to delete the temp folder we just created, do nothing if error } return tempFile; }
/// <summary> /// Lists the specified path. /// </summary> /// <param name="path">The path.</param> /// <param name="uri">The URI.</param> /// <returns>List of content models.</returns> public IQueryable<ContentModel> List(string path, Uri uri) { // make sure it's a folder listing var model = new ContentModel { Host = this.GetTenantHost(uri), Path = this.Normalize(path).Trim().TrimEnd('/') + "/" }; this.OnContentEvent("Listing", model); var result = this.ContentRepository.List(model).Where(h => !HiddenFolderChars.IsMatch(h.ParentPath)); this.OnContentEvent(new ContentConnectorEventArgument("Listed", model) { Extra = result }); return result; }
/// <summary> /// Deletes the specified model. /// </summary> /// <param name="path">The path.</param> /// <param name="uri">The URI.</param> /// <returns>Content model result.</returns> /// <exception cref="System.Web.HttpException">500;Unable to delete protected path '/'.</exception> public virtual ContentModel Delete(string path, Uri uri) { var model = new ContentModel() { Path = this.Normalize(path), Host = this.GetTenantHost(uri) }; this.OnContentEvent("Deleting", model); var thePath = model.Path.TrimEnd('/'); if (thePath.Equals(string.Empty, StringComparison.OrdinalIgnoreCase)) { throw new HttpException(500, "Unable to delete root path '/'."); } else if (thePath.Equals("/page", StringComparison.OrdinalIgnoreCase)) { throw new HttpException(500, "Unable to delete path '/page'."); } else if (thePath.Equals("/content", StringComparison.OrdinalIgnoreCase)) { throw new HttpException(500, "Unable to delete path '/content'."); } this.ContentRepository.Remove(model); this.OnContentEvent("Deleted", model); return model; }
/// <summary> /// Creates the or update. /// </summary> /// <param name="path">The path.</param> /// <param name="uri">The URI.</param> /// <param name="data">The data.</param> /// <returns> /// Content model result. /// </returns> /// <exception cref="System.ArgumentException">Cannot update root path.;path</exception> public virtual ContentModel CreateOrUpdate(string path, Uri uri, byte[] data) { var model = new ContentModel() { Path = this.Normalize(path), Data = data, Host = this.GetTenantHost(uri), CreateBy = System.Threading.Thread.CurrentPrincipal.Identity.Name, ModifyBy = System.Threading.Thread.CurrentPrincipal.Identity.Name }; this.OnContentEvent("CreateOrUpdating", model); // empty files are allowed, otherwise throw exception somewhere here // root folder creation is not allow if (string.Compare(model.Path, "/", StringComparison.OrdinalIgnoreCase) == 0) { throw new HttpException(500, "Cannot update root path."); } this.ContentRepository.Save(model); this.OnContentEvent("CreateOrUpdated", model); return model; }
/// <summary> /// Create the file. /// </summary> /// <param name="path">The path.</param> /// <param name="data">The data.</param> /// <param name="uri">The URI.</param> /// <returns>Content model result.</returns> /// <exception cref="System.Web.HttpException">500;Cannot create or overwrite an existing content of path: + path</exception> public virtual ContentModel Create(string path, string data, Uri uri) { var model = new ContentModel() { Path = this.Normalize(path), Host = this.GetTenantHost(uri) }; this.OnContentEvent("Creating", model); if (this.ContentRepository.Exists(model)) { throw new HttpException(500, "Cannot create or overwrite an existing content: " + path); } var result = this.CreateOrUpdate(path, data, uri); this.OnContentEvent("Created", result); return result; }
public virtual ActionResult CreatePage(string path, string data) { if (!path.ToLowerInvariant().EndsWith("_default.htm") && !path.ToLowerInvariant().EndsWith("_default.vash")) { throw new HttpException(500, "Page path must ends with _default.htm or _default.vash: " + path); } var model = new ContentModel() { Path = this.ContentConnector.Normalize(path) }; var folderPath = model.ParentPath; this.Create(folderPath + "app.js", string.Empty, null); this.Create(folderPath + "app.css", string.Empty, null); this.Create(folderPath + "layout.vash", @"<!DOCTYPE html> <html lang=""en""> <head> <meta charset=""utf-8"" /> <title>@model.title</title> <link type=""text/css"" rel=""stylesheet"" href=""app.css""> <script type=""text/javascript"" src=""app.js""></script> </head> <body> @html.block('content') </body> </html>", null); return this.Create(model.Path, data, null); }
/// <summary> /// Uploads the specified upload. /// </summary> /// <param name="file">The upload.</param> /// <param name="path">The path.</param> /// <returns>Parent path.</returns> protected virtual string Upload(HttpPostedFileBase file, string path) { var contentModel = new ContentModel() { Path = this.ContentConnector.Normalize(path), CreateBy = this.User.Identity.Name, ModifyBy = this.User.Identity.Name }; // if it's a file path then get the folder if (!contentModel.Path.EndsWith("/")) { contentModel.Path = contentModel.Path.Substring(0, contentModel.Path.LastIndexOf('/')); } var fileName = (file.FileName + string.Empty).Replace("\\", "/").Replace("//", "/"); contentModel.Path = string.Concat(contentModel.Path, "/", fileName.IndexOf('/') >= 0 ? System.IO.Path.GetFileName(file.FileName) : file.FileName); this.ContentConnector.CreateOrUpdate(contentModel.Path, this.Request.Url, file.InputStream.ReadAll()); return contentModel.ParentPath; }
/// <summary> /// Check for exist of content. /// </summary> /// <param name="content">The content - requires host, path, and name property.</param> /// <returns> /// true if content exists. /// </returns> public bool Exists(ContentModel content) { var path = this.ResolvePath(content); return content.Path.EndsWith("/", StringComparison.OrdinalIgnoreCase) ? System.IO.Directory.Exists(path) : File.Exists(path); }
/// <summary> /// Caches the retrieve. /// </summary> /// <param name="filename">The filename.</param> /// <returns> /// Cache retrieve. /// </returns> protected virtual string CacheRetrieve(string filename) { var path = filename; var content = new ContentModel() { Path = path, Host = this.api.tenantHost }; // search the shared folder for file if (!this.connector.ContentRepository.Exists(content)) { content.Path = "/page/shared/" + content.FileName; } // connector should be good for handling and retrieve error var contentResult = this.connector.Retrieve(content.Path, this.api.request.url); contentResult.SetDataFromStream(); var result = System.Text.Encoding.UTF8.GetString(contentResult.Data); return result; }
/// <summary> /// Retrieves the specified path. /// </summary> /// <param name="path">The path.</param> /// <param name="uri">The URI.</param> /// <returns>Content result.</returns> /// <exception cref="System.Web.HttpException">404;PhunCms path not found.</exception> public virtual ContentModel Retrieve(string path, Uri uri) { var content = new ContentModel() { Path = this.Normalize(path), Host = this.GetTenantHost(uri) }; this.OnContentEvent("Retrieving", content); if (content.Path.EndsWith("/", StringComparison.OrdinalIgnoreCase)) { var localFilePath = this.ContentRepository.GetFolder(content); content.DataStream = new System.IO.FileStream(localFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); content.Path = content.Path.Replace("/", "_") + ".zip"; this.OnContentEvent("RetrievedFolder", content); return content; } var result = this.ContentRepository.Retrieve(content, true); if (result.DataLength == null) { throw new HttpException(404, "PhunCms path not found: " + path); } this.OnContentEvent("Retrieved", result); return result; }
/// <summary> /// Saves the specified content. /// </summary> /// <param name="content">The content - requires host, path, and name property.</param> public void Save(ContentModel content) { var pathAndName = this.ResolvePath(content); var path = Path.GetDirectoryName(pathAndName); var isValidChildOfBasePath = System.IO.Path.GetFullPath(path) .StartsWith(System.IO.Path.GetFullPath(this.basePath), StringComparison.OrdinalIgnoreCase); if (!isValidChildOfBasePath) { return; } // make sure directories exist if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path); } // write all data File.WriteAllBytes(pathAndName, content.Data); }
/// <summary> /// Lists the specified content.Path /// </summary> /// <param name="content">The content.</param> /// <returns> /// Enumerable to content model. /// </returns> public abstract System.Linq.IQueryable<ContentModel> List(ContentModel content);
/// <summary> /// Resolves the path. /// </summary> /// <param name="content">The content.</param> /// <returns> /// The full path to the content or file. /// </returns> /// <exception cref="System.ArgumentException">Content is required.;content /// or /// Content path is required or not valid: + content.Path;content.Path</exception> private string ResolvePath(ContentModel content) { bool isFolder = content.Path.EndsWith("/", StringComparison.OrdinalIgnoreCase); if (content == null) { throw new ArgumentException("Content is required.", "content"); } if (string.IsNullOrEmpty(content.Host)) { content.Host = this.defaultHost; } var path = this.NormalizedPath(content.Path); // add: 'basePath\host\contentPath' var result = string.Concat(this.basePath, "\\", content.Host, "\\", path.Trim('/').Replace("/", "\\")); // result full path must be more than 3 characters if (result.Length <= 3) { throw new ArgumentException("Illegal path length detected: " + content.Path, "path"); } if (isFolder) { result = result + "\\"; } return result; }
/// <summary> /// Called when [content event]. /// </summary> /// <param name="eventName">Name of the event.</param> /// <param name="model">The model.</param> public void OnContentEvent(string eventName, ContentModel model) { this.OnContentEvent(new ContentConnectorEventArgument(eventName, model)); }
/// <summary> /// Phuns the partial. /// </summary> /// <param name="contentName">Name of the content.</param> /// <param name="url">The URL.</param> /// <returns> /// Partial content. /// </returns> /// <exception cref="System.ArgumentException">contentName is required.</exception> protected internal virtual string PhunPartial(string contentName, Uri url) { if (string.IsNullOrEmpty(contentName)) { throw new ArgumentException("contentName is required."); } var result = string.Empty; var config = this.ContentConfig ?? Bootstrapper.Default.ContentConfig; var content = new ContentModel() { Path = this.Normalize( "/page" + (contentName.Contains("/") ? contentName : url.AbsolutePath + "/" + contentName)), Host = this.GetTenantHost(url) }; config.ContentRepository.Retrieve(content, true); if (content.DataLength != null) { content.SetDataFromStream(); result = System.Text.Encoding.UTF8.GetString(content.Data).GetHtmlBody(); } return result; }
/// <summary> /// View content. /// </summary> /// <param name="httpContext">The HTTP context.</param> public virtual ContentModel RenderPage(HttpContextBase httpContext) { var path = httpContext.Request.QueryString["path"]; if (string.IsNullOrEmpty(path)) { path = (httpContext.Request.Path + string.Empty).Trim(); } // if somehow, CMS Resource URL get routed here then intercept if (this.Config.IsResourceRoute(path)) { this.Config.GetResourceFile(path).WriteFile(httpContext); return null; } if (!path.EndsWith("/", StringComparison.OrdinalIgnoreCase)) { httpContext.Response.RedirectPermanent(path + "/"); return null; } path = path.TrimEnd('/'); var tenantHost = this.GetTenantHost(httpContext.Request.Url); var model = new ContentModel() { Host = tenantHost, Path = this.ResolvePath(path, tenantHost, httpContext) }; this.OnContentEvent("PageRendering", model); if (model.Path.EndsWith(".htm")) { // set response 302 return this.ContentRepository.Retrieve(model, true); } // now that it is a vash file, attempt to render the file this.TemplateHandler.Render(model, this, httpContext); this.OnContentEvent("PageRendered", model); return null; }
/// <summary> /// Populate or gets the content provided specific host, path, and name property. /// </summary> /// <param name="content">The content - requires host, path, and name property.</param> /// <param name="includeData">if set to <c>true</c> [include data].</param> /// <returns> /// The <see cref="ContentModel" /> that was passed in. /// </returns> public abstract ContentModel Retrieve(ContentModel content, bool includeData = true);
/// <summary> /// Checks the page. /// </summary> /// <param name="path">The path.</param> /// <param name="tenantHost">The tenant host.</param> /// <param name="extension">The extension.</param> /// <returns></returns> private string CheckPage(string path, string tenantHost, string extension) { // only normalize after determine that it is not a CMS Resource URL // it must also start with page path var result = string.Concat("/page", this.Normalize(path)); var newModel = new ContentModel() { Host = tenantHost, Path = result }; if (!result.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) { newModel.Path += extension; if (this.ContentRepository.Exists(newModel)) { result = newModel.Path; } else { newModel.Path = result + "/_default" + extension; result = this.ContentRepository.Exists(newModel) ? newModel.Path : "==NOT FOUND=="; } } return result; }
public void TestListRootPathReturnsTwoItem() { // Arrange var repo = new SqlContentRepository(new SqlDataRepository(), "DefaultDatabase", "CmsContent", string.Empty); var model = new ContentModel() { Host = "localhost", Path = "/" }; // Act repo.Save( new ContentModel() { Host = "localhost", Path = "/test/foo/article", Data = System.Text.Encoding.UTF8.GetBytes("test"), CreateBy = string.Empty }); var result = repo.List(model); // Assert Assert.AreEqual(2, result.Count()); }
/// <summary> /// Tries the set304. /// </summary> /// <param name="content">The content.</param> /// <returns>True if content has not been modified since last retrieve.</returns> protected virtual bool TrySet304(ContentModel content, double hours = 24) { var context = this.HttpContext; if (this.ContentConnector.Config.DisableResourceCache || !Bootstrapper.Default.ContentRegEx.IsMatch(content.FileName)) { context.Response.Cache.SetCacheability(HttpCacheability.NoCache); context.Response.Cache.SetExpires(DateTime.MinValue); return false; } // allow static file to access core context.Response.AddHeader("Access-Control-Allow-Origin", "*"); var currentDate = content.ModifyDate ?? DateTime.Now; context.Response.Cache.SetLastModified(currentDate); context.Response.Cache.SetCacheability(HttpCacheability.Public); context.Response.Cache.SetExpires(DateTime.Now.AddHours(hours)); DateTime previousDate; string data = context.Request.Headers["If-Modified-Since"] + string.Empty; if (DateTime.TryParse(data, out previousDate)) { if (currentDate > previousDate.AddMilliseconds(100)) { context.Response.StatusCode = 304; context.Response.StatusDescription = "Not Modified"; return true; } } return false; }
/// <summary> /// Lists the specified content.Path /// </summary> /// <param name="content">The content.</param> /// <returns> /// Enumerable to content model. /// </returns> public override IQueryable<ContentModel> List(ContentModel content) { var path = this.ResolvePath(content); var result = new List<ContentModel>(); var isValidChildOfBasePath = System.IO.Path.GetFullPath(path) .StartsWith(System.IO.Path.GetFullPath(this.basePath), StringComparison.OrdinalIgnoreCase); // don't do anything for invalid path if (!isValidChildOfBasePath) { return result.AsQueryable(); } // only proceed if it is a folder if (content.Path.EndsWith("/", StringComparison.OrdinalIgnoreCase)) { var directory = new DirectoryInfo(path); if (!directory.Exists) { return result.AsQueryable(); } // build directory result foreach (var dir in directory.GetDirectories()) { result.Add( new ContentModel() { Host = content.Host, Path = string.Concat(content.Path, dir.Name, "/"), CreateDate = directory.CreationTime, ModifyDate = directory.LastWriteTime }); } // build file result foreach (var file in directory.GetFiles()) { result.Add( new ContentModel() { Host = content.Host, Path = string.Concat(content.Path, file.Name), CreateDate = file.CreationTime, ModifyDate = file.LastWriteTime }); } } return result.AsQueryable(); }