Author

Guss

Browsing

Sometimes, your User Control needs to get some unique identifier from the page it is hosted in.
Let’s assume you have a side menu that is built server-side, and all the fiddling with friendly URL’s and routes is just not enough of a constant for you to set the active item in the menu.

On the page, declare a variable. In the case below, I call it PageId.
This is because my side menu is built from the database and table.Menu.PageId and table.Page.Id is in a relationship.
I guess you would call this a database first approach because every page in my system is actually listed in the table.Pages and from there you have table.Menu, table.RolePages, table.UserRoles etc, all interrelated.
I therefore set PageId = “5”, because in table.Pages.Id its the primary key. (Of course, if you do not want to ship the DB with your app, you can later use reflection and let the page register itself inside blank-fresh database)

namespace MyAppName.Pages
{
    public partial class MyPageName : System.Web.UI.Page
    {
        public string PageId { get { return "5"; } }

        protected void Page_Load(object sender, EventArgs e)
        {
        }
    }
}

Each participating page in your application now has a unique value for PageId. Over to the ASCX user control.

  1. See the variable at the top called ParentPageId, it’s storing in the ViewState so that I do not lose it over postback etc.
  2. Then in the Page Load event, only executing for !IsPosback, I set the ParentPageId. See SetParentPageId().
  3. Later, in the logic of building the menu, I then compare the PageId coming from the database, with ParentPageId, and can make the list item active.
    if (pageid == ParentPageId) { li.Attributes.Add(“class”, “active”); }
  4. After Note: return ViewState[“ParentPageId”] != null ? (string)ViewState[“ParentPageId”] : “0”;    –> do NOT make this return ViewState[“ParentPageId”] != null ? (string)ViewState[“ParentPageId”] : “”;
    Do not pass back an Empty string, as this will try and enable items that do not have PageIds (some of your pages may not have the PageId set, and in that case, pass some value that you know does not exist, like “0” or “-999”.
using System;
using System.Configuration;
using System.Data;
using System.Data.SQLite;
using System.Linq;
using System.Web.UI;
using System.Web.UI.HtmlControls;

namespace MyAppName.Controls
{
    public partial class Sidebar : System.Web.UI.UserControl
    {
        private string ParentPageId
        {
            get
            {
                return ViewState["ParentPageId"] != null ? (string)ViewState["ParentPageId"] : "0";
            }
            set
            {
                ViewState["ParentPageId"] = value;
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                SetParentPageId();
                BuildSidebarMenu();
            }
        }

        protected void SetParentPageId()
        {
            Type t = Page.GetType();
            System.Reflection.PropertyInfo[] pi = t.GetProperties();
            foreach (System.Reflection.PropertyInfo pinfo in pi)
            {
                if (pinfo.Name == "PageId")
                {
                    ParentPageId = (string)pinfo.GetValue(Page, null);
                    break;
                }
            }
        }

        protected void BuildSidebarMenu()
        {
            // My code to build the sidebar
        }

        protected DataTable GetSidebarMenuTable()
        {
            string sql = @"SELECT ....; ";
            DataSet ds = new DataSet();
            // My code to get the data

            DataTable dt = ds.Tables[0];
            return dt;
        }

        protected HtmlGenericControl ListItem(string id, string menutitle, string menuicon, string page, string pageid, DataTable dt)
        {
            DataRow[] matchingchildrenrows = dt.Select("MenuParentId = " + id);
            HtmlGenericControl li = new HtmlGenericControl("li");
            if (!String.IsNullOrEmpty(pageid))
            {
                if (!String.IsNullOrEmpty(ParentPageId))
                {
                    if (pageid == ParentPageId) { li.Attributes.Add("class", "active"); }
                }
            }
            // The rest of my code here to set classes, create hyperlink-, text-, icon- elements, etc.
            // ...
            // ...
            return li;
        }

    }
}

The same approach can then used form page permissions.

Pages got Ids and your relationship between the logged in UserId, RoleId, PageRoles, etc. then being used to control page access, all pivoting around a simple ID.

There’s may chart out there, free and very expensive ones. You would have expected slightly cheaper prices on the commercials ones, seeing that representing data visually in a chart, is as old as the list box. Lucky, there is Chart.js, which is open source.

It’s a javascript library, so using it with ASP.NET webforms, need some tricks. Making jQuery calls via AJAX is our medicine.

Something else I found on the internet, there is everyone doing it differently and nobody has a complete solution. To be dynamic, it is not just the data that should be dynamics, but also things like chart type, chart options, width, height, container titles and footers, etc. I worked through the night and quickly put together my take on it. Yea, lots of improvements needed as I just slapped things together to get it to work. Cleanup can come later.

The website I’m working on is using bootstrap, charts should be responsive, and it’s tiled together with other elements using Masonry.

Clientside

This is my compact client-side script:

var uCharts = [56];
        var i; for (i = 0; i < uCharts.length; i++) { CreateCharts(uCharts[i]); TileGrid(); }

        function CreateCharts(id) {
            try {
                var ctx = $('#ucc' + id)[0].getContext("2d");
                return $.ajax({
                    url: "/Services/ChartData.asmx/Load", data: '{UserChartId: ' + id + '}',
                    type: "POST", contentType: "application/json; charset=utf-8", dataType: "json",
                    success: function (res, textStatus) {
                        var d = JSON.parse(res.d[0]); var o = JSON.parse(res.d[1]); var c = res.d[6];
                        var t = res.d[2]; var f = res.d[3]; var w = res.d[4]; var h = res.d[5];
                        $('#ucc' + id).attr('width', w).attr('height', h);
                        var myChart = new Chart(ctx, { type: c, data: d, options: o });
                        $('#uct' + id).text(t); $('#ucf' + id).text(f);
                        TileGrid();
                    },
                    error: function (res, textStatus) { }
                });
            }
            catch (err) { }
            finally { }
        }

        function TileGrid() {
            var $grid = $('.grid').masonry({
                itemSelector: '.grid-item',
                columnWidth: '.grid-sizer',
                percentPosition: true,
                horizontalOrder: false,
            });
        }
  1. First I have an array of ids (uCharts). These are the ids of the charts a logged-in user want to display on his dashboard.
  2. I loop through these ids and call the cart creator function CreatCharts for each of the charts. These charts are inside a masonry titled grid, so I call TileGrid each time after a tile was created.
  3. The jQuery ajax call get back and array. Usually “d” only contains the JSON data, but I have added options, title, footer, width, height and chart type to the string being return so that everthing about the chart is done server-side.
  4. When I call the web service, I also pass the users’ chart id, so that the web service can do its thing server side.

Server Side

The service receives the identifier (user’s chart id in my case), and use this id to get information about this chart from wherever needed. In the code below, I have just hard coded some dummy data, but in real life, I will go and fetch the data and chart options from the database.

using System.Web.Script.Serialization;
using System.Web.Services;

namespace WLMT.Services
{
    [System.Web.Script.Services.ScriptService]
    public class ChartData : System.Web.Services.WebService
    {
        [WebMethod(EnableSession = true)]
        public string[] Load(int UserChartId)
        {
            var data = new
            {
                labels = new[] {
                    "South Western", "Western", "Northern", "North Eastern", "Eastern"
                },
                datasets = new[] {
                    new {
                        label = "2016 - 2017",
                        backgroundColor = new[] { "rgba(220, 220, 220, 0.5)" },
                        pointBorderColor = "#fff",
                        data = new[] {"312","260","350","275","230"}
                    },
                    new {
                        label = "2017 - 2018",
                        backgroundColor = new[] { "#3c8dbc", "#00c0ef", "#00a65a", "#f39c12", "#605ca8" },
                        pointBorderColor = "#fff",
                        data = new[] {"350","280","375","300","260"}
                    }
                }
            };

            var options = new
            {
                responsive = true,
                maintainAspectRatio = true,
                scales = new
                {
                    xAxes = new[]
                    {
                       new {
                           gridLines = new { display = true }
                       }
                    },
                    yAxes = new[]
                    {
                        new {
                            gridLines = new { display = true },
                            scaleLabel = new { display = true, labelString = "Millions" }
                        }
                    }
                }
            };
            string ChartTitle = "ChartJS Responsive & Aspect Ratio Test";
            string ChartFooter = "Data is fetch with a web service from client side";
            string Width = "400";
            string Height = "250";
            string ChartType = "bar";

            string[] result = { data.ToJSON(), options.ToJSON(), ChartTitle, ChartFooter, Width, Height, ChartType };
            return result;
        }
    }
+}

Array[0] and Array[1] values is converted to JSON. The other array values just plain strings. If you go back to the client-side script, you will see that I read all array values from the response (res.d[0] – res.d[6]) and use it where applicable.

My JSONHelper is in another class, see below.

Helper

using System.Web.Script.Serialization;

namespace WLMT
{
    public static class JSONHelper
    {
        public static string ToJSON(this object obj)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            return serializer.Serialize(obj);
        }

        public static string ToJSON(this object obj, int recursionDepth)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            serializer.RecursionLimit = recursionDepth;
            return serializer.Serialize(obj);
        }
    }
}

The end result is shown below.

The width and height passed to the chart are only for maintaining the aspect ratio, as Chart.js has “responsive” = true and “maintainAspectRatio” = true set.

In my HTML, I have a bootstrap card, where card title is used for the title, body for the chart canvas etc. These placeholder containers are created by the page, server-side, and so is the javascript array I place on the page. So each placeholder card will be in the HTML when in reach the client browser, from where the client-side script will do its thing to populate the placeholder with its chart.

Stuff can go wrong, as charts are created by users using a web-based wizard. For now, the client-side code is in a try-catch block, so that all client-side stuff does not fall over when something goes wrong here. In the catch, I will be removing the empty bootstrap placeholder card. The issue will be logged to ELMAH, from where action can be taken. A notification will tell the user that the card is not shown due to…

Configuring ASP.NET WebForms, Hangfire and Sqlite storage will work with the following setup:

First, install these packages

<package id="Hangfire.AspNet" version="0.2.0" targetFramework="net472" />
<package id="Hangfire.Core" version="1.7.19" targetFramework="net472" />
<package id="Hangfire.Dashboard.Authorization" version="3.0.0" targetFramework="net472" />
<package id="Hangfire.Storage.SQLite" version="0.2.5" targetFramework="net472" />

The last one is the non-abandoned one with the description of “An Alternative SQLite Storage for Hangfire”
When you install this Hangfire.Storage.SQLite package, it will auto-install these:

<package id="sqlite-net-pcl" version="1.6.292" targetFramework="net472" />
<package id="SQLitePCLRaw.bundle_green" version="1.1.13" targetFramework="net472" />
<package id="SQLitePCLRaw.core" version="1.1.13" targetFramework="net472" />
<package id="SQLitePCLRaw.lib.e_sqlite3.linux" version="1.1.13" targetFramework="net472" />
<package id="SQLitePCLRaw.lib.e_sqlite3.osx" version="1.1.13" targetFramework="net472" />
<package id="SQLitePCLRaw.lib.e_sqlite3.v110_xp" version="1.1.13" targetFramework="net472" />
<package id="SQLitePCLRaw.provider.e_sqlite3.net45" version="1.1.13" targetFramework="net472" />
<package id="Stub.System.Data.SQLite.Core.NetFramework" version="1.0.113.3" targetFramework="net472" />

I guess some of them is not required, but hell, I’ve spent to much time on getting all this to work…

Now, use something like SQLiteStudio (https://sqlitestudio.pl) and create an empty database called Hangfire.db. In my code example, I called it “Jobs.db”)

Now change your startup.cs to look something like this:

using Microsoft.Owin;
using Owin;
using Hangfire;
using System;
using Hangfire.Dashboard;
using System.Web;
using Hangfire.Storage.SQLite;

[assembly: OwinStartupAttribute(typeof(WLMT.Startup))]

namespace WLMT
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
            string hangfireSqliteDb = String.Format(@"{0}App_Data/Jobs.db", AppDomain.CurrentDomain.BaseDirectory);
            var storeageOptions = new SQLiteStorageOptions();
            storeageOptions.QueuePollInterval = TimeSpan.FromSeconds(30);
            GlobalConfiguration.Configuration.UseSQLiteStorage(hangfireSqliteDb, storeageOptions);
            var backgrounJobServerOption = new BackgroundJobServerOptions { WorkerCount = 1 };
            var dashboardOptions = new DashboardOptions
            {
                Authorization = new[] { new JobDashboardAuthorization() },
                AppPath = VirtualPathUtility.ToAbsolute("~"),
                DashboardTitle = "WLMT",
                DisplayStorageConnectionString = false
            };
            app.UseHangfireDashboard("/jobs", dashboardOptions);
            app.UseHangfireServer(backgrounJobServerOption);
        }
    }
}

You almost there. You will see that I have my own Authorization. This is because the default method did not work in Webforms. Its as if some sequence of something is wrong and I kept getting now page displaying due to Authentication failure. To fix this, create your own IDashboardAuthorizationFilter. Create a new class in your Code / App_Code folder, or wherever…. here is how my class looks like:

using Hangfire.Annotations;
using Hangfire.Dashboard;
using System.Web;

namespace WLMT
{
    public class JobDashboardAuthorization : IDashboardAuthorizationFilter
    {
        public bool Authorize([NotNull] DashboardContext context)
        {
            bool hasAccess;
            try
            {
                hasAccess = HttpContext.Current.User.IsInRole("Admin");
            }
            catch
            {
                hasAccess = false;
            }
            return hasAccess;
        }
    }
}

That’s it!

In my example, once logged in with a user that belongs to the “Admin” role, I can navigate to /jobs (see my path app.UseHangfireDashboard(“/jobs”, dashboardOptions);) and Hangfire GUI display and everything seems to work.

Take note of this line: GlobalConfiguration.Configuration.UseSQLiteStorage(hangfireSqliteDb, storeageOptions);

UseSQLiteStorage(hangfireSqliteDb, storeageOptions); hangfireSqliteDb is the connection string.
Do not try things like File=… or Data Source=… like you do in other connection string. This is NOT A CONNECTION STRING. Its is FILEPATH.

You need to provide a path like “c:\yourdirectory\your.db”.
Because this is serverless and runs within my web application I just defined my path like this:

string hangfireSqliteDb = String.Format(@"{0}App_Data/Jobs.db", AppDomain.CurrentDomain.BaseDirectory);

 

 

 

We all know the code (all over the internet) that is used to split a csv string, based on the delimiter. I good example is:

ALTER FUNCTION [dbo].[ToDelimitedTable] (@Value NVARCHAR(MAX), @Delimiter NVARCHAR(3) = ',')
RETURNS @t TABLE (id INT IDENTITY(1, 1), val NVARCHAR(MAX))
AS
    BEGIN
        DECLARE @xml XML;
        SET @xml = N'<root><r>' + REPLACE(@Value, @Delimiter, '</r><r>') + '</r></root>';
        INSERT INTO @t (val)
        SELECT
            RTRIM(LTRIM(r.value('.', 'NVARCHAR(max)'))) AS item
        FROM @xml.nodes('//root/r') AS records(r);
        RETURN;
    END;

If you run this SQL:

SELECT * FROM [dbo].[ToDelimitedTable]('aaaa,bbbb,cccc,dddd', ',');

then your output is:

id val
1 aaaa
2 bbbb
3 cccc
4 dddd

Extracting Tokens

The next challenge is extracting all tokens within a text string. What I mean with tokens is ([this]):
“Dear [Firstname]. How are you today? We would like to confirm [Product] is working as expected. Regards [Sender]

Now I know the following code is slow CHARINDEX functions, but I had to get a solution quickly and the code where I use it is within an offline process, so it’s not that I’m trying to get millisecond improvements.

ALTER FUNCTION [dbo].[ExtractTokens]
(
@Source NVARCHAR(MAX)
,@StartCharacter NVARCHAR(1)
,@EndCharacter NVARCHAR(1)
)
RETURNS @t TABLE (id INT IDENTITY(1, 1), val NVARCHAR(MAX))
AS
    BEGIN
        DECLARE @RemainingLength INT = LEN(@Source);
        DECLARE @StartPosition INT;
        DECLARE @EndPosition INT;
        DECLARE @RemainingText NVARCHAR(MAX);
        SELECT @RemainingText = @Source;
        WHILE @RemainingLength > 0
            BEGIN
                SET @StartPosition = CHARINDEX(@StartCharacter, @RemainingText, 1);
                IF (@StartPosition = 0) SET @RemainingLength = 0;
                ELSE
                    BEGIN
                        SET @RemainingText = SUBSTRING(@RemainingText, @StartPosition, LEN(@RemainingText));
                        SET @EndPosition = CHARINDEX(@EndCharacter, @RemainingText, 1);
                        IF @EndPosition = 0 SET @RemainingLength = 0;
                        ELSE
                            BEGIN
                                INSERT INTO @t (val) SELECT SUBSTRING(@RemainingText, 1, @EndPosition);
                                SET @RemainingText = SUBSTRING(@RemainingText, @EndPosition, LEN(@RemainingText));
                            END;
                    END;
            END;
        RETURN;
    END;
GO

SELECT val FROM [dbo].[ExtractTokens] ('This is some [sample] [text] wich include [tokens] wrapped in square brackets','[',']')

This is the results:

val
[sample]
[text]
[tokens]

I use this in a reporting tool, where the user can create there own reports from pre-defined views. Only showing specific columns, their conditions and sort. My Views are version controller (schema change trigger), so when db data guy change the view, the users’ version of this view my become broken, because of invalid columns. My DB version trigger will flag those user copies to be “dirty” (just due to version differences). I then come afterwards with a integrity checker, looking at the uses’s chosen columns, order by and where parts, and then see if some columns are invalid. My user query builder is enforcing square brackets around [Column Names], so without having to worry about details of all the in-between SQL, I can extract the columns, get it in a table, and then compare it with the column names I extract from sys.Columns and sys.Views.

For my desktop mill CNC conversion, I gathered the following information:
My mill is a PM-30 clone, so I gathered information on what other hobbyists have used with their Precision Matthews PM-30MV CNC conversions.

Stepper Motor Sizing

According to Bruce from Heavy Metal CNC in the USA, for the X and Y-axis, 906 oz-in (6.39 N-m) will work well and for the Z-axis,  1200 oz-in (8.47 N-m) will work well.

Battle Resistance Outfitters has 640 oz-in (4.52 N-m) on the X/Y and 1200 oz-in (8.47 Nm) on the Z, and everything still works fine.

Torque Conversion Table
N-m  oz-in  Axis  Candidate
4 568
4.5 637 X, Y
4.6 652 X, Y
4.8 680 X, Y
5 710 X, Y
5.8 822 X, Y www.omc-stepperonline.com
6 852 X, Y
7 994 X, Y
8 1136 X, Y, Z
8.5 1204 X, Y, Z www.omc-stepperonline.com
9 1278 Z
10 1420 Z
11 1562
12 1704

Open vs Closed Loop

For me, the whole open-loop thing does not make sense on a milling machine where it is very likely that some unplanned forces may influence the coordinates. I think it is even more likely that without a degree or two in engineering,  that a DIY may have some of a flawed design. Therefore a motion system that has feedback/position encoder, may counter some of these flaws. Servo motors are the ultimate, but expensive. This is why I’m going the hybrid route, stepper motors with encoders, a closed-looped motion system.

Locally Available Products

3dprintingstore.co.za

Specifications

Motor Size: NEMA 34
Torque: 8.5Nm
No Load top speed: 2000 RPM
Full Load top speed: 1000 RPM
Encoder: Mounted to motor
Cables: 3-meter motor and encoder cables attached to the motor
Supply voltage: 18 – 70V
Micro stepping: Can be set between 200 and 51200 steps/rev
Motor Model Number: 86HSE8.5N-B32-3M
Driver Model Number: HBS86H

Price: R3,899.95 (inc VAT)

This may be a bit of an overkill for X and Y, but the next smaller model is 4.5NM, which may be too small. The price difference is R700.

It’s sized correctly for the Z-axis, but I would like a break on the Z-axis and this model is without a break.

DIY Geek now have 19% discount on the same motor and driver.

https://www.diygeek.co.za/product/nema-34-8-5n-m-closed-loop-stepper-motor-hbs860h-driver/

Tracking the Vessel Brooklyn Bridge

With the help of marinetraffic.com you can quickly embed code to track vessels at sea. Below is the live tracker for the vessel Brooklyn Bridge, MMSI Number 370458000

Brooklyn Bridge is a Container Ship that was built in 2010 (10 years ago) and is sailing under the flag of Panama. It’s carrying capacity is 4432 TEU and her current draught is reported to be 12 meters. Her length overall (LOA) is 266.65 meters and her width is 35.4 meters.

Tip: Move the vessel window out of the way if it is obscuring the position marker of the vessel.


The widget above is created with this code:

<script type="text/javascript">
    width='100%';		// the width of the embedded map in pixels or percentage
    height='600';		// the height of the embedded map in pixels or percentage
    border='0';		// the width of the border around the map (zero means no border)
    shownames='true';	// to display ship names on the map (true or false)
    latitude='-5.1198';	// the latitude of the center of the map, in decimal degrees
    longitude='85.3053';	// the longitude of the center of the map, in decimal degrees
    zoom='3';		// the zoom level of the map (values between 2 and 17)
    maptype='0';		// use 0 for Normal Map, 1 for Satellite
    trackvessel='370458000';	// MMSI of a vessel (note: overrides "zoom" option)
    fleet='';		// the registered email address of a user-defined fleet 
</script>
<script type="text/javascript" src="//www.marinetraffic.com/js/embed.js"></script>

If you have set up you Dokan Store so that clients can not buy form multiple Vendor at the same time, it may be that you are not interested in split payments and want the Shop’s bank details to show on the checkout page when the shopper has chosen to pay with Direct Payment.

The first page you need to modify is:

1. woocommerce/includes/gateways/bacs/class-wc-gateway-bacs.php

With the change below, we will be reading the Banking information on the Vendors / Shop’s page and rather show that banking details.

You need to modify private function bank_details( $order_id = ” ) like in the code below.

You will see that there is a section at the top starting with // Start – Dokan Multistore stuff. In here we first get $seller (Vendor / Shop Id) of this order. It should be the id of the user linked to this shop. Once we have that id, we can get the object $store_info. Now that we have the store information we can extract all the values and use it in a pattern similar to the original code, generate the HTML.

All this new code was added with an if statement (if it could find some of this vendor banking stuff), else if, we revert to the original WooCommerce code. This code is nowhere complete, (but does work). It’s just for giving you a head start.

    /**
     * Get bank details and place into a list format.
     *
     * @param int $order_id Order ID.
     */
    private function bank_details( $order_id = '' ) {

        if ( empty( $this->account_details ) ) {
            return;
        }

        // Get order and store in $order.
        $order = wc_get_order( $order_id );

        // Get the order country and country $locale.
        $country = $order->get_billing_country();
        $locale  = $this->get_country_locale();

        // Get sortcode label in the $locale array and use appropriate one.
        $sortcode = isset( $locale[ $country ]['sortcode']['label'] ) ? $locale[ $country ]['sortcode']['label'] : __( 'Sort code', 'woocommerce' );
        
        $bacs_accounts = apply_filters( 'woocommerce_bacs_accounts', $this->account_details );
        
        // Start - Dokan Multistore stuff
        $seller = dokan_get_seller_id_by_order( $order_id );
        $store_info = dokan_get_store_info($seller);
        $store_ac_name = $store_info['payment']['bank']['ac_name']; 
        $store_ac_number = $store_info['payment']['bank']['ac_number'];
        $store_bank_name = $store_info['payment']['bank']['bank_name']; 
        $store_bank_addr = $store_info['payment']['bank']['bank_addr'] ;
        $store_routing_number = $store_info['payment']['bank']['routing_number'] ;
        $store_iban = $store_info['payment']['bank']['iban'] ;
        $store_swift = $store_info['payment']['bank']['swift'] ;
        if ( ! empty(store_ac_name)) {
            $store_account_html = '';
            $store_has_details  = false;
            $store_account_html .= '<ul class="wc-bacs-bank-details order_details bacs_details">' . PHP_EOL;
            $store_account_fields = apply_filters(
                'woocommerce_bacs_account_fields',
                array(
                    'account_name'      => array(
                        'label' => __( 'Account name', 'woocommerce' ),
                        'value' => $store_ac_name,
                    ),
                    'bank_name'      => array(
                        'label' => __( 'Bank', 'woocommerce' ),
                        'value' => $store_bank_name,
                    ),
                    'sort_code'      => array(
                        'label' => __( 'Branch Code', 'woocommerce' ),
                        'value' => $store_routing_number,
                    ),
                    'account_number' => array(
                        'label' => __( 'Account number', 'woocommerce' ),
                        'value' => $store_ac_number,
                    ),
                    'iban'           => array(
                        'label' => __( 'Pay Ref', 'woocommerce' ),
                        'value' => $order_id,
                    )
                ),
                $order_id
            );
            foreach ( $store_account_fields as $field_key => $field ) {
                if ( ! empty( $field['value'] ) ) {
                    $store_account_html .= '<li class="' . esc_attr( $field_key ) . '">' . wp_kses_post( $field['label'] ) . ': <strong>' . wp_kses_post( wptexturize( $field['value'] ) ) . '</strong></li>' . PHP_EOL;
                    $store_has_details   = true;
                }
            }
            $store_account_html .= '</ul>';
            if ( $store_has_details ) {
                echo '<section class="woocommerce-bacs-bank-details"><h2 class="wc-bacs-bank-details-heading">' . esc_html__( 'Our bank details', 'woocommerce' ) . '</h2>' . wp_kses_post( PHP_EOL . $store_account_html ) . '</section>';
            }
        }
        // End - Dokan Multistore stuff


        elseif ( ! empty( $bacs_accounts ) ) {
            $account_html = '';
            $has_details  = false;

            foreach ( $bacs_accounts as $bacs_account ) {
                $bacs_account = (object) $bacs_account;
 
                   
                //if ( $bacs_account->account_name ) {
                //	$account_html .= '<h3 class="wc-bacs-bank-details-account-name">' . wp_kses_post( wp_unslash( $bacs_account->account_name ) ) . ':</h3>' . PHP_EOL;
                //} 
                

                $account_html .= '<ul class="wc-bacs-bank-details order_details bacs_details">' . PHP_EOL;

                //BACS account fields shown on the thanks page and in emails.
                $account_fields = apply_filters(
                    'woocommerce_bacs_account_fields',
                    array(
                        'account_name'      => array(
                            'label' => __( 'Account name', 'woocommerce' ),
                            'value' => $bacs_account->account_name,
                        ),
                        'bank_name'      => array(
                            'label' => __( 'Bank', 'woocommerce' ),
                            'value' => $bacs_account->bank_name,
                        ),
                        'sort_code'      => array(
                            'label' => $sortcode,
                            'value' => $bacs_account->sort_code,
                        ),
                        'account_number' => array(
                            'label' => __( 'Account number', 'woocommerce' ),
                            'value' => $bacs_account->account_number,
                        ),
                        'iban'           => array(
                            'label' => __( 'Pay Ref', 'woocommerce' ),
                            'value' => $order_id,
                        )
                    ),
                    $order_id
                );

                foreach ( $account_fields as $field_key => $field ) {
                    if ( ! empty( $field['value'] ) ) {
                        $account_html .= '<li class="' . esc_attr( $field_key ) . '">' . wp_kses_post( $field['label'] ) . ': <strong>' . wp_kses_post( wptexturize( $field['value'] ) ) . '</strong></li>' . PHP_EOL;
                        $has_details   = true;
                    }
                }

                $account_html .= '</ul>';
            }

            if ( $has_details ) {
                echo '<section class="woocommerce-bacs-bank-details"><h2 class="wc-bacs-bank-details-heading">' . esc_html__( 'Our bank details', 'woocommerce' ) . '</h2>' . wp_kses_post( PHP_EOL . $account_html ) . '</section>';
            }
        }

    }

to be continued…

 

We all have seen how a small child can throw a tantrum, and when slightly older how they can negotiate something obscure. Well it by nature because there is no other leverage they have. You are bigger / stronger, provide the food and shelter, and have the money, right?

Now you let this little brad, with a brain just 5000 days old, loose on the internet, with a fake identity, into adult and political forums, forming an driving the mass idiotic internet social hysteria, and then witnesses the result. What do you get?

 

 

Voorwoord

Ek is nou al goed keelvol om vir tegnies gestremdes en telekommunikasie belanghebbendes te verduidelik dat radiofrekwensie radiasie wel ‘n skadelik effek kan hê. Daar is altyd daardie redenasies van een of ander snuiter wat net ‘n klomp twak braak, sonder enige kennis van die onderwerp, en ‘n klomp jabroers wat saam sing.

Verder dink ek ook dat dit baie oneties van sogenaamde “tekkies” van sekere tydskrifte is, om blatant op nasionale TV te sê dat 5G absoluut geen gevaar inhou nie.

Hier is nuus vir jou i.v.m. die gevare van radiofrekwensie radiasie, en jou inner drang na vinniger internet.

Onvoldoende Riglyne

Ajita Pia, die voorsitter van die Federale Kommunikasie Kommissie (FCC) in Amerika, het die 8st Augustus 2019,  in ‘n persverklaring [1] gesê dat hul die radiofrekwensie bestraling riglyne wat in die 1990’s daargestel is,  herbevestig het. Hierdie riglyne is gebaseer op die gedrag verandering by rotte [2] wat aan mikrogolfstraling blootgestel is, en is ontwerp om ons te beskerm teen die verhitting wat veroorsaak word deur die blootstelling van hierdie radiogolwe [3].

  1. https://docs.fcc.gov/public/attachments/DOC-358968A1.pdf
  2. https://www.ncbi.nlm.nih.gov/pubmed/21999884
  3. Nominations from FDA’s Center from Device and Radiological Health

Hierdie FCC riglyne, grotendeels gebaseer op navorsing in die 80’s en 90’s, is egter onvoldoende, want daar is 1000’de eweknie-geëvalueerde studies [4] wat gevind het dat daar skadelike biologiese- en gesondheidseffekte is, al is daar geen verhittings effek nie.

  1. Peer-Reviewed Scientific Papers on Electromagnetic Fields and Biology or Health

Internasionale Appèl

Meer as 240 wetenskaplikes wat hierdie eweknie-geëvalueerde navorsing oor die biologiese- en gesondheidseffekte van nie-ioniserende elektromagnetiese velde (EMF) gepubliseer het, het die “International EMF Scientist Appeal[5] onderteken. Hierdie appèl doen ‘n beroep op strenger reëls en  maak die volgende stellings:

“Verskeie onlangse wetenskaplike publikasies het getoon dat EMF lewende organismes beïnvloed op vlakke wat ver onder die meeste internasionale en nasionale riglyne is. Effekte sluit in ‘n verhoogde kanker risiko, sellulêre spanning, toename in skadelike vrye radikale, genetiese skade, strukturele en funksionele veranderinge van die voortplanting stelsel, leer- en geheue-tekorte, neurologiese afwykings en negatiewe gevolge vir algemene welstand by mense. Verder is daar ook al hoe meer bewyse dat die skadelike gevolge ook op plant- en dierelewe van toepassing is.”

  1. The International EMF Scientist Appeal

Die wetenskaplikes wat hierdie appèl onderteken het, vorm waarskynlik die meerderheid van al die kundiges in die studieveld van nie-ioniserende bestraling en die gevolge wat dit inhou. Hul het meer as 2000 artikels en briewe oor EMF in vaktydskrifte gepubliseer.

Sein Eienskappe

Die FCC se radiofrekwensie radiasie blootstellingslimiete reguleer die intensiteit van die blootstelling, inagneming met die frekwensie van die draer golwe, maar ignoreer al die ander sein eienskappe van die radiofrekwensie radiasie. Die patroon, tydsduur van blootstelling verhoog en ander eienskappe (bv. pols, polarisasie) verhoog die biologiese en gesondheid impak [6] van hierdie blootstelling. Nuwe blootstellingslimiete is inderdaad nodig om die effekte hiervan te reguleer. Boonop moet hierdie limiete gebaseer wees op ‘n biologiese effek [7] , nie net ‘n verandering in die gedrag van ‘n laboratorium rot nie.

  1. DNA damage induced by mobile telephony and other types of electromagnetic fields
  2. Electromagnetic fields and health: DNA-based dosimetry

Navorsing oor Kanker

In 2012, het die Wêreldgesondheidsorganisasie (WHO) se Internasionale Agentskap vir Navorsing oor Kanker (IARC), radiofrekwensie radiasie geklassifiseer dat dit moontlik kanker opwekkend is vir mense [8] . In ‘n US$30 miljoen studie wat deur die Amerikaanse nasionale toksikologie-program (NTP) uitgevoer is, is daar duidelike bewys dat twee jaar se blootstelling aan selfoon radiofrekwensie radiasie, kanker by manlike rotte verhoog en DNA van manlike en vroulike rotte [9] en muise [10] beskadig. Die Ramazzini-instituut in Italië het die sleutel bevindings van die NTP herhaal met behulp van ander draer golwe en teen selfs ‘n baie swakker blootstelling.

  1. IARC Classifies Radiofrequency Electromagnetic Fields
  2. Toksikologie en karsinogenese studies op rotte (900 MHz)
  3. Toksikologie en karsinogenese studies op muise (1900 MHz)

Voldoende Bewyse

Op grond van die navorsing wat sedert 2011 gepubliseer is, het die Internasionale Agentskap vir Navorsing oor Kanker (IARC) onlangs voorkeur begin gegee aan radiofrekwensie radiasie, en dat dit binne vyf jaar weer hersien moet word. Aangesien baie elektromagnetiese veld (EMF) wetenskaplikes nou glo dat daar voldoende bewyse [11] is om radiofrekwensie radiasie as ‘n waarskynlike of bekende menslike karsinogeen te klassifiseer, sal die IARC waarskynlik die kankerwekkende aard van radiofrekwensie radiasie in die nabye toekoms bewerk.

  1. Evaluation of Mobile Phone and Cordless Phone Use and Glioma Risk

Die 5de Generasie

Die nuutste sellulêre tegnologie, 5G, sal vir die eerste keer millimeter golwe (24.25 GHz –  52.6 GHz) gebruik [12] .

  1. Fig 1: Kandidaat frekwensies vir 5G

Hierdie golwe het ‘n baie hoër frekwensie as die mikrogolwe wat tans gebruik word deur die ouer sellulêre tegnologieë soos:

  • 2G (800 MHz, 850MHz / 900 MHz, 1800 MHz),
  • 3G (850MHz, 1700MHz / 900 MHz, 2100 MHz)  en
  • 4G (600 MHz, 700 MHz, 1.7/2.1 GHz, 2.3 GHz, en 2.5 GHz).

Omdat hierdie millimeter golwe ‘n beperkte reikafstand het, moet 5G sel antennes elke 100 tot 200 meter ontplooi word, en dit sal baie mense blootstel aan millimeter-golf-bestraling.

Verwarring

Hierdie digte 5G radio ontplooiing en hul millimeter golflengtes moet ook nie verwar word met die 5G- “backhaul” frekwensies van 450 MHz tot 6 GHz nie. Daar is so groot mengelmoes van 5G frekwensies, dat enige leek ‘n rat voor die oë gedraai kan word deur te gryp of te verwys na net een of twee van hierdie magdom frekwensies. Die “backhaul”, wat torings met mekaar verbind, is die frekwesies laer as 6 GHz, en die berugte millimeter golwe, is daardie bokant 20 GHz.

Blootstelling

5G gebruik ook nuwe tegnologieë soos byvoorbeeld aktiewe antenna “beam-forming”, “phased arrays” en massiewe veelvuldige insette en uitsette, bekend as “massive MIMO”. Hierdie nuwe tegnologieë hou unieke uitdagings in vir die meting van verskillende soorte blootstelling en die kombinasies daarvan.

Millimeter golwe dring net ‘n paar millimeter in die menslike vel in en word ook opgeneem deur die oppervlak lae van die kornea (oog). Blootstelling op kort termyn kan nadelige fisiologiese gevolge hê in die perifere senuweestelsel, die immuunstelsel en die kardiovaskulêre stelsel. Die navorsing dui daarop dat langdurige blootstelling gesondheidsrisiko’s vir die vel (bv. Melanoom), die oë (bv. Okulêre melanoom) en die testes (bv. Steriliteit) kan inhou.

Geen Navorsing

Aangesien 5G ‘n nuwe tegnologie is, is daar geen navorsing oor gesondheidseffekte nie. Om  ‘n Amerikaanse senator aan te haal:

“so we are flying blind”

Ons het egter baie bewyse oor die skadelike gevolge van 2G en 3G.
Oor die gevolge van blootstelling aan die 10 jaar oue 4G, is min bekend, omdat regerings nie genoeg geld gehad het om hierdie navorsing te befonds nie.

Selfoonbestraling

Intussen sien ons toenames in sekere soorte kop- en nek gewasse in gewas registers, wat moontlik gedeeltelik toegeskryf kan word aan “selfoonbestraling”. Hierdie verhogings stem ooreen met die resultate uit gevallestudie-ondersoeke van gewas risiko by swaar selfoongebruikers.

Die Toekoms

5G sal nie 4G vervang nie; dit sal 4G vergesel vir die nabye toekoms en moontlik ook oor die langtermyn. As daar sinergistiese effekte is van gelyktydige blootstelling aan verskeie soorte radiofrekwensie radiasie, kan ons algemene risiko vir skade deur radiofrekwensie radiasie aansienlik toeneem. Kanker is nie die enigste risiko nie, aangesien daar aansienlike bewyse is dat radiofrekwensie radiasie neurologiese afwykings en reproduktiewe skade veroorsaak, waarskynlik as gevolg van oksidatiewe spanning.

Wat nou?

As ‘n samelewing, moet ons honderde miljoene Rande belê in die uitrol van 5G, ‘n sellulêre tegnologie wat die installering van 100de duisende sel antenne naby ons woon, werk en speel plek insluit?

In teendeel, ons behoort die aanbevelings van die 250 wetenskaplikes en mediese dokters wat die 5G-appèl onderteken te ondersteun.  Hierdie appèl vra vir ‘n onmiddellike moratorium op die ontplooiing van 5G en eis dat regerings die navorsing befonds wat benodig word om biologiese-gebaseerde blootstellingslimiete in te stel wat ons veilig en gesond hou.

Ten Laaste

Hier afblaas sessie kan nog talle blaaie langer wees. Ek het nie eers gepraat oor ons “expat” Mr Musk nie. Klink Starlink nie vir jou soos iets uit ‘n Arnold Schwarzenegger fliek nie? Nee, inteendeel, dis SpaceX se 45000 lae aarde wentelbaan (550km) satelliete wat ‘n kokon om die aarde vorm en ook van 5G gebruik maak.

Hoe sterk dink jy is hierdie seine, en waar is die navorsing oor die effect van hier magdom radiofrekwessies?

Na al die feitlike leesstof hierbo, kan jy die leser, nog steeds so naïef wees om te dink dat al hierdie frekwensies onskadelik is, en geskep word net vir jou eie aardse vinnige internet plesiertjies?

Capish

Money talk, people are sheep, and technology do not care a rats ass about your health.

In hierdie tegnologie rot resies sonder voldoende reëls, gaan die mensdom letterlik die rot se gat sien.

Goodies

Figuur 1: Kandidaat frekwensies vir 5G soos geïdentifiseer in WRC15
Tabel 1: Frekwensies
Band name Abbr ITU band Frequency and wavelength Example uses
Tremendously low frequency TLF < 3 Hz
> 100,000 km
Natural and man-made electromagnetic noise
Extremely low frequency ELF 3 – 30 Hz
100,000 km – 10,000 km
Communication with submarines
Super low frequency SLF 30 – 300 Hz
10,000 km – 1000 km
Communication with submarines
Ultra low frequency ULF 300–3000 Hz
1000 km – 100 km
Submarine communication, Communication within mines
Very low frequency VLF 4 3 – 30 kHz
100 km – 10 km
Navigation, time signals, submarine communication, wireless heart rate monitors, geophysics
Low frequenc LF 5 30 – 300 kHz
10 km – 1 km
Navigation, time signals, AM longwave broadcasting (Europe and parts of Asia), RFID, amateur radio
Medium frequency MF 6 300 – 3000 kHz
1 km – 100 m
AM (medium-wave) broadcasts, amateur radio, avalanche beacons
High frequency HF 7 3–30 MHz
100 m – 10 m
Shortwave broadcasts, citizens’ band radio, amateur radio and over-the-horizon aviation communications, RFID, Over-the-horizon radar, Automatic link establishment (ALE) / Near Vertical Incidence Skywave (NVIS) radio communications, Marine and mobile radio telephony
Very high frequency VHF 8 30–300 MHz
10 m – 1 m
FM, television broadcasts and line-of-sight ground-to-aircraft and aircraft-to-aircraft communications. Land Mobile and Maritime Mobile communications, amateur radio, weather radio
Ultra high frequency UHF 9 300 – 3000 MHz
1 m – 100 mm
Television broadcasts, microwave ovens, microwave devices/communications, radio astronomy, mobile phones, wireless LAN, Bluetooth, ZigBee, GPS and two-way radios such as Land Mobile, FRS and GMRS radios, amateur radio
Super high frequency SHF 10 3 – 30 GHz
100 mm – 10 mm
Radio astronomy, microwave devices/communications, wireless LAN, most modern radars, communications satellites, satellite television broadcasting, DBS, amateur radio
Extremely high frequency EHF 11 30 – 300 GHz
10 mm – 1 mm
Radio astronomy, high-frequency microwave radio relay, microwave remote sensing, amateur radio, directed-energy weapon, millimeter wave scanner
Terahertz or Tremendously high frequency THz or THF 12 300–3,000 GHz
1 mm – 100 μm
Terahertz imaging – a potential replacement for X-rays in some medical applications, ultrafast molecular dynamics, condensed-matter physics, terahertz time-domain spectroscopy, terahertz computing/communications, sub-mm remote sensing, amateur radio