Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

SettingService.cs

using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Configuration;
using Nop.Core.Domain.Configuration;
using Nop.Data;

namespace Nop.Services.Configuration;

/// 
/// Setting manager
/// 
public partial class SettingService : ISettingService
{
    #region Fields

    protected readonly IRepository _settingRepository;
    protected readonly IStaticCacheManager _staticCacheManager;

    #endregion

    #region Ctor

    public SettingService(IRepository settingRepository,
        IStaticCacheManager staticCacheManager)
    {
        _settingRepository = settingRepository;
        _staticCacheManager = staticCacheManager;
    }

    #endregion

    #region Utilities

    /// 
    /// Gets all settings
    /// 
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the settings
    /// 
    protected virtual async Task>> GetAllSettingsDictionaryAsync()
    {
        return await _staticCacheManager.GetAsync(NopSettingsDefaults.SettingsAllAsDictionaryCacheKey, async () =>
        {
            var settings = await GetAllSettingsAsync();

            var dictionary = new Dictionary>();
            foreach (var s in settings)
            {
                var resourceName = s.Name.ToLowerInvariant();
                var settingForCaching = new Setting
                {
                    Id = s.Id,
                    Name = s.Name,
                    Value = s.Value,
                    StoreId = s.StoreId
                };
                if (!dictionary.TryGetValue(resourceName, out var value))
                    //first setting
                    dictionary.Add(resourceName, new List
                    {
                        settingForCaching
                    });
                else
                    //already added
                    //most probably it's the setting with the same name but for some certain store (storeId > 0)
                    value.Add(settingForCaching);
            }

            return dictionary;
        });
    }

    /// 
    /// Gets all settings
    /// 
    /// 
    /// Settings
    /// 
    protected virtual IDictionary> GetAllSettingsDictionary()
    {
        return _staticCacheManager.Get(NopSettingsDefaults.SettingsAllAsDictionaryCacheKey, () =>
        {
            var settings = GetAllSettings();

            var dictionary = new Dictionary>();
            foreach (var s in settings)
            {
                var resourceName = s.Name.ToLowerInvariant();
                var settingForCaching = new Setting
                {
                    Id = s.Id,
                    Name = s.Name,
                    Value = s.Value,
                    StoreId = s.StoreId
                };
                if (!dictionary.TryGetValue(resourceName, out var value))
                    //first setting
                    dictionary.Add(resourceName, new List
                    {
                        settingForCaching
                    });
                else
                    //already added
                    //most probably it's the setting with the same name but for some certain store (storeId > 0)
                    value.Add(settingForCaching);
            }

            return dictionary;
        });
    }

    /// 
    /// Set setting value
    /// 
    /// Type
    /// Key
    /// Value
    /// Store identifier
    /// A value indicating whether to clear cache after setting update
    /// A task that represents the asynchronous operation
    protected virtual async Task SetSettingAsync(Type type, string key, object value, int storeId = 0, bool clearCache = true)
    {
        ArgumentNullException.ThrowIfNull(key);
        key = key.Trim().ToLowerInvariant();
        var valueStr = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value);

        var allSettings = await GetAllSettingsDictionaryAsync();
        var settingForCaching = allSettings.TryGetValue(key, out var settings) ?
            settings.FirstOrDefault(x => x.StoreId == storeId) : null;
        if (settingForCaching != null)
        {
            //update
            var setting = await GetSettingByIdAsync(settingForCaching.Id);
            setting.Value = valueStr;
            await UpdateSettingAsync(setting, clearCache);
        }
        else
        {
            //insert
            var setting = new Setting
            {
                Name = key,
                Value = valueStr,
                StoreId = storeId
            };
            await InsertSettingAsync(setting, clearCache);
        }
    }

    /// 
    /// Set setting value
    /// 
    /// Type
    /// Key
    /// Value
    /// Store identifier
    /// A value indicating whether to clear cache after setting update
    protected virtual void SetSetting(Type type, string key, object value, int storeId = 0, bool clearCache = true)
    {
        ArgumentNullException.ThrowIfNull(key);
        key = key.Trim().ToLowerInvariant();
        var valueStr = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value);

        var allSettings = GetAllSettingsDictionary();
        var settingForCaching = allSettings.TryGetValue(key, out var settings) ?
            settings.FirstOrDefault(x => x.StoreId == storeId) : null;
        if (settingForCaching != null)
        {
            //update
            var setting = GetSettingById(settingForCaching.Id);
            setting.Value = valueStr;
            UpdateSetting(setting, clearCache);
        }
        else
        {
            //insert
            var setting = new Setting
            {
                Name = key,
                Value = valueStr,
                StoreId = storeId
            };
            InsertSetting(setting, clearCache);
        }
    }

    #endregion

    #region Methods

    /// 
    /// Adds a setting
    /// 
    /// Setting
    /// A value indicating whether to clear cache after setting update
    /// A task that represents the asynchronous operation
    public virtual async Task InsertSettingAsync(Setting setting, bool clearCache = true)
    {
        await _settingRepository.InsertAsync(setting);

        //cache
        if (clearCache)
            await ClearCacheAsync();
    }

    /// 
    /// Adds a setting
    /// 
    /// Setting
    /// A value indicating whether to clear cache after setting update
    public virtual void InsertSetting(Setting setting, bool clearCache = true)
    {
        _settingRepository.Insert(setting);

        //cache
        if (clearCache)
            ClearCache();
    }

    /// 
    /// Updates a setting
    /// 
    /// Setting
    /// A value indicating whether to clear cache after setting update
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateSettingAsync(Setting setting, bool clearCache = true)
    {
        ArgumentNullException.ThrowIfNull(setting);

        await _settingRepository.UpdateAsync(setting);

        //cache
        if (clearCache)
            await ClearCacheAsync();
    }

    /// 
    /// Updates a setting
    /// 
    /// Setting
    /// A value indicating whether to clear cache after setting update
    public virtual void UpdateSetting(Setting setting, bool clearCache = true)
    {
        ArgumentNullException.ThrowIfNull(setting);

        _settingRepository.Update(setting);

        //cache
        if (clearCache)
            ClearCache();
    }

    /// 
    /// Deletes a setting
    /// 
    /// Setting
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSettingAsync(Setting setting)
    {
        await _settingRepository.DeleteAsync(setting);

        //cache
        await ClearCacheAsync();
    }

    /// 
    /// Deletes a setting
    /// 
    /// Setting
    public virtual void DeleteSetting(Setting setting)
    {
        _settingRepository.Delete(setting);

        //cache
        ClearCache();
    }

    /// 
    /// Deletes settings
    /// 
    /// Settings
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSettingsAsync(IList settings)
    {
        await _settingRepository.DeleteAsync(settings);

        //cache
        await ClearCacheAsync();
    }

    /// 
    /// Gets a setting by identifier
    /// 
    /// Setting identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the setting
    /// 
    public virtual async Task GetSettingByIdAsync(int settingId)
    {
        return await _settingRepository.GetByIdAsync(settingId, cache => default);
    }

    /// 
    /// Gets a setting by identifier
    /// 
    /// Setting identifier
    /// 
    /// The setting
    /// 
    public virtual Setting GetSettingById(int settingId)
    {
        return _settingRepository.GetById(settingId, cache => default);
    }

    /// 
    /// Get setting by key
    /// 
    /// Key
    /// Store identifier
    /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the setting
    /// 
    public virtual async Task GetSettingAsync(string key, int storeId = 0, bool loadSharedValueIfNotFound = false)
    {
        if (string.IsNullOrEmpty(key))
            return null;

        var settings = await GetAllSettingsDictionaryAsync();
        key = key.Trim().ToLowerInvariant();
        if (!settings.TryGetValue(key, out var value))
            return null;

        var settingsByKey = value;
        var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId);

        //load shared value?
        if (setting == null && storeId > 0 && loadSharedValueIfNotFound)
            setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0);

        return setting != null ? await GetSettingByIdAsync(setting.Id) : null;
    }

    /// 
    /// Get setting by key
    /// 
    /// Key
    /// Store identifier
    /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found
    /// 
    /// The setting
    /// 
    public virtual Setting GetSetting(string key, int storeId = 0, bool loadSharedValueIfNotFound = false)
    {
        if (string.IsNullOrEmpty(key))
            return null;

        var settings = GetAllSettingsDictionary();
        key = key.Trim().ToLowerInvariant();
        if (!settings.TryGetValue(key, out var value))
            return null;

        var settingsByKey = value;
        var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId);

        //load shared value?
        if (setting == null && storeId > 0 && loadSharedValueIfNotFound)
            setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0);

        return setting != null ? GetSettingById(setting.Id) : null;
    }

    /// 
    /// Get setting value by key
    /// 
    /// Type
    /// Key
    /// Default value
    /// Store identifier
    /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the setting value
    /// 
    public virtual async Task GetSettingByKeyAsync(string key, T defaultValue = default,
        int storeId = 0, bool loadSharedValueIfNotFound = false)
    {
        if (string.IsNullOrEmpty(key))
            return defaultValue;

        var settings = await GetAllSettingsDictionaryAsync();
        key = key.Trim().ToLowerInvariant();
        if (!settings.TryGetValue(key, out var value))
            return defaultValue;

        var settingsByKey = value;
        var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId);

        //load shared value?
        if (setting == null && storeId > 0 && loadSharedValueIfNotFound)
            setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0);

        return setting != null ? CommonHelper.To(setting.Value) : defaultValue;
    }

    /// 
    /// Get setting value by key
    /// 
    /// Type
    /// Key
    /// Default value
    /// Store identifier
    /// A value indicating whether a shared (for all stores) value should be loaded if a value specific for a certain is not found
    /// 
    /// Setting value
    /// 
    public virtual T GetSettingByKey(string key, T defaultValue = default,
        int storeId = 0, bool loadSharedValueIfNotFound = false)
    {
        if (string.IsNullOrEmpty(key))
            return defaultValue;

        var settings = GetAllSettingsDictionary();
        key = key.Trim().ToLowerInvariant();
        if (!settings.TryGetValue(key, out var value))
            return defaultValue;

        var settingsByKey = value;
        var setting = settingsByKey.FirstOrDefault(x => x.StoreId == storeId);

        //load shared value?
        if (setting == null && storeId > 0 && loadSharedValueIfNotFound)
            setting = settingsByKey.FirstOrDefault(x => x.StoreId == 0);

        return setting != null ? CommonHelper.To(setting.Value) : defaultValue;
    }

    /// 
    /// Set setting value
    /// 
    /// Type
    /// Key
    /// Value
    /// Store identifier
    /// A value indicating whether to clear cache after setting update
    /// A task that represents the asynchronous operation
    public virtual async Task SetSettingAsync(string key, T value, int storeId = 0, bool clearCache = true)
    {
        await SetSettingAsync(typeof(T), key, value, storeId, clearCache);
    }

    /// 
    /// Set setting value
    /// 
    /// Type
    /// Key
    /// Value
    /// Store identifier
    /// A value indicating whether to clear cache after setting update
    public virtual void SetSetting(string key, T value, int storeId = 0, bool clearCache = true)
    {
        SetSetting(typeof(T), key, value, storeId, clearCache);
    }

    /// 
    /// Gets all settings
    /// 
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the settings
    /// 
    public virtual async Task> GetAllSettingsAsync()
    {
        var settings = await _settingRepository.GetAllAsync(query =>
        {
            return from s in query
                orderby s.Name, s.StoreId
                select s;
        }, cache => default);

        return settings;
    }

    /// 
    /// Gets all settings
    /// 
    /// 
    /// Settings
    /// 
    public virtual IList GetAllSettings()
    {
        var settings = _settingRepository.GetAll(query => from s in query
            orderby s.Name, s.StoreId
            select s, cache => default);

        return settings;
    }

    /// 
    /// Determines whether a setting exists
    /// 
    /// Entity type
    /// Property type
    /// Entity
    /// Key selector
    /// Store identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the true -setting exists; false - does not exist
    /// 
    public virtual async Task SettingExistsAsync(T settings,
        Expression> keySelector, int storeId = 0)
        where T : ISettings, new()
    {
        var key = GetSettingKey(settings, keySelector);

        var setting = await GetSettingByKeyAsync(key, storeId: storeId);
        return setting != null;
    }

    /// 
    /// Determines whether a setting exists
    /// 
    /// Entity type
    /// Property type
    /// Entity
    /// Key selector
    /// Store identifier
    /// 
    /// The true -setting exists; false - does not exist
    /// 
    public virtual bool SettingExists(T settings,
        Expression> keySelector, int storeId = 0)
        where T : ISettings, new()
    {
        var key = GetSettingKey(settings, keySelector);

        var setting = GetSettingByKey(key, storeId: storeId);
        return setting != null;
    }

    /// 
    /// Load settings
    /// 
    /// Type
    /// Store identifier for which settings should be loaded
    /// A task that represents the asynchronous operation
    public virtual async Task LoadSettingAsync(int storeId = 0) where T : ISettings, new()
    {
        return (T)await LoadSettingAsync(typeof(T), storeId);
    }

    /// 
    /// Load settings
    /// 
    /// Type
    /// Store identifier for which settings should be loaded
    public virtual T LoadSetting(int storeId = 0) where T : ISettings, new()
    {
        return (T)LoadSetting(typeof(T), storeId);
    }

    /// 
    /// Load settings
    /// 
    /// Type
    /// Store identifier for which settings should be loaded
    /// A task that represents the asynchronous operation
    public virtual async Task LoadSettingAsync(Type type, int storeId = 0)
    {
        var settings = Activator.CreateInstance(type);

        if (!DataSettingsManager.IsDatabaseInstalled())
            return settings as ISettings;

        foreach (var prop in type.GetProperties())
        {
            // get properties we can read and write to
            if (!prop.CanRead || !prop.CanWrite)
                continue;

            var key = type.Name + "." + prop.Name;
            //load by store
            var setting = await GetSettingByKeyAsync(key, storeId: storeId, loadSharedValueIfNotFound: true);
            if (setting == null)
                continue;

            if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string)))
                continue;

            if (!TypeDescriptor.GetConverter(prop.PropertyType).IsValid(setting))
                continue;

            var value = TypeDescriptor.GetConverter(prop.PropertyType).ConvertFromInvariantString(setting);

            //set property
            prop.SetValue(settings, value, null);
        }

        return settings as ISettings;
    }

    /// 
    /// Load settings
    /// 
    /// Type
    /// Store identifier for which settings should be loaded
    /// Settings
    public virtual ISettings LoadSetting(Type type, int storeId = 0)
    {
        var settings = Activator.CreateInstance(type);

        if (!DataSettingsManager.IsDatabaseInstalled())
            return settings as ISettings;

        foreach (var prop in type.GetProperties())
        {
            // get properties we can read and write to
            if (!prop.CanRead || !prop.CanWrite)
                continue;

            var key = type.Name + "." + prop.Name;
            //load by store
            var setting = GetSettingByKey(key, storeId: storeId, loadSharedValueIfNotFound: true);
            if (setting == null)
                continue;

            if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string)))
                continue;

            if (!TypeDescriptor.GetConverter(prop.PropertyType).IsValid(setting))
                continue;

            var value = TypeDescriptor.GetConverter(prop.PropertyType).ConvertFromInvariantString(setting);

            //set property
            prop.SetValue(settings, value, null);
        }

        return settings as ISettings;
    }

    /// 
    /// Save settings object
    /// 
    /// Type
    /// Store identifier
    /// Setting instance
    /// A task that represents the asynchronous operation
    public virtual async Task SaveSettingAsync(T settings, int storeId = 0) where T : ISettings, new()
    {
        /* We do not clear cache after each setting update.
         * This behavior can increase performance because cached settings will not be cleared 
         * and loaded from database after each update */
        foreach (var prop in typeof(T).GetProperties())
        {
            // get properties we can read and write to
            if (!prop.CanRead || !prop.CanWrite)
                continue;

            if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string)))
                continue;

            var key = typeof(T).Name + "." + prop.Name;
            var value = prop.GetValue(settings, null);
            if (value != null)
                await SetSettingAsync(prop.PropertyType, key, value, storeId, false);
            else
                await SetSettingAsync(key, string.Empty, storeId, false);
        }

        //and now clear cache
        await ClearCacheAsync();
    }

    /// 
    /// Save settings object
    /// 
    /// Type
    /// Store identifier
    /// Setting instance
    public virtual void SaveSetting(T settings, int storeId = 0) where T : ISettings, new()
    {
        /* We do not clear cache after each setting update.
         * This behavior can increase performance because cached settings will not be cleared 
         * and loaded from database after each update */
        foreach (var prop in typeof(T).GetProperties())
        {
            // get properties we can read and write to
            if (!prop.CanRead || !prop.CanWrite)
                continue;

            if (!TypeDescriptor.GetConverter(prop.PropertyType).CanConvertFrom(typeof(string)))
                continue;

            var key = typeof(T).Name + "." + prop.Name;
            var value = prop.GetValue(settings, null);
            if (value != null)
                SetSetting(prop.PropertyType, key, value, storeId, false);
            else
                SetSetting(key, string.Empty, storeId, false);
        }

        //and now clear cache
        ClearCache();
    }

    /// 
    /// Save settings object
    /// 
    /// Entity type
    /// Property type
    /// Settings
    /// Key selector
    /// Store ID
    /// A value indicating whether to clear cache after setting update
    /// A task that represents the asynchronous operation
    public virtual async Task SaveSettingAsync(T settings,
        Expression> keySelector,
        int storeId = 0, bool clearCache = true) where T : ISettings, new()
    {
        if (keySelector.Body is not MemberExpression member)
            throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property.");

        var propInfo = member.Member as PropertyInfo
                       ?? throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property.");

        var key = GetSettingKey(settings, keySelector);
        var value = (TPropType)propInfo.GetValue(settings, null);
        if (value != null)
            await SetSettingAsync(key, value, storeId, clearCache);
        else
            await SetSettingAsync(key, string.Empty, storeId, clearCache);
    }

    /// 
    /// Save settings object
    /// 
    /// Entity type
    /// Property type
    /// Settings
    /// Key selector
    /// Store ID
    /// A value indicating whether to clear cache after setting update
    public virtual void SaveSetting(T settings,
        Expression> keySelector,
        int storeId = 0, bool clearCache = true) where T : ISettings, new()
    {
        if (keySelector.Body is not MemberExpression member)
            throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property.");

        var propInfo = member.Member as PropertyInfo
                       ?? throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property.");

        var key = GetSettingKey(settings, keySelector);
        var value = (TPropType)propInfo.GetValue(settings, null);
        if (value != null)
            SetSetting(key, value, storeId, clearCache);
        else
            SetSetting(key, string.Empty, storeId, clearCache);
    }

    /// 
    /// Save settings object (per store). If the setting is not overridden per store then it'll be delete
    /// 
    /// Entity type
    /// Property type
    /// Settings
    /// Key selector
    /// A value indicating whether to setting is overridden in some store
    /// Store ID
    /// A value indicating whether to clear cache after setting update
    /// A task that represents the asynchronous operation
    public virtual async Task SaveSettingOverridablePerStoreAsync(T settings,
        Expression> keySelector,
        bool overrideForStore, int storeId = 0, bool clearCache = true) where T : ISettings, new()
    {
        if (overrideForStore || storeId == 0)
            await SaveSettingAsync(settings, keySelector, storeId, clearCache);
        else if (storeId > 0)
            await DeleteSettingAsync(settings, keySelector, storeId);
    }

    /// 
    /// Delete all settings
    /// 
    /// Type
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSettingAsync() where T : ISettings, new()
    {
        var settingsToDelete = new List();
        var allSettings = await GetAllSettingsAsync();
        foreach (var prop in typeof(T).GetProperties())
        {
            var key = typeof(T).Name + "." + prop.Name;
            settingsToDelete.AddRange(allSettings.Where(x => x.Name.Equals(key, StringComparison.InvariantCultureIgnoreCase)));
        }

        await DeleteSettingsAsync(settingsToDelete);
    }

    /// 
    /// Delete settings object
    /// 
    /// Entity type
    /// Property type
    /// Settings
    /// Key selector
    /// Store ID
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSettingAsync(T settings,
        Expression> keySelector, int storeId = 0) where T : ISettings, new()
    {
        var key = GetSettingKey(settings, keySelector);
        key = key.Trim().ToLowerInvariant();

        var allSettings = await GetAllSettingsDictionaryAsync();
        var settingForCaching = allSettings.TryGetValue(key, out var settings_) ?
            settings_.FirstOrDefault(x => x.StoreId == storeId) : null;
        if (settingForCaching == null)
            return;

        //update
        var setting = await GetSettingByIdAsync(settingForCaching.Id);
        await DeleteSettingAsync(setting);
    }

    /// 
    /// Clear cache
    /// 
    /// A task that represents the asynchronous operation
    public virtual async Task ClearCacheAsync()
    {
        await _staticCacheManager.RemoveByPrefixAsync(NopEntityCacheDefaults.Prefix);
    }

    /// 
    /// Clear cache
    /// 
    public virtual void ClearCache()
    {
        _staticCacheManager.RemoveByPrefix(NopEntityCacheDefaults.Prefix);
    }

    /// 
    /// Get setting key (stored into database)
    /// 
    /// Type of settings
    /// Property type
    /// Settings
    /// Key selector
    /// Key
    public virtual string GetSettingKey(TSettings settings, Expression> keySelector)
        where TSettings : ISettings, new()
    {
        if (keySelector.Body is not MemberExpression member)
            throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property.");

        if (member.Member is not PropertyInfo propInfo)
            throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property.");

        var key = $"{typeof(TSettings).Name}.{propInfo.Name}";

        return key;
    }
    #endregion
}