/// <summary> /// Starts the compile themes thread. /// </summary> private static void StartCompileThemesThread() { // compile less files CompileThemesThread = new Thread(() => { /* Set to background thread so that this thread doesn't prevent Rock from shutting down. */ var stopwatchCompileLess = Stopwatch.StartNew(); Thread.CurrentThread.IsBackground = true; string messages = string.Empty; // Pass in a CancellationToken so we can stop compiling if Rock shuts down before it is done RockTheme.CompileAll(out messages, _threadCancellationTokenSource.Token); if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { if (messages.IsNullOrWhiteSpace()) { System.Diagnostics.Debug.WriteLine(string.Format("[{0,5:#} seconds] Less files compiled successfully. ", +stopwatchCompileLess.Elapsed.TotalSeconds)); } else { System.Diagnostics.Debug.WriteLine("RockTheme.CompileAll messages: " + messages); } } }); CompileThemesThread.Start(); }
/// <summary> /// Binds the grid. /// </summary> private void BindGrid() { DirectoryInfo themeDirectory = new DirectoryInfo(HttpRuntime.AppDomainAppPath + "Themes"); var themes = RockTheme.GetThemes(); var sortProperty = gThemes.SortProperty; if (sortProperty != null) { switch (sortProperty.Property) { case "Name": { if (sortProperty.Direction == SortDirection.Ascending) { themes = themes.OrderBy(t => t.Name).ToList(); } else { themes = themes.OrderByDescending(t => t.Name).ToList(); } break; } } } gThemes.DataSource = themes.ToList(); gThemes.DataBind(); }
public void OnStartup() { foreach (var theme in RockTheme.GetThemes()) { string themeMessage = string.Empty; bool themeSuccess = theme.CompileSass(out themeMessage); } }
protected void btnSave_Click(object sender, EventArgs e) { string variableFile = string.Format(@"{0}Themes/{1}/Styles/_variables.less", Request.PhysicalApplicationPath, _themeName); string variableOverrideFile = string.Format(@"{0}Themes/{1}/Styles/_variable-overrides.less", Request.PhysicalApplicationPath, _themeName); string cssOverrideFile = string.Format(@"{0}Themes/{1}/Styles/_css-overrides.less", Request.PhysicalApplicationPath, _themeName); if (File.Exists(cssOverrideFile)) { File.WriteAllText(cssOverrideFile, ceOverrides.Text); } // get list of original values Dictionary <string, string> originalValues = GetVariables(variableFile); StringBuilder overrideFile = new StringBuilder(); foreach (var control in phThemeControls.Controls) { if (control is TextBox) { var textBoxControl = (TextBox)control; string variableName = textBoxControl.ID.Replace(" ", "-").ToLower(); // find original value if (originalValues.ContainsKey(variableName)) { string originalValue = originalValues[variableName]; // color picker will convert #fff to #ffffff so take that into account string secondaryValue = string.Empty; if (originalValue.Length == 4 && originalValue[0] == '#') { secondaryValue = originalValue + originalValue.Substring(1, 3); } if (originalValue != textBoxControl.Text && secondaryValue != textBoxControl.Text) { overrideFile.Append(string.Format("@{0}: {1};{2}", variableName, textBoxControl.Text, Environment.NewLine)); } } } } System.IO.StreamWriter file = new System.IO.StreamWriter(variableOverrideFile); file.WriteLine(overrideFile); file.Dispose(); // compile theme string messages = string.Empty; var theme = new RockTheme(_themeName); theme.Compile(); NavigateToParentPage(); }
public static bool CompileSass(this RockTheme theme, out string messages) { messages = string.Empty; bool result = true; if (IntPtr.Size == 8) { // 64 bit machine SetDllDirectory(System.Web.HttpContext.Current.Server.MapPath("~/LibSass/win-x64/native")); } else if (IntPtr.Size == 4) { // 32 bit machine SetDllDirectory(System.Web.HttpContext.Current.Server.MapPath("~/LibSass/win-x86/native")); } try { DirectoryInfo themeDirectory = new DirectoryInfo(theme.AbsolutePath + @"\Styles"); if (themeDirectory.Exists) { List <FileInfo> files = GetSCSSFiles(themeDirectory); if (files != null) { if (theme.AllowsCompile) { // don't compile files that start with an underscore foreach (var file in files.Where(f => f.Name.EndsWith(".scss") && !f.Name.StartsWith("_"))) { var content = File.ReadAllText(file.FullName); var compact = Scss.ConvertToCss(content, new ScssOptions() { InputFile = file.FullName, OutputFile = file.Name.Replace(".scss", ".css"), // Note: It will not generate the file, // only used for exception reporting // includes and source maps GenerateSourceMap = false, }); File.WriteAllText(file.DirectoryName + @"\" + file.Name.Replace(".scss", ".css"), compact.Css); } } } } } catch (Exception ex) { result = false; messages = ex.Message; } return(result); }
/// <summary> /// Handles the Click event of the gCompileTheme control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="RowEventArgs"/> instance containing the event data.</param> protected void gCompileTheme_Click(object sender, RowEventArgs e) { var theme = new RockTheme(e.RowKeyValue.ToString()); string messages = string.Empty; bool compileSuccess = theme.Compile(out messages); if (compileSuccess) { mdThemeCompile.Show("Theme was successfully compiled.", ModalAlertType.Information); } else { nbMessages.NotificationBoxType = NotificationBoxType.Danger; nbMessages.Text = string.Format("An error occurred while compiling the {0} them. Message: {1}", theme.Name, messages); } }
/// <summary> /// Handles the Delete event of the gThemes control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="RowEventArgs"/> instance containing the event data.</param> protected void gThemes_Delete(object sender, RowEventArgs e) { string messages = string.Empty; bool deleteSuccess = RockTheme.DeleteTheme(e.RowKeyValue.ToString(), out messages); if (deleteSuccess) { nbMessages.NotificationBoxType = NotificationBoxType.Success; nbMessages.Text = string.Format("The {0} theme has been successfully deleted.", e.RowKeyValue); BindGrid(); } else { nbMessages.NotificationBoxType = NotificationBoxType.Danger; nbMessages.Text = "Could not delete theme. Message: " + messages; } }
/// <summary> /// Handles the Click event of the btnCompileTheme control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void btnCompileTheme_Click(object sender, EventArgs e) { var rockContext = new RockContext(); SiteService siteService = new SiteService(rockContext); Site site = siteService.Get(hfSiteId.Value.AsInteger()); string messages = string.Empty; var theme = new RockTheme(site.Theme); bool success = theme.Compile(out messages); if (success) { mdThemeCompile.Show("Theme was successfully compiled.", ModalAlertType.Information); } else { mdThemeCompile.Show(string.Format("An error occurred compiling the theme {0}. Message: {1}.", site.Theme, messages), ModalAlertType.Warning); } }
/// <summary> /// Handles the SaveClick event of the mdThemeClone control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void mdThemeClone_SaveClick(object sender, EventArgs e) { mdThemeClone.Hide(); string resultMessages = string.Empty; var cloneWasSuccessful = RockTheme.CloneTheme(hfClonedThemeName.Value, tbNewThemeName.Text, out resultMessages); if (cloneWasSuccessful) { nbMessages.NotificationBoxType = NotificationBoxType.Success; nbMessages.Text = string.Format("The {0} theme has been successfully cloned to {1}.", hfClonedThemeName.Value, tbNewThemeName.Text); BindGrid(); } else { nbMessages.NotificationBoxType = NotificationBoxType.Danger; nbMessages.Text = resultMessages; } }
/// <summary> /// Handles the Start event of the Application control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> protected void Application_Start(object sender, EventArgs e) { try { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); LogMessage(APP_LOG_FILENAME, "Application Starting..."); if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { System.Diagnostics.Debug.WriteLine(string.Format("Application_Start: {0}", RockDateTime.Now.ToString("hh:mm:ss.FFF"))); } // Clear all cache RockMemoryCache.Clear(); // If not migrating, set up view cache to speed up startup (Not supported when running migrations). var fileInfo = new FileInfo(Server.MapPath("~/App_Data/Run.Migration")); if (!fileInfo.Exists) { RockInteractiveViews.SetViewFactory(Server.MapPath("~/App_Data/RockModelViews.xml")); } // Get a db context using (var rockContext = new RockContext()) { if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { try { // default Initializer is CreateDatabaseIfNotExists, so set it to NULL so that nothing happens if there isn't a database yet Database.SetInitializer <Rock.Data.RockContext>(null); new AttributeService(rockContext).Get(0); System.Diagnostics.Debug.WriteLine(string.Format("ConnectToDatabase {2}/{1} - {0} ms", stopwatch.Elapsed.TotalMilliseconds, rockContext.Database.Connection.Database, rockContext.Database.Connection.DataSource)); } catch { // Intentionally Blank } } //// Run any needed Rock and/or plugin migrations //// NOTE: MigrateDatabase must be the first thing that touches the database to help prevent EF from creating empty tables for a new database MigrateDatabase(rockContext); // Preload the commonly used objects stopwatch.Restart(); LoadCacheObjects(rockContext); if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { System.Diagnostics.Debug.WriteLine(string.Format("LoadCacheObjects - {0} ms", stopwatch.Elapsed.TotalMilliseconds)); } // Run any plugin migrations MigratePlugins(rockContext); RegisterRoutes(rockContext, RouteTable.Routes); // Configure Rock Rest API stopwatch.Restart(); GlobalConfiguration.Configure(Rock.Rest.WebApiConfig.Register); if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { System.Diagnostics.Debug.WriteLine(string.Format("Configure WebApiConfig - {0} ms", stopwatch.Elapsed.TotalMilliseconds)); stopwatch.Restart(); } // setup and launch the jobs infrastructure if running under IIS bool runJobsInContext = Convert.ToBoolean(ConfigurationManager.AppSettings["RunJobsInIISContext"]); if (runJobsInContext) { ISchedulerFactory sf; // create scheduler sf = new StdSchedulerFactory(); sched = sf.GetScheduler(); // get list of active jobs ServiceJobService jobService = new ServiceJobService(rockContext); foreach (ServiceJob job in jobService.GetActiveJobs().ToList()) { const string errorLoadingStatus = "Error Loading Job"; try { IJobDetail jobDetail = jobService.BuildQuartzJob(job); ITrigger jobTrigger = jobService.BuildQuartzTrigger(job); sched.ScheduleJob(jobDetail, jobTrigger); //// if the last status was an error, but we now loaded successful, clear the error // also, if the last status was 'Running', clear that status because it would have stopped if the app restarted if (job.LastStatus == errorLoadingStatus || job.LastStatus == "Running") { job.LastStatusMessage = string.Empty; job.LastStatus = string.Empty; rockContext.SaveChanges(); } } catch (Exception ex) { // log the error LogError(ex, null); // create a friendly error message string message = string.Format("Error loading the job: {0}.\n\n{2}", job.Name, job.Assembly, ex.Message); job.LastStatusMessage = message; job.LastStatus = errorLoadingStatus; rockContext.SaveChanges(); } } // set up the listener to report back from jobs as they complete sched.ListenerManager.AddJobListener(new RockJobListener(), EverythingMatcher <JobKey> .AllJobs()); // start the scheduler sched.Start(); } // Force the static Liquid class to get instantiated so that the standard filters are loaded prior // to the custom RockFilter. This is to allow the custom 'Date' filter to replace the standard // Date filter. Liquid.UseRubyDateFormat = false; //// NOTE: This means that template filters will also use CSharpNamingConvention //// For example the dotliquid documentation says to do this for formatting dates: //// {{ some_date_value | date:"MMM dd, yyyy" }} //// However, if CSharpNamingConvention is enabled, it needs to be: //// {{ some_date_value | Date:"MMM dd, yyyy" }} Template.NamingConvention = new DotLiquid.NamingConventions.CSharpNamingConvention(); Template.FileSystem = new RockWeb.LavaFileSystem(); Template.RegisterSafeType(typeof(Enum), o => o.ToString()); Template.RegisterFilter(typeof(Rock.Lava.RockFilters)); // add call back to keep IIS process awake at night and to provide a timer for the queued transactions AddCallBack(); Rock.Security.Authorization.Load(); } EntityTypeService.RegisterEntityTypes(Server.MapPath("~")); FieldTypeService.RegisterFieldTypes(Server.MapPath("~")); BundleConfig.RegisterBundles(BundleTable.Bundles); // mark any user login stored as 'IsOnline' in the database as offline MarkOnlineUsersOffline(); SqlServerTypes.Utilities.LoadNativeAssemblies(Server.MapPath("~")); LogMessage(APP_LOG_FILENAME, "Application Started Successfully"); if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { System.Diagnostics.Debug.WriteLine(string.Format("Application_Started_Successfully: {0}", RockDateTime.Now.ToString("hh:mm:ss.FFF"))); } } catch (Exception ex) { SetError66(); throw (new Exception("Error occurred during application startup", ex)); } // Update attributes for new workflow actions new Thread(() => { Rock.Workflow.ActionContainer.Instance.UpdateAttributes(); }).Start(); // compile less files new Thread(() => { Thread.CurrentThread.IsBackground = true; RockTheme.CompileAll(); }).Start(); }
/// <summary> /// Handles the Click event of the btnSave control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> protected void btnSave_Click(object sender, EventArgs e) { string cssOverrideFile = string.Format(@"{0}Themes/{1}/Styles/_css-overrides.less", Request.PhysicalApplicationPath, _themeName); if (File.Exists(cssOverrideFile)) { File.WriteAllText(cssOverrideFile, ceOverrides.Text); } // get list of original values Dictionary <string, string> originalValues = GetVariables(_variableFile); StringBuilder overrideFile = new StringBuilder(); if (pnlFontAwesomeSettings.Visible) { overrideFile.AppendLine(FontAwesomeHelper.VariableOverridesTokens.StartRegion); var selectedPrimaryWeight = FontAwesomeHelper.FontAwesomeIconCssWeights.FirstOrDefault(a => a.WeightName == ddlFontAwesomeIconWeight.SelectedValue); overrideFile.AppendLine(string.Format("{0} {1};", FontAwesomeHelper.VariableOverridesTokens.FontWeightValueLineStart, selectedPrimaryWeight.WeightValue)); overrideFile.AppendLine(string.Format("{0} '{1}';", FontAwesomeHelper.VariableOverridesTokens.FontWeightNameLineStart, selectedPrimaryWeight.WeightName)); if (FontAwesomeHelper.HasFontAwesomeProKey()) { overrideFile.AppendLine("@fa-edition: 'pro';"); } overrideFile.AppendLine(); if (!selectedPrimaryWeight.IncludedInFree) { overrideFile.AppendLine(string.Format("{0} {1};", FontAwesomeHelper.VariableOverridesTokens.FontEditionLineStart, FontAwesomeHelper.VariableOverridesTokens.FontEditionPro)); } overrideFile.AppendLine("@import \"../../../Styles/FontAwesome/_rock-fa-mixins.less\";"); foreach (var alternateFontWeightName in cblFontAwesomeAlternateFonts.Items.OfType <ListItem>().Where(a => a.Selected).Select(a => a.Value).ToList()) { var alternateFont = FontAwesomeHelper.FontAwesomeIconCssWeights.Where(a => a.WeightName == alternateFontWeightName).FirstOrDefault(); if (alternateFont != null) { string suffixParam = string.Empty; overrideFile.AppendLine( string.Format("{0} '{1}', 'pro' );", FontAwesomeHelper.VariableOverridesTokens.FontFaceLineStart, alternateFont.WeightName )); } } overrideFile.AppendLine(FontAwesomeHelper.VariableOverridesTokens.EndRegion); } foreach (var control in phThemeControls.Controls) { if (control is TextBox) { var textBoxControl = ( TextBox )control; string variableName = textBoxControl.ID.Replace(" ", "-").ToLower(); // find original value if (originalValues.ContainsKey(variableName)) { string originalValue = originalValues[variableName]; // color picker will convert #fff to #ffffff so take that into account string secondaryValue = string.Empty; if (originalValue.Length == 4 && originalValue[0] == '#') { secondaryValue = originalValue + originalValue.Substring(1, 3); } if (originalValue.ToLower() != textBoxControl.Text.ToLower() && secondaryValue.ToLower() != textBoxControl.Text.ToLower()) { overrideFile.Append(string.Format("@{0}: {1};{2}", variableName, textBoxControl.Text, Environment.NewLine)); } } } } File.WriteAllText(_variableOverrideFile, overrideFile.ToString()); // compile theme string messages = string.Empty; var theme = new RockTheme(_themeName); if (!theme.Compile(out messages)) { nbMessages.NotificationBoxType = NotificationBoxType.Danger; nbMessages.Text = string.Format("An error occurred while compiling the {0} theme.\nMessage: <pre>{1}</pre>", theme.Name, messages);; nbMessages.Visible = true; } else { NavigateToParentPage(); } }
/// <summary> /// Handles the Start event of the Application control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> protected void Application_Start(object sender, EventArgs e) { RockApplicationStartupHelper.ShowDebugTimingMessage("Application Start"); QueueInUse = false; /* 2020-05-20 MDP * Prior to Application_Start, Rock.WebStartup has an AssemblyInitializer class that runs as a PreApplicationStartMethod. * * This will call RockApplicationStartupHelper which will take care of the following * -- EF Migrations * -- Rock Plugin Migrations (except for ones that are in App_Code) * -- Sending Version update notifications to Spark * -- Pre-loading EntityTypeCache, FieldTypeCache, and AttributeCache * -- Loading any attributes defined in web.config * -- Registering HttpModules * -- Initializing Lava * -- Starting the Job Scheduler (if configured to run) */ /* 2020-05-20 MDP * The remaining items need to be run here in Application_Start since they depend on things that don't happen until now * like Routes, plugin stuff in App_Code */ try { // AssemblyInitializer will catch any exception that it gets and sets AssemblyInitializerException. // Doing this lets us do any error handling (now that RockWeb has started) if (AssemblyInitializer.AssemblyInitializerException != null) { throw AssemblyInitializer.AssemblyInitializerException; } // register the App_Code assembly in the Rock.Reflection helper so that Reflection methods can search for types in it var appCodeAssembly = typeof(Global).Assembly; Rock.Reflection.SetAppCodeAssembly(appCodeAssembly); // Probably won't be any, but run any migrations that might be in App_Code. (Any that are dll's get run in RockApplicationStartupHelper.RunApplicationStartup()) RockApplicationStartupHelper.RunPluginMigrations(appCodeAssembly); // Register Routes RouteTable.Routes.Clear(); Rock.Web.RockRouteHandler.RegisterRoutes(); // Configure Rock Rest API GlobalConfiguration.Configure(Rock.Rest.WebApiConfig.Register); // set the encryption protocols that are permissible for external SSL connections System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12; RockApplicationStartupHelper.ShowDebugTimingMessage("Register Routes"); // Perform any Rock startups RunStartups(); // add call back to keep IIS process awake at night and to provide a timer for the queued transactions AddCallBack(); // register any EntityTypes or FieldTypes that are discovered in Rock or any plugins (including ones in ~/App_Code) EntityTypeService.RegisterEntityTypes(); FieldTypeService.RegisterFieldTypes(); BundleConfig.RegisterBundles(BundleTable.Bundles); // mark any user login stored as 'IsOnline' in the database as offline MarkOnlineUsersOffline(); SqlServerTypes.Utilities.LoadNativeAssemblies(Server.MapPath("~")); RockApplicationStartupHelper.ShowDebugTimingMessage("Register Types"); RockApplicationStartupHelper.LogStartupMessage("Application Started Successfully"); if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { System.Diagnostics.Debug.WriteLine(string.Format("[{0,5:#} ms] Total Startup Time", (RockDateTime.Now - RockApplicationStartupHelper.StartDateTime).TotalMilliseconds)); } ExceptionLogService.AlwaysLogToFile = false; } catch (Exception ex) { if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { System.Diagnostics.Debug.WriteLine(string.Format("##Startup Exception##: {0}\n{1}", ex.Message, ex.StackTrace)); } SetError66(); var startupException = new RockStartupException("Error occurred during application startup", ex); LogError(startupException, null); throw startupException; } // Update attributes for new workflow actions, instead of doing them on demand // Not sure why we did this but this is the commit c23a4021d2ce7be96a30bae8c431c113f942f26f new Thread(() => { Rock.Workflow.ActionContainer.Instance.UpdateAttributes(); }).Start(); // compile less files new Thread(() => { var stopwatchCompileLess = Stopwatch.StartNew(); Thread.CurrentThread.IsBackground = true; string messages = string.Empty; RockTheme.CompileAll(out messages); if (System.Web.Hosting.HostingEnvironment.IsDevelopmentEnvironment) { if (messages.IsNullOrWhiteSpace()) { System.Diagnostics.Debug.WriteLine(string.Format("[{0,5:#} seconds] Less files compiled successfully. ", +stopwatchCompileLess.Elapsed.TotalSeconds)); } else { System.Diagnostics.Debug.WriteLine("RockTheme.CompileAll messages: " + messages); } } }).Start(); }