add new global replace service for bulk updating content

This commit is contained in:
sbwalker
2026-02-18 13:59:25 -05:00
parent 48a70c8be3
commit 13a58ed099
12 changed files with 599 additions and 8 deletions

View File

@@ -0,0 +1,117 @@
@namespace Oqtane.Modules.Admin.GlobalReplace
@using System.Text.Json
@inherits ModuleBase
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="find" HelpText="Specify the content which needs to be replaced" ResourceKey="Find">Find What: </Label>
<div class="col-sm-9">
<input id="find" class="form-control" @bind="@_find" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="replace" HelpText="Specify the replacement content" ResourceKey="Replace">Replace With: </Label>
<div class="col-sm-9">
<input id="replace" class="form-control" @bind="@_replace" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="casesensitive" HelpText="Specify if the replacement operation should be case sensitive" ResourceKey="CaseSensitive">Match Case? </Label>
<div class="col-sm-9">
<select id="casesensitive" class="form-select" @bind="@_caseSensitive">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="site" HelpText="Specify if site properties should be updated (ie. name, headcontent, bodycontent)" ResourceKey="Site">Site Properties? </Label>
<div class="col-sm-9">
<select id="site" class="form-select" @bind="@_site">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pages" HelpText="Specify if page properties should be updated (ie. name, title, headcontent, bodycontent)" ResourceKey="Pages">Page Properties? </Label>
<div class="col-sm-9">
<select id="pages" class="form-select" @bind="@_pages">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="modules" HelpText="Specify if module properties should be updated (ie. title, header, footer)" ResourceKey="Modules">Module Properties? </Label>
<div class="col-sm-9">
<select id="modules" class="form-select" @bind="@_modules">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="content" HelpText="Specify if module content should be updated" ResourceKey="Content">Module Content? </Label>
<div class="col-sm-9">
<select id="content" class="form-select" @bind="@_content">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<br /><br />
<ActionDialog Header="Global Replace" Message="This Operation is Permanent. Are You Sure You Wish To Proceed?" Action="Replace" Class="btn btn-primary" OnClick="@(async () => await Save())" ResourceKey="GlobalReplace" />
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Global Replace";
private string _find;
private string _replace;
private string _caseSensitive = "True";
private string _site = "True";
private string _pages = "True";
private string _modules = "True";
private string _content = "True";
private async Task Save()
{
try
{
if (!string.IsNullOrEmpty(_find) && !string.IsNullOrEmpty(_replace))
{
var replace = new GlobalReplace
{
Find = _find,
Replace = _replace,
CaseSensitive = bool.Parse(_caseSensitive),
Site = bool.Parse(_site),
Pages = bool.Parse(_pages),
Modules = bool.Parse(_modules),
Content = bool.Parse(_content)
};
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "GlobalReplace_" + DateTime.UtcNow.ToString("yyyyMMddHHmmss"), JsonSerializer.Serialize(replace));
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Global Replace Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
}
}
}

View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CaseSensitive.Text" xml:space="preserve">
<value>Match Case?</value>
</data>
<data name="CaseSensitive.HelpText" xml:space="preserve">
<value>Specify if the replacement operation should be case sensitive</value>
</data>
<data name="Content.Text" xml:space="preserve">
<value>Module Content?</value>
</data>
<data name="Content.HelpText" xml:space="preserve">
<value>Specify if module content should be updated</value>
</data>
<data name="Pages.Text" xml:space="preserve">
<value>Page Properties?</value>
</data>
<data name="Pages.HelpText" xml:space="preserve">
<value>Specify if page properties should be updated (ie. name, title, headcontent, bodycontent)</value>
</data>
<data name="Site.Text" xml:space="preserve">
<value>Site Properties?</value>
</data>
<data name="Site.HelpText" xml:space="preserve">
<value>Specify if site properties should be updated (ie. name, headcontent, bodycontent)</value>
</data>
<data name="Replace.Text" xml:space="preserve">
<value>Replace With:</value>
</data>
<data name="Replace.HelpText" xml:space="preserve">
<value>Specify the replacement content</value>
</data>
<data name="Modules.Text" xml:space="preserve">
<value>Module Properties?</value>
</data>
<data name="Modules.HelpText" xml:space="preserve">
<value>Specify if module properties should be updated (ie. title, header, footer)</value>
</data>
<data name="Success.Save" xml:space="preserve">
<value>Your Global Replace Request Has Been Submitted And Will Be Executed Shortly</value>
</data>
<data name="Error.Save" xml:space="preserve">
<value>Error Saving Global Replace</value>
</data>
<data name="Find.HelpText" xml:space="preserve">
<value>Specify the content which needs to be replaced</value>
</data>
<data name="Find.Text" xml:space="preserve">
<value>Find What:</value>
</data>
<data name="GlobalReplace.Header" xml:space="preserve">
<value>Global Replace</value>
</data>
<data name="GlobalReplace.Message" xml:space="preserve">
<value>This Operation is Permanent. Are You Sure You Wish To Proceed?</value>
</data>
</root>

View File

@@ -483,4 +483,7 @@
<data name="Installed" xml:space="preserve"> <data name="Installed" xml:space="preserve">
<value>Installed</value> <value>Installed</value>
</data> </data>
<data name="Global Replace" xml:space="preserve">
<value>Global Replace</value>
</data>
</root> </root>

View File

@@ -0,0 +1,178 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class GlobalReplaceJob : HostedServiceBase
{
public GlobalReplaceJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "Global Replace Job";
Frequency = "m"; // run every minute.
Interval = 1;
IsEnabled = true;
}
public override async Task<string> ExecuteJobAsync(IServiceProvider provider)
{
string log = "";
// get services
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var settingRepository = provider.GetRequiredService<ISettingRepository>();
var tenantManager = provider.GetRequiredService<ITenantManager>();
var pageRepository = provider.GetRequiredService<IPageRepository>();
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
var sites = siteRepository.GetSites().ToList();
foreach (var site in sites.Where(item => !item.IsDeleted))
{
log += $"Processing Site: {site.Name}<br />";
// get global replace items in order by date/time submitted
var globalReplaceSettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId)
.Where(item => item.SettingName.StartsWith("GlobalReplace_"))
.OrderBy(item => item.SettingName);
if (globalReplaceSettings != null && globalReplaceSettings.Any())
{
// get first item
var globalReplace = JsonSerializer.Deserialize<GlobalReplace>(globalReplaceSettings.First().SettingValue);
var find = globalReplace.Find;
var replace = globalReplace.Replace;
var comparisonType = (globalReplace.CaseSensitive) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
log += $"Replacing: '{find}' With: '{replace}' Case Sensitive: {globalReplace.CaseSensitive}<br />";
var tenantId = tenantManager.GetTenant().TenantId;
tenantManager.SetAlias(tenantId, site.SiteId);
var changed = false;
if (site.Name != null && site.Name.Contains(find, comparisonType))
{
site.Name = site.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (site.HeadContent != null && site.HeadContent.Contains(find, comparisonType))
{
site.HeadContent = site.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (site.BodyContent != null && site.BodyContent.Contains(find, comparisonType))
{
site.BodyContent = site.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Site)
{
siteRepository.UpdateSite(site);
log += $"Site Updated<br />";
}
var pages = pageRepository.GetPages(site.SiteId);
var pageModules = pageModuleRepository.GetPageModules(site.SiteId);
// iterate pages
foreach (var page in pages)
{
// page properties
changed = false;
if (page.Name != null && page.Name.Contains(find, comparisonType))
{
page.Name = page.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (page.Title != null && page.Title.Contains(find, comparisonType))
{
page.Title = page.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (page.HeadContent != null && page.HeadContent.Contains(find, comparisonType))
{
page.HeadContent = page.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (page.BodyContent != null && page.BodyContent.Contains(find, comparisonType))
{
page.BodyContent = page.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Pages)
{
pageRepository.UpdatePage(page);
log += $"Page Updated: /{page.Path}<br />";
}
foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId))
{
// pagemodule properties
changed = false;
if (pageModule.Title != null && pageModule.Title.Contains(find, comparisonType))
{
pageModule.Title = pageModule.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Header != null && pageModule.Header.Contains(find, comparisonType))
{
pageModule.Header = pageModule.Header.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Footer != null && pageModule.Footer.Contains(find, comparisonType))
{
pageModule.Footer = pageModule.Footer.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Modules)
{
pageModuleRepository.UpdatePageModule(pageModule);
log += $"Module Updated: {pageModule.Title} - /{page.Path}<br />";
}
// module content
if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "")
{
Type moduleType = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType);
if (moduleType != null && moduleType.GetInterface(nameof(IPortable)) != null)
{
try
{
// module content
var moduleObject = ActivatorUtilities.CreateInstance(provider, moduleType);
var moduleContent = ((IPortable)moduleObject).ExportModule(pageModule.Module);
if (!string.IsNullOrEmpty(moduleContent) && moduleContent.Contains(find, comparisonType) && globalReplace.Content)
{
moduleContent = moduleContent.Replace(find, replace, comparisonType);
((IPortable)moduleObject).ImportModule(pageModule.Module, moduleContent, pageModule.Module.ModuleDefinition.Version);
log += $"Module Content Updated: {pageModule.Title} - /{page.Path}<br />";
}
}
catch (Exception ex)
{
log += $"Error Processing Module {pageModule.Module.ModuleDefinition.Name} - {ex.Message}<br />";
}
}
}
}
}
// remove global replace setting to prevent reprocessing
settingRepository.DeleteSetting(EntityNames.Site, globalReplaceSettings.First().SettingId);
}
else
{
log += $"No Criteria Provided<br />";
}
}
return log;
}
}
}

View File

@@ -38,7 +38,7 @@ namespace Oqtane.Infrastructure
// iterate through sites for current tenant // iterate through sites for current tenant
List<Site> sites = siteRepository.GetSites().ToList(); List<Site> sites = siteRepository.GetSites().ToList();
foreach (Site site in sites) foreach (Site site in sites.Where(item => !item.IsDeleted))
{ {
log += "Processing Notifications For Site: " + site.Name + "<br />"; log += "Processing Notifications For Site: " + site.Name + "<br />";
@@ -48,7 +48,7 @@ namespace Oqtane.Infrastructure
// get site settings // get site settings
var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1); var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1);
if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True") if (settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True")
{ {
bool valid = true; bool valid = true;
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic") if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
@@ -290,7 +290,7 @@ namespace Oqtane.Infrastructure
} }
else else
{ {
log += "Site Deleted Or SMTP Disabled In Site Settings<br />"; log += "SMTP Disabled In Site Settings<br />";
} }
} }
else else

View File

@@ -36,7 +36,7 @@ namespace Oqtane.Infrastructure
// iterate through sites for current tenant // iterate through sites for current tenant
List<Site> sites = siteRepository.GetSites().ToList(); List<Site> sites = siteRepository.GetSites().ToList();
foreach (Site site in sites) foreach (Site site in sites.Where(item => !item.IsDeleted))
{ {
log += "<br />Processing Site: " + site.Name + "<br />"; log += "<br />Processing Site: " + site.Name + "<br />";
int count; int count;

View File

@@ -40,7 +40,7 @@ namespace Oqtane.Infrastructure
var searchService = provider.GetRequiredService<ISearchService>(); var searchService = provider.GetRequiredService<ISearchService>();
var sites = siteRepository.GetSites().ToList(); var sites = siteRepository.GetSites().ToList();
foreach (var site in sites) foreach (var site in sites.Where(item => !item.IsDeleted))
{ {
log += $"Indexing Site: {site.Name}<br />"; log += $"Indexing Site: {site.Name}<br />";

View File

@@ -68,7 +68,7 @@ namespace Oqtane.Infrastructure
// get primary site // get primary site
var primarySite = sites.FirstOrDefault(item => item.SiteId == siteGroup.PrimarySiteId); var primarySite = sites.FirstOrDefault(item => item.SiteId == siteGroup.PrimarySiteId);
if (primarySite != null) if (primarySite != null && !primarySite.IsDeleted)
{ {
// update flag to prevent job from processing group again // update flag to prevent job from processing group again
siteGroup.Synchronize = false; siteGroup.Synchronize = false;
@@ -112,7 +112,7 @@ namespace Oqtane.Infrastructure
} }
else else
{ {
log += $"Site Group {siteGroup.Name} Has A PrimarySiteId {siteGroup.PrimarySiteId} Which Does Not Exist<br />"; log += $"Site Group {siteGroup.Name} Has A PrimarySiteId {siteGroup.PrimarySiteId} Which Does Not Exist Or Is Deleted<br />";
} }
} }

View File

@@ -607,6 +607,34 @@ namespace Oqtane.Infrastructure.SiteTemplates
} }
} }
}); });
pageTemplates.Add(new PageTemplate
{
Name = "Global Replace",
Parent = "Admin",
Order = 23,
Path = "admin/replace",
Icon = Icons.LoopSquare,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.GlobalReplace.Index).ToModuleDefinitionName(), Title = "Global Replace", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = ""
}
}
});
// host pages (order starts at 51) // host pages (order starts at 51)
pageTemplates.Add(new PageTemplate pageTemplates.Add(new PageTemplate

View File

@@ -93,6 +93,9 @@ namespace Oqtane.Infrastructure
case "10.0.4": case "10.0.4":
Upgrade_10_0_4(tenant, scope); Upgrade_10_0_4(tenant, scope);
break; break;
case "10.1.0":
Upgrade_10_1_0(tenant, scope);
break;
} }
} }
} }
@@ -602,6 +605,45 @@ namespace Oqtane.Infrastructure
RemoveAssemblies(tenant, assemblies, "10.0.4"); RemoveAssemblies(tenant, assemblies, "10.0.4");
} }
private void Upgrade_10_1_0(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>
{
new PageTemplate
{
Update = false,
Name = "Global Replace",
Parent = "Admin",
Order = 23,
Path = "admin/replace",
Icon = Icons.LoopSquare,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.GlobalReplace.Index).ToModuleDefinitionName(), Title = "Global Replace", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = ""
}
}
}
};
AddPagesToSites(scope, tenant, pageTemplates);
}
private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates) private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates)
{ {
var tenants = scope.ServiceProvider.GetRequiredService<ITenantManager>(); var tenants = scope.ServiceProvider.GetRequiredService<ITenantManager>();

View File

@@ -354,7 +354,7 @@ namespace Oqtane.Repository
moduledefinition = new ModuleDefinition moduledefinition = new ModuleDefinition
{ {
Name = Utilities.GetTypeNameLastSegment(modulecontroltype.Namespace, 0), Name = Utilities.GetTypeNameLastSegment(modulecontroltype.Namespace, 0),
Description = "Manage " + Utilities.GetTypeNameLastSegment(modulecontroltype.Namespace, 0), Description = Utilities.GetTypeNameLastSegment(modulecontroltype.Namespace, 0),
Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "") Categories = ((qualifiedModuleType.StartsWith("Oqtane.Modules.Admin.")) ? "Admin" : "")
}; };
} }
@@ -434,6 +434,12 @@ namespace Oqtane.Repository
moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + modulecontroltype.Assembly.GetName().Name + ";"); moduledefinition.ControlTypeRoutes += (action + "=" + modulecontroltype.FullName + ", " + modulecontroltype.Assembly.GetName().Name + ";");
} }
} }
// module title
if (modulecontroltype.Name == Constants.DefaultAction && !string.IsNullOrEmpty(modulecontrolobject.Title))
{
moduledefinition.Name = modulecontrolobject.Title;
moduledefinition.Description = "Manage " + moduledefinition.Name;
}
// check for Page attribute // check for Page attribute
var routeAttributes = modulecontroltype.GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>(); var routeAttributes = modulecontroltype.GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>();

View File

@@ -0,0 +1,43 @@
namespace Oqtane.Models
{
/// <summary>
/// Describes a global replace operation
/// </summary>
public class GlobalReplace
{
/// <summary>
/// text to replace
/// </summary>
public string Find { get; set; }
/// <summary>
/// replacement text
/// </summary>
public string Replace { get; set; }
/// <summary>
/// case sensitive
/// </summary>
public bool CaseSensitive { get; set; }
/// <summary>
/// include site properties
/// </summary>
public bool Site { get; set; }
/// <summary>
/// include page properties
/// </summary>
public bool Pages { get; set; }
/// <summary>
/// include module properties
/// </summary>
public bool Modules { get; set; }
/// <summary>
/// include content
/// </summary>
public bool Content { get; set; }
}
}