Custom PHP Development

Custom PHP Development

We Built New Security Features. Here's Why They Matter.

 

We Built New Security Features. Here's Why They Matter.

Your website runs on a foundation you probably don't think about: your domain registration, hosting account, SSL certificate, and the credentials to access them. Most site owners don't pay attention until one of them expires.

Then the panic starts.

The Real Problem: Websites Die Quietly

We've watched it happen. A client loses access to their registrar account. Domain expires. They can't renew it. Site goes dark. Email stops working. Within hours, business operations are disrupted.

It happens more often than you'd think. Not because people are careless—because it's easy to lose track of 5+ different renewal dates scattered across different services.

A restaurant's site went down during their busiest season. An e-commerce business missed a critical renewal email and lost their domain for 72 hours while trying to get it back. A freelancer's portfolio disappeared and clients couldn't reach them.

These weren't security breaches. They were logistics failures.

What We Built

We created a Renewals Tracker because we got tired of watching this happen.

1. Centralized Renewal Management

Instead of checking your email, your registrar, your hosting control panel, and three other places, everything lives in one place:

  • Domain Registration — registrar, renewal date, cost
  • Hosting — provider, renewal date, cost, frequency
  • SSL Certificate — provider, renewal date, cost
  • WordPress Management — renewal date and cost
  • Static IP — provider, IP address, renewal date, cost

One dashboard. All your critical dates in one place.

2. 90-Day Warning System

We show you what's due in the next 90 days. Not 14 days—90. Because:

  • Some registrars take weeks to process renewals
  • Payment issues happen
  • You want time to evaluate new providers
  • Panic-decision-making at midnight is how expensive mistakes happen

The warning is front-and-center on your dashboard. Not buried in an email. Not in a separate admin page you forgot exists.

3. The Phone Number Problem (And How We Help)

Here's the conversation we have with clients constantly:

"I can't renew my domain."

"Why not?"

"The email went to an old address I don't have access to."

"Call the registrar."

"I can't. They only have an old phone number on file."

Update your phone number with your registrar. Do this right now. Seriously.

If your domain is locked (which it should be), you often can't renew it through the website—you have to call. If they can't reach you, you're locked out.

Our Renewals Tracker makes this easier by keeping renewal dates visible. Set a calendar reminder to verify your contact info every 6 months. Check that phone number. Check that email.

4. Security-First Whitelisting

We also built a Traffic Training system with granular security rules:

  • Block XML-RPC (stops brute force attacks on old APIs)
  • Block direct access to wp-config.php and .htaccess
  • Block suspicious user agents and SQL injection attempts
  • Block directory listing
  • Rate limiting for brute force protection

But here's the thing: your team needs access. So we built IP whitelisting with temporary links.

  • Generate a 90-minute temporary link for team members on mobile networks
  • They click it, their IP is whitelisted automatically
  • After 90 minutes, it expires
  • Or you can approve it to permanent if they're trusted

No more "let me send you access credentials." Just a link. And it's temporary by default—better security posture.

Why We're Being Blunt About This

We could sugar-coat this. "Proactive renewal management ensures continuity." Boring.

The truth: You're one email filter mistake away from your site disappearing.

Not from hackers. Not from security breaches. From a renewal date you didn't see because it ended up in spam.

We've seen:

  • Domains that recovered only after 72+ hours of downtime
  • Clients who lost their domain to competitors during the grace period
  • Sites that were de-indexed by Google because they were down for a week
  • Email service disruptions because the domain renewal lapsed

It's avoidable. It just requires systems and attention.

The Technical Side (Yes, We'll Tell You Everything)

Our whitelist feature uses a simple token system. When someone clicks an auto-add link, we:

  1. Detect their visitor IP
  2. Verify the token matches our hash
  3. Add it to either temporary (90 min) or permanent storage
  4. Show you what's whitelisted with expiry times

Temporary IPs are stored separately and cleaned up every time the admin page loads. If you want to know exactly how it works, it's not hidden. This isn't security through obscurity—it's security through:

  • Small, auditable code
  • Temporary credentials by default
  • Your visibility into what's whitelisted
  • Ability to revoke instantly

Backwards engineering it won't help you. The security is in the process, not the encryption.

What You Should Do

  1. Set up the Renewals Tracker. Add all your critical dates. All of them.
  2. Enable the 90-day warnings. Pay attention when they show up.
  3. Update your registrar contact info right now. Phone number. Email. Make sure it's current.
  4. Use temporary links for team access. Not permanent credentials.
  5. Enable the blocking rules. They're on by default, but whitelist your trusted IPs.

The Bottom Line

Your website is more fragile than you think—but not because of hackers. Because of forgotten dates and locked-out accounts.

We built these features because we kept having to help clients rebuild after preventable failures. You're using site management tools. You should be using renewal management tools too.

Don't be the site that went down because of a registrar email that hit spam.

Custom PHP Development

Your Website Can Be Rebuilt. Your Data Can’t.

Split scene: a website interface reassembling from puzzle pieces while a database shatters into an unrecoverable void

Here’s a hard truth most website owners never think about until it’s too late: almost everything on your website is replaceable. Except the one thing that actually runs your business.

What you can always get back

Plugins and themes? Replaceable. Every plugin on your site can be re-downloaded in minutes. WordPress itself can be reinstalled with one click. Even a premium theme is just a receipt and a download link away.

Images and files? Backupable the easy way. Your uploads folder — photos, PDFs, logos — is just files. Copy the folder, and you have it. Drag it to an external drive, sync it to Dropbox, done. It doesn’t change every hour, so even an occasional copy keeps you covered.

Your design? Rebuildable. Painful, maybe. But a developer can put your site back together from screenshots if they have to.

What you can never get back

Your database. That’s where the real business lives:

Every customer record. Every order. Every form submission and lead. Every blog post you’ve written. Every product, price, and inventory count. Years of accumulated history that makes your website yours.

None of that exists as files you can drag and drop. It lives inside a MySQL database on your hosting company’s server — and if it disappears, there is no download link. No receipt. No getting it back.

“But my hosting company backs everything up”

Maybe. Read the fine print sometime.

Host backups are made for their convenience, not yours. They’re often kept for only a few days, stored on the same servers as your site, and offered with no guarantee whatsoever — most hosting agreements explicitly say backups are provided “as is” and recommend you keep your own.

And hosting companies go south. They get acquired and gutted. They botch server migrations. They suspend accounts over billing disputes and delete the data behind them. They get hit by ransomware, fires, and outages. When that happens, “we have backups” turns into “we’re sorry for the inconvenience” — and your customer list, your orders, your years of content go down with them.

If your only backup lives with your host, you don’t have a backup. You have a hope.

What a real data backup looks like

A proper backup strategy for your database is built on one principle: it doesn’t depend on your hosting company surviving.

That means a full SQL export of your database — a file containing the structure of every table and every row of data, capable of rebuilding your entire database on any server, anywhere. Not a “restore point” trapped inside your host’s control panel. A file you hold.

Done right, it works like this:

It runs on a schedule. Backups that depend on someone remembering don’t happen. Nightly for a busy store, weekly at minimum for anything.

It leaves the building. The backup file gets stored away from your hosting account — your own computer, an external drive, cloud storage. If your host vanished tomorrow, your data wouldn’t.

It keeps history. Not just last night’s copy, but days or weeks of them. Sometimes you don’t discover a problem — a hack, a bad plugin update, accidentally deleted records — until well after it happened. A single overwritten backup can faithfully preserve the damage.

It’s been tested. A backup you’ve never restored is a guess. The time to find out your backup file works is on a quiet Tuesday, not the morning after a disaster.

The full picture

So the complete strategy is simpler than it sounds, because each piece gets the level of protection it actually needs:

Plugins and themes — keep a list; re-download when needed. Uploads folder — copy it periodically; it’s just files. Database — automated SQL exports, on a schedule, stored somewhere your hosting company can’t touch.

That last piece is the one almost everyone skips, and it’s the only one that can genuinely end a business. Companies fold over lost data. Nobody folds over a lost plugin.

Do this today

If you take one action after reading this: log into your site and find out when your database was last backed up to somewhere outside your hosting account. If the answer is “never” or “I don’t know,” that’s the gap.

Setting up automated, off-host database backups takes an afternoon. Recreating ten years of customer data takes forever, because you can’t.


Need help setting up a backup strategy that doesn’t depend on your host’s goodwill? Get in touch — it’s cheaper than a data recovery specialist, and considerably more effective.

Custom PHP Development

Advanced Themer 13 – Feature Update Article

SolarBlu - Advanced Themer 13 Update

Advanced Themer 13: The Big Login, Access & Billing Update

It started with a simple request — "can we change the login background color?" — and turned into the biggest Advanced Themer 13 release yet. A fully branded login screen, real agency-grade access control, and a complete client billing loop powered by Cash App. Here is everything that shipped.

A Login Screen That Wears Your Brand

Branded login screen mockup with logo, field icons, and yellow login button

The login page is the first thing every client sees, and now every piece of it is yours. The new Login Page section under AT13’s Admin settings gives you a background color picker, a background image with tile, single, or stretch-to-fill display modes (a seamless dark-blue ripple tile ships with the plugin), and your own logo above the form — pulled from the Media Library and linked to your home page instead of wordpress.org.

Text gets the same treatment: a color for the links outside the form box, a button color with an automatically derived hover state and outline, and a separate button text color. Then the fun part — a little unlock icon on the Log In button, a person icon beside the Username label, and a key beside Password. A login-only Custom CSS box covers anything the options don’t.

Super Admin: Your Plugin, Not Theirs

Super Admin shield protecting the plugin while client admins run their sites

Clients need real administrator accounts to run their sites — but agency tooling should stay with the agency. AT13 now has a Super Admin layer: the plugin’s pages and settings are visible only to designated Super Admins, while client admins keep full control of their content, users, and everything else. They simply never see AT13 at all, and even hand-crafted settings requests are rejected server-side.

Making someone a Super Admin is one checkbox on their user profile — a checkbox only existing Super Admins can see, so nobody can promote themselves. A built-in email whitelist and a wp-config override round out the safety net so you can never be locked out of your own plugin.

Super Admin Overrides

Two site-wide switches live in the new Overrides section. Hide Plugins removes the Plugins screen for everyone but Super Admins — menu gone, direct URLs blocked, install and deactivate disabled — while plugin auto-updates keep running untouched. Suspend Client Access is the big one, and it deserves its own section.

Suspension & Self-Service Unlock: The Billing Loop

Billing loop diagram: Suspend, Pay by QR, One-Tap Approve, Unlock Code

Nonpayment is awkward. This makes it automatic and fair. Flip one switch and content editing is suspended: Posts, Pages, Media, and Comments step aside for every user except Super Admins. The public website stays fully online the whole time — nothing is deleted, nothing is modified, and unticking the switch restores everything instantly.

Suspended clients are never left guessing. Their dashboard opens with an Account Status box — green and reassuring when all is well, and during suspension it shows exactly what is owed with a Make a Payment button. That button carries the site’s domain and balance straight to the SolarBlu payment portal, where a scannable Cash App QR code and a prefilled form make paying take about a minute.

Then the loop closes itself. The moment a payment is submitted, an email goes out with the details and a one-tap approve link — secure, signed, and phone-friendly, so payments can be approved from anywhere in seconds. Approval mints a single-use unlock code tied to that client’s domain. The client grabs it from their status page, types it into the Account Status box, and the site verifies it and unlocks itself on the spot. The code burns after one use. No back-and-forth, no waiting.

Built-In Invoicing

You do not even have to wait for a client to notice. The Admin screen now has an Invoice section: the payment link for the current site is displayed ready to test or copy, and a Send Invoice form emails the amount due and payment link to whoever needs to pay it — email address saved for next time, subject line prefilled.

The Payment Portal Itself

The Cash App portal got its own upgrade to match: the SolarBlu logo up top, the QR code front and center, a required "website this payment is for" field that tracks every payment to its site, a live status checker, and the unlock code page. On the admin side there is a Site column for matching payments at a glance, plus a tucked-away user management panel — super admins can add or remove portal users, everyone can change their own password, and regular users see none of it.

Little Things That Add Up

Also in this release: the Duplicate Posts/Pages tool is now a proper toggle, the plugin’s main page shows a live feature list with account status at the top, missing tracking scripts show an orange "Not Enabled" nudge instead of quietly saying disabled, and the QR Tracker got its own icon in the admin menu.

Everything here lives in the plugin, so it travels. Activate Advanced Themer 13 on a new site and the branded login, the access control, and the entire billing loop come along for the ride. Build it once, run it everywhere.

Advanced Themer 13 is built and maintained by SolarBlu. Questions or ideas for the next release? Reach out at seth@solarblu.net.

Custom PHP Development

The QR Codes That Actually Made Money — and How We Built the Tracking Into Advanced Themer 13

Field Notes · Marketing Technology

The QR Codes That Actually Made Money — and How We Built the Tracking Into Advanced Themer 13

Everyone prints QR codes. Almost no one can tell you which ones paid off. Here's what changed when we stopped guessing.

Walk down any street and you'll see them. On door hangers. On mailers. On banners at the gas
station and stickers on the back of a work truck. QR codes are everywhere now — the pandemic made
them normal, and every marketer reached for them at once. But here's the question almost nobody in
the room can answer: which of those codes actually made a sale?

Not "how many scans." Scans are easy — every free generator gives you a number. A scan count tells
you a code got opened. It tells you nothing about whether the person who opened it in a driveway in
central Illinois turned into revenue, or whether the identical-looking code on last month's email
blast did all the real work. When you're running door-to-door crews, direct mail, and paid banners
at the same time, "we got 4,000 scans" is not an answer. It's a shrug with a number attached.

The problem was never generating the code. It was proving what it did.

Our codes were never dumb links. Each one carried a campaign-coded URL — tagged by campaign ID,
by location, by usage type (banner, mailer, email), and by the affiliate or D2D/CSR rep who put it in
someone's hand. The link was the easy part. The hard part was catching the visit on the other end,
silently, and stitching it back to the sale — without bouncing the customer through some clunky
redirect that makes the whole thing feel like spam.

That's the difference that turned QR from a novelty into a channel. When a rep works a
neighborhood and a homeowner scans the hanger on their door three days later, we don't just know
"someone scanned." We know which campaign, which territory, which rep, and what
happened next. Do that across a full program and the fog lifts. The campaigns that looked busy but
quietly lost money start to separate from the ones printing revenue — and you pour budget into the
winners instead of spreading it evenly across guesses.

The scan number told us a code was opened. The system told us which code paid the bills.

$2,000,000+ in attributed sales
Not "influenced." Not "part of the mix." Traced — scan to sale — back to the exact codes that produced it.

That number matters less as a brag than as a proof of concept. Without the tracking, that $2M is
just… sales. Unattributable, unrepeatable, invisible to the next planning meeting. With it, every
dollar has a return address: the location, the channel, the campaign, the rep. That's the part you
can run again on purpose.

Bringing it home: baking the tracker into Advanced Themer 13

The early version lived off to the side — a standalone generator and a separate dashboard, held
together by one person's knowledge of how it all fit. That's fine until it isn't. The whole point of
Advanced Themer 13 is that the tools live inside WordPress, where the pages already are. So
that's where the tracker belongs.

The integration is deliberately simple, because simple is what survives contact with a real
marketing team:

  • A per-page toggle. Every page and post gets a "QR Tracker" checkbox right in the editor
    sidebar. Turn it on for your campaign landing pages; leave it off everywhere else. No developer, no
    shortcode surgery — a checkbox.
  • Silent logging. When a tracked page gets a QR visit, it's recorded quietly — campaign,
    location, usage, affiliate, IP, device, referrer — while the visitor just sees your page load
    normally. No redirect hop, no interstitial, nothing that smells like tracking.
  • One dashboard, inside wp-admin. Results by campaign and location, scans and visits side
    by side, right under the Advanced Themer 13 menu. The people running the campaigns can read it
    without logging into a database.
  • Your data, your database. Everything writes to a dedicated tracking database you own —
    not a third-party platform that rents your own numbers back to you and owns the relationship with
    your customer.

That last point is the quiet one, and it's the one that matters most over time. The big QR
platforms are happy to count your scans — as long as the codes point at their servers and the
data lives on their side of the fence. Build it into your own plugin, on your own pages, in
your own database, and you're not renting your results. You own the whole loop: the code, the
landing page, the visit, the sale, and the history that proves what works.

Why this is worth doing right

QR codes aren't going anywhere — they're on more surfaces every year. What's still rare is the
boring, unglamorous discipline of measuring them all the way through to money. Most of the market can
tell you a code was scanned. Very few can hand you a line that reads "this territory, this channel,
this campaign, these dollars." We can. And now that it's built into Advanced Themer 13, any page on
the site can become a tracked landing page with a single click.

That's the whole idea: stop admiring scan counts, and start reading the map that shows you where
the money actually comes from.

Solar Blu Seth
 ·  seth@solarblu.net  ·  solarblu.net  ·  Advanced Themer 13
Custom PHP Development

Advanced Themer 13 Dev Log: One Day, Fourteen Features, and a Three-Plugin Pileup

Advanced Themer 13 Dev Log: One Day, Fourteen Features, and a Three-Plugin Pileup

Behind the scenes of a full day hardening our in-house Astra toolkit — filters, hooks, hotfixes, and one very satisfying bug hunt.


Advanced Themer 13 is our ground-up rebuild of the toolkit we bolt onto every Astra site we ship. Today it grew up a lot. Here's the full engineering log — not the marketing version, the real one.

The philosophy: filter Astra, don't fight it

Everything AT13 does to the front end goes through Astra's own option pipeline. Astra reads every setting through astra_get_option(), and that function runs each value through a dynamic filter: astra_get_option_{$option}. Hook that filter and you own the setting — no template overrides, no CSS specificity wars, no forked theme files that break on update.

That's the pattern behind today's Blog Layout module. The new Layout tab writes plugin options (at13_blog_layout, at13_blog_columns, at13_blog_content, and friends), and a loop registers a closure on the matching Astra filter for each one that's actually set:

add_filter( "astra_get_option_{$astra_opt}", function () use ( $at13_val, $astra_opt ) {
    return in_array( $astra_opt, [ 'blog-grid', 'blog-post-per-page' ], true )
        ? (int) $at13_val
        : $at13_val;
} );

Anything left on default never registers a filter at all, so the Customizer keeps authority until you deliberately take it. Grid/List/Cover layouts, column counts, excerpt vs. full content, hover effects, and opt-in overrides for Astra's blog-post-structure and blog-meta arrays — all of it rides astra_get_option_*. Zero template files touched.

The same trick killed an annoyance: Astra free injects a permanently-disabled "Astra Menu Settings 🔒" button into every item on Appearance → Menus — pure Pro upsell, and it makes third-party panels like Menu Icons look locked. Turns out the entire upsell layer gates on one option. One line in the child theme:

add_filter( 'astra_get_option_ast-disable-upgrade-notices', '__return_false' );

Locks gone. Nothing was ever actually locked.

Header builder, the surgical version

The header picked up real client-facing controls today, each one wired to the markup layer that actually owns it:

  • CTA button after the menu. Appended via the wp_nav_menu_items filter, scoped to theme_location === 'primary', with label, URL, background color, and an open-in-new-tab toggle (rel="noopener noreferrer" included). Astra stretches .menu-link to full nav height, which left the button text riding the top of a tall block — fixed by flex-centering the <li> and forcing a compact box (line-height:1, height:auto, both !important to out-rank Astra's own menu rules).
  • Phone number with SVG icon. The icon is inline SVG using stroke="currentColor", so it inherits the header text color and the hover color for free — no extra HTTP request, no recoloring assets. The whole phone/social row also got wrapped in Astra's .ast-container, so its right edge now aligns to the same content boundary as the menu instead of drifting to the browser edge.
  • Tagline with honest semantics. The show/hide checkbox now overrides Astra's own "Display Site Tagline" customizer setting in both directions via its option filter — checked shows, unchecked hides, and blank text falls back to Settings → General. Previously an empty tagline rendered an invisible empty <p> and looked like a bug.
  • Site title sizing (default 34px, changeable) and per-element show/hide toggles for the phone, all with sane defaults so existing sites don't shift when the plugin updates.

Content controls with per-page escape hatches

Two requests that every client eventually makes: "kill the comment spam" and "hide the page titles." Both are now global toggles with local overrides:

  • Hide Comments (posts) filters comments_open and get_comments_number to zero on singular posts — Astra sees a closed, empty discussion and renders no comments UI at all. No CSS hiding, no orphaned markup.
  • Hide Titles is split into separate Pages and Posts toggles, implemented through Astra's astra_the_title_enabled filter so the <h1> markup is skipped, not display:none'd. Every editor screen gets an "AT13 Title" meta box whose checkbox defaults to checked (hidden); unchecking writes _at13_show_title = 1 post meta and that one page opts back in. Default-on with opt-out means zero clicks for the common case.

Also new: a Duplicate row action on posts and pages — nonce-verified, capability-checked, clones content, excerpt, taxonomies, and all post meta (minus _edit_lock/_edit_last) into a draft titled "(Copy)". Our AT13 per-page flags ride along with the meta, which is exactly what you want when cloning a landing page.

Tracking scripts done right

Three boxes, three injection points, because that's what the vendors actually specify: Header (wp_head at priority 99 — GA4, GTM loader), Body (wp_body_open at priority 1 — GTM's <noscript> frame, textbook placement), and Footer (wp_footer — chat widgets and anything lazy). The sanitizer preserves raw HTML only for users with unfiltered_html; anyone else gets wp_kses_post'd on save, so the feature can't become a stored-XSS vector through a lesser role.

The CSS tab also gained a Mobile CSS box: write plain rules, set a breakpoint (default 720px), and the plugin wraps output in @media (max-width: …) at priority 999 — after Quick Styles, after Astra, so mobile rules win.

The bug hunt: Visual Composer × WooCommerce × PHP 8.5

Mid-session, the VC editor started white-screening. Site fine, one page uneditable. This is where discipline beats guessing: straight to debug.log — all 201 MB of it, bloated by PHP 8.5 deprecation notices from plugins that haven't caught up yet.

The real fatal, timestamped to the minute we hit the editor:

PHP Fatal error: Uncaught TypeError: strpos(): Argument #1 ($haystack)
must be of type string, int given in
woocommerce/src/Internal/ProductAttributes/VisualAttributeTermAdmin.php:290

The autopsy: Visual Composer's editor swaps in an admin screen object whose ID is an integer — and it does it directly, without firing the current_screen action. WooCommerce later runs strpos( $screen->id, 'edit-pa_' ) on admin_enqueue_scripts, assuming a string. On PHP 7 that mismatch was a shrugging warning; on PHP 8+ it's fatal. Three parties, each individually defensible, collectively a white screen.

The fix is a 20-line mu-plugin that normalizes $screen->id to a string on both current_screen and admin_enqueue_scripts at priority 9 — one tick before WooCommerce reads it at 10. No core files patched, survives every update of both plugins, and becomes a no-op the day either vendor fixes their side. We also truncated the log and added error_reporting( E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED ) to wp-config so real errors stay findable.

Diagnosis to deployed fix: about ten minutes. That's the entire argument for WP_DEBUG_LOG and reading the actual error instead of bisecting plugins for an afternoon.

The graduation: from "this site's hack" to shippable product

The biggest structural change shipped last. All the Astra wiring used to live in the child theme — which meant every new site needed files hand-copied around. It now lives in the plugin (inc/frontend.php), loaded like this:

add_action( 'after_setup_theme', function () {
    if ( get_template() === 'astra' ) {
        require_once AT13_PATH . 'inc/frontend.php';
    }
}, 5 );

Astra active? Full wiring. Any other theme? Silent no-op — the admin pages still work, nothing fatals. An AT13_FRONTEND_LOADED guard plus a defined('AT13_VERSION') check in the child theme means old child-theme copies can coexist as a fallback without ever double-loading.

New site recipe, final form: Astra → Advanced Themer 13 → bare child theme → drop the mu-plugin hotfix in mu-plugins/. Configure from one menu. Ship.


SolarBlu
Scroll to Top