<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Xlinesoft Blog</title>
	<atom:link href="https://xlinesoft.com/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://xlinesoft.com/blog</link>
	<description>Building the best web application builder, one byte at the time</description>
	<lastBuildDate>Tue, 21 Jul 2026 21:56:15 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.4.2</generator>
	<item>
		<title>Building a World Cup Prediction Challenge in PHPRunner</title>
		<link>https://xlinesoft.com/blog/2026/07/21/building-a-world-cup-prediction-challenge-in-phprunner/</link>
					<comments>https://xlinesoft.com/blog/2026/07/21/building-a-world-cup-prediction-challenge-in-phprunner/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Tue, 21 Jul 2026 20:36:19 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3379</guid>

					<description><![CDATA[The World Cup 2026 Crystal Ball Challenge is over! Thank you to everyone who took part and made the challenge fun and engaging. Congratulations to all participants, and especially to the winners for their outstanding predictions! In this tutorial we will cover all non-trivial techniques that made this project possible. As you can imagine, the main challenge making this project look sleek and easy to use and I believe we achieved this goal. See the final leaderboard online We will talk about: 1. Creating image-based...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2026/07/21/building-a-world-cup-prediction-challenge-in-phprunner/" class="more-link">Continue Reading <span class="screen-reader-text">"Building a World Cup Prediction Challenge in PHPRunner"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>The World Cup 2026 Crystal Ball Challenge is over! Thank you to everyone who took part and made the challenge fun and engaging. Congratulations to all participants, and especially to the winners for their outstanding predictions!</p>
<p>In this tutorial we will cover all non-trivial techniques that made this project possible. As you can imagine, the main challenge making this project look sleek and easy to use and I believe we achieved this goal. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_leaderboard.png"><img fetchpriority="high" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_leaderboard.png" alt="" width="900" height="505" class="alignnone size-full wp-image-3383" srcset="https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_leaderboard.png 900w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_leaderboard-600x337.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_leaderboard-768x431.png 768w" sizes="(max-width: 900px) 100vw, 900px" /></a></p>
<p><a href="https://xlinesoft.com/projects/worldcup2026/leaderboard_list.php"><i class="fas fa-link"></i> See the final leaderboard online</a></p>
<p>We will talk about:</p>
<p>1. Creating image-based edit controls<br />
2. Grouping team pickers and preventing duplicate selections<br />
3. Enforcing one prediction per user with PHPRunner events<br />
4. Disabling Add and Edit after the submission deadline<br />
5. Enabling Save only after all required answers are selected<br />
6. Calculating scores and rankings with custom SQL<br />
7. Adding rank graphics, progress bars, and current-user highlighting<br />
8. Integrating custom landing, scoring, and thank-you pages<br />
9. A few more visual tweaks<br />
10. How to create your own custom Edit controls easily</p>
<p><span id="more-3379"></span></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2026/07/WorldCup2026Codex_20260721215446607.zip"><i class="fas fa-download"></i> Download project for PHPRunner</a></p>
<h3>1. Creating image-based edit controls with table and SQL lookups</h3>
<p>I think this is the most important part. We introduced three image-based custom edit controls that you can use in other applications as well. All three are available for download on our marketplace. Right now it is for PHPRunner only but we will provide ASPRunner.NET equivalents very soon. We will also teach you how to create new custom Edit controls using ChatGPT. </p>
<p><strong>ImagePicker</strong></p>
<p><a href="https://xlinesoft.com/marketplace/ImagePicker"><i class="fas fa-link"></i> ImagePicker on marketplace</a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagepicker.png"><img decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagepicker.png" alt="" width="1302" height="854" class="alignnone size-full wp-image-3385" srcset="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagepicker.png 1302w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagepicker-600x394.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagepicker-1024x672.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagepicker-768x504.png 768w" sizes="(max-width: 1302px) 100vw, 1302px" /></a></p>
<p>This control allows a selection of a single country. We display all countries and their flags in a scrollable popup and allow to select one. Once country is selected the popup is closed. </p>
<p><strong>ImageDropdown</strong></p>
<p><a href="https://xlinesoft.com/marketplace/ImageDropdown"><i class="fas fa-link"></i> ImageDropdown on marketplace</a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagedropdown.png"><img decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagedropdown.png" alt="" width="1241" height="612" class="alignnone size-full wp-image-3387" srcset="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagedropdown.png 1241w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagedropdown-600x296.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagedropdown-1024x505.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagedropdown-768x379.png 768w" sizes="(max-width: 1241px) 100vw, 1241px" /></a></p>
<p>This is an advanced version of a regular dropdown box. We have used it to select podium teams and top scorer players. Displaying country flag next to player name definitely makes it easier to choose the player you want.  </p>
<p><strong>ImageChoice</strong></p>
<p><a href="https://xlinesoft.com/marketplace/ImageChoice"><i class="fas fa-link"></i> ImageChoice on marketplace</a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagechoice-2.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagechoice-2.png" alt="" width="1024" height="375" class="alignnone size-full wp-image-3386" srcset="https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagechoice-2.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagechoice-2-600x220.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/imagechoice-2-768x281.png 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<p>This is something that resembles a regular radio-button control, it just looks nicer. We have used to allow a selection of the host team that advances further.  </p>
<h3>2. Grouping team pickers and preventing duplicate selections</h3>
<p>The Top 8 prediction is stored in eight fields: top8_team1_id through top8_team8_id. Without additional logic, eight independent lookup controls would allow the same country to be selected several times. The project solves this inside the custom edit control by assigning all eight controls to one group.</p>
<p>Each Top 8 field uses the following group settings:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$this->settings["picker_group"] = "top8";
$this->settings["max_group_count"] = 8;
$this->settings["status_element_id"] = ["status_element_id"];
$this->settings["prevent_duplicates"] = 1;</textarea></pre>
</div>
<p>The group name tells the controls that they belong to the same logical question. When a country is selected in one field, duplicate prevention makes it unavailable to the other seven. The shared status element (&#8220;status_element_id&#8221;) can report progress across the whole group rather than treating each field as an unrelated input. You can see status field saying something like &#8220;7 of 8 selected&#8221; at the top right corner of the first section. </p>
<p>The podium fields use the same idea with a different group:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$this->settings["picker_group"] = "podium";
$this->settings["prevent_duplicates"] = 1;</textarea></pre>
</div>
<p>Champion, second place, and third place remain separate database columns, but they behave as one coordinated set of choices. This is useful in many PHPRunner projects: room assignments, ranked preferences, product bundles, team selection, or any form where several fields use the same type of lookup wizard but must not repeat the same value.</p>
<h3>3. Enforcing one prediction per user with PHPRunner events</h3>
<p>Every participant should have one prediction record. Hiding the Add button after submission is not sufficient because the user could revisit the Add URL directly. The project checks for an existing record in the prediction table&#8217;s Before Process Add event.</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$currentUser = Security::getUserName();

$sql = DB::PrepareSQL(
    "SELECT p.id
     FROM wc_predictions p
     INNER JOIN wc_users u ON u.id = p.user_id
     WHERE u.email = ':1'
     LIMIT 1",
    $currentUser
);

$existingId = DB::DBLookup($sql);

if ($existingId) {
    header(
        "Location: wc_predictions_edit.php?editid1="
        . rawurlencode($existingId)
    );
    exit();
}</textarea></pre>
</div>
<p>Security::getUserName() returns the logged-in email because email is configured as the PHPRunner username field. The query translates that login identity into a prediction record ID. If the record exists, the user is redirected to Edit instead of being allowed to create another row.</p>
<p>The project also performs the same lookup in the Menu event. A normal user who reaches the menu is sent to Edit when a prediction exists or Add when it does not. The Menu event improves navigation; the Before Process Add event enforces the rule at the page boundary.</p>
<h3>4. Disabling Add and Edit after the submission deadline</h3>
<p>Prediction forms must close at a known time. Instead of only hiding buttons, the project changes the table permission string in the Table permissions event. This affects PHPRunner&#8217;s permission checks for every request. The following code was added to wc_predictions table GetTablePermissions event. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$lockDate = "2026-06-28 00:00:00";

if (date("Y-m-d H:i:s") >= $lockDate) {
    $permissions = str_replace(
        array("A", "E"),
        "",
        $permissions
    );
}

return $permissions;</textarea></pre>
</div>
<p>PHPRunner represents table operations with letters in the permission string. Removing A and E disables Add and Edit while leaving allowed read operations intact. This is stronger than CSS or JavaScript because it is evaluated on the server.</p>
<p>The project also detects direct requests for wc_predictions_add.php or wc_predictions_edit.php and redirects them to a read-only page. That redirect is useful feedback, but the permission change is the actual security control.</p>
<p>The example has a hard-coded date. A reusable implementation should read the deadline and timezone from an application settings table. It should also locate the current user&#8217;s prediction ID before redirecting to View so the destination contains a valid editid1 key.</p>
<h3>5. Enabling &#8216;Save&#8217; button only after all required answers are selected</h3>
<p>PHPRunner&#8217;s normal validation works field by field. This form also needs a page-level rule: the participant should not submit until every required prediction category is complete. The Add and Edit JavaScript OnLoad events monitor the required controls and toggle the Save item.</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="javascript" name="mshighlighter" >
var requiredFields = [
    "top8_team1_id", "top8_team2_id",
    "top8_team3_id", "top8_team4_id",
    "top8_team5_id", "top8_team6_id",
    "top8_team7_id", "top8_team8_id",
    "champion_team_id",
    "second_place_team_id",
    "third_place_team_id",
    "top_scorer_id",
    "final_goes_to_penalties",
    "host_team_goes_furthest_id"
];

function allRequiredAnswered() {
    return requiredFields.every(function(fieldName) {
        var ctrl = Runner.getControl(pageid, fieldName);
        return ctrl && $.trim((ctrl.getValue() || "") + "") !== "";
    });
}

function refreshSaveButton() {
    var button = pageObj.getItemButton("add_save");
    button.toggleClass("disabled", !allRequiredAnswered());
}</textarea></pre>
</div>
<p>The important detail is that the page contains both standard PHPRunner inputs and custom image controls. Listening only to ordinary change events is not enough, so the project also listens for interaction with the custom-control elements:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="javascript" name="mshighlighter" >
$(document).on(
    "change keyup click",
    "input, select, textarea, .ip-control, .idrop-control, .ichoice-card",
    function() {
        setTimeout(refreshSaveButton, 50);
    }
);</textarea></pre>
</div>
<p>The short delay lets the custom editor update its PHPRunner control value before the page checks it. The Edit page uses the same code with the edit_save item ID. The script also adds min=&#8221;0&#8243; and max=&#8221;10&#8243; to the final-score inputs.</p>
<p>This improves the user experience, but it is not a replacement for server-side validation. Required values and numeric ranges should still be checked in Before Add and Before Edit before the record is written.</p>
<h3>6. Calculating scores and rankings with custom SQL</h3>
<p>The Leaderboard is implemented as a custom view. We wanted to perform the whole score calculation in SQL query so it looks quite tricky. </p>
<p>Top 8 selections are unordered. Each predicted team is tested against all eight actual Top 8 fields:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="sql" name="mshighlighter" >
CASE
    WHEN p.top8_team1_id > 0
     AND p.top8_team1_id IN (
        NULLIF(a.top8_team1_id, 0),
        NULLIF(a.top8_team2_id, 0),
        NULLIF(a.top8_team3_id, 0),
        NULLIF(a.top8_team4_id, 0),
        NULLIF(a.top8_team5_id, 0),
        NULLIF(a.top8_team6_id, 0),
        NULLIF(a.top8_team7_id, 0),
        NULLIF(a.top8_team8_id, 0)
    )
    THEN 1 ELSE 0
END</textarea></pre>
</div>
<p>NULLIF(&#8230;, 0) prevents an unanswered actual-result field stored as zero from being treated as a legitimate team. The pattern is repeated for all eight predicted positions and then multiplied by five for the point score.</p>
<p>The exact-position categories use direct comparisons with their own weights:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="sql" name="mshighlighter" >
CASE
    WHEN a.champion_team_id > 0
     AND p.champion_team_id = a.champion_team_id
    THEN 25 ELSE 0
END
+
CASE
    WHEN a.second_place_team_id > 0
     AND p.second_place_team_id = a.second_place_team_id
    THEN 15 ELSE 0
END
+
CASE
    WHEN a.third_place_team_id > 0
     AND p.third_place_team_id = a.third_place_team_id
    THEN 10 ELSE 0
END</textarea></pre>
</div>
<p>The remaining categories add points for top scorer, both final-score values, penalties, and the host nation progressing furthest. The result is exposed as normal Leaderboard fields, allowing PHPRunner to build a List page over calculated data.</p>
<p>The exported query orders by points DESC. If the published rules promise additional tie breakers—correct picks and then submission time—the ORDER BY should explicitly include them:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="sql" name="mshighlighter" >
ORDER BY points DESC, correct_picks DESC, submitted_at ASC</textarea></pre>
</div>
<h3>7. Adding rank graphics, progress bars, and current-user highlighting</h3>
<p>The SQL produces leaderboard data, but the List page turns it into a competition display. Before processing the List page, an event resets the rank counter and removes any previous current-user standing from the session:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$_SESSION["rank"] = 1;
unset(
    $_SESSION["leaderboard_current_rank"],
    $_SESSION["leaderboard_current_points"],
    $_SESSION["leaderboard_current_player"]
);</textarea></pre>
</div>
<p>Before each row is displayed, the project compares the row&#8217;s player with the current user&#8217;s display name. When they match, it stores the rank and points for the standing panel and highlights the row:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$userData = Security::currentUserData();

if ($data["player"] == $userData["display_name"]) {
    $_SESSION["leaderboard_current_rank"] = $_SESSION["rank"];
    $_SESSION["leaderboard_current_points"] = $data["points"];
    $record["css"] = "background:#fff7dc;";
}

$_SESSION["rank"]++;</textarea></pre>
</div>
<p>Custom field formats replace raw values with visual components. Positions 1, 2, and 3 use gold, silver, and bronze images. The correct_picks value becomes both text and a progress bar:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$percent = $value * 100 / 16;

$value = '<div class="wc-correct-picks">
    <span class="wc-correct-picks-value">'.$value.' / 16</span>
    <span class="wc-progress-track">
        <span class="wc-progress-fill" style="width:'.$percent.'%"></span>
    </span>
</div>';</textarea></pre>
</div>
<p>Two Page Designer snippets complete the page. The header displays the latest actual-answer update time, and the standing snippet reads the session values to show “Your current standing: #N with N points.” This is a useful example of coordinating List events, custom field formats, session state, snippets, images, and CSS on one generated page.</p>
<p>For a larger application, compare users by an immutable user ID rather than display_name. Display names can change and may not be unique.</p>
<h3>8. Integrating custom landing, scoring, and thank-you pages</h3>
<p>Not every page in a PHPRunner application must be generated from a database table. This project carries standalone files in the export&#8217;s files/ directory. PHPRunner copies them into the output folder when the application is built.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_index.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_index.png" alt="" width="900" height="517" class="alignnone size-full wp-image-3392" srcset="https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_index.png 900w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_index-600x345.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2026/07/world_cup_index-768x441.png 768w" sizes="(max-width: 900px) 100vw, 900px" /></a></p>
<p>index.php is the public homepage/landing page. It explains the challenge, dates, prizes, and entry process, then links visitors to PHPRunner&#8217;s register.php and login.php. scoring.php explains the rules implemented by the Leaderboard SQL. thankyou.php provides a deliberate completion screen after the participant saves the initial prediction.</p>
<p>The connection back to PHPRunner happens through events and ordinary URLs. For example, the prediction After Add event sends the browser to the standalone confirmation page:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
header("Location: thankyou.php");
exit();</textarea></pre>
</div>
<p>Registration uses the opposite direction: a generated PHPRunner page sends the new account into the prediction workflow after assigning its role and logging it in:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
// Before Register
$userdata["usertype"] = "user";
return true;

// After Register
Security::loginAs($userdata["email"]);
header("Location: wc_predictions_add.php");
exit();</textarea></pre>
</div>
<p>The exported files also include include/wc_functions.php, which is loaded by the global initialization event. It provides shared rendering functions used by prediction View and leaderboard formats, allowing generated pages to display the same flags and labels as the custom controls.</p>
<p>This approach keeps authentication, permissions, and data entry inside PHPRunner while allowing marketing, rules, and confirmation pages to have purpose-built layouts. The important maintenance rule is to keep URLs, asset paths, and shared includes consistent between the standalone files and generated output.</p>
<h3>9. A few more visual tweaks</h3>
<p><strong>Yes/No switch on Add/Edit pages</strong></p>
<p>We decided that using a new Edit control for such a simple task would be an overkill. Instead, we used a simple radio-button control for &#8216;Final goes to penalty?&#8217; field and a bit of CSS and Javascript OnLoad event of Add/Edit pages. Javascript basically assigns CSS class to radio-button control and the styling itself is done in CSS. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
$(function() {
    $('input[name^="radio_final_goes_to_penalties_"]')
        .closest('.rnr-horizontal-lookup')
        .addClass('wc-penalty-toggle');
});</textarea></pre>
</div>
<p>Check classes that start with <strong>.wc-penalty-toggle</strong> in Custom CSS.</p>
<p>One more tweak, the display of <strong>correct_picks</strong> field on leaderboard page. It is implemented as &#8216;View as&#8217; Custom and use some additional CSS classes. Here is the code:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$value = '<div class="wc-correct-picks">
    <span class="wc-correct-picks-value">'.$value.' / 16</span>
    <span class="wc-progress-track">
        <span class="wc-progress-fill" style="width: '. $value*100/16 .'%;"></span>
    </span>
</div>';</textarea></pre>
</div>
<p>In this code 16 is the total number of picks and this is how we calculate the percentage of correct picks. </p>
<h3>10. How to create your own custom Edit controls easily</h3>
<ul>
<li><a href="https://xlinesoft.com/blog/wp-content/uploads/2026/07/phprunner_custom_edit_controls_skill_pack.zip"><i class="fas fa-link"></i> Download this skill pack</a></li>
<li>Start a new ChatGPT conversation</li>
<li>Upload the ZIP</li>
<li>Tell ChatGPT: &#8220;Use the PHPRunner custom Edit controls skill from the uploaded ZIP.&#8221;</li>
<li>Then describe the control you want. For instance:
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
Use the PHPRunner custom Edit controls skill.

Create a custom Edit control called CurrencyInput.

Purpose:
A currency-friendly numeric input that formats values instantly for display, while submitting a clean numeric value to the database.

Requirements:
- User can type 1234.5 and see $1,234.50.
- Submitted value should be 1234.50, without currency symbol or thousand separators.
- Support configurable currency_symbol, symbol_position, decimal_places, thousand_separator, decimal_separator, allow_negative, format_on_load, and format_while_typing.
- Empty value should remain empty.
- Must work with normal Add/Edit form submission.
- Provide PHP, JavaScript, CSS, sample.php, and README.txt.
- sample.php must contain settings code only, without PHP opening/closing tags.
- Package files at the root of a ZIP archive.</textarea></pre>
</div>
</li>
</ul>
<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2026/07/21/building-a-world-cup-prediction-challenge-in-phprunner/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Version 11.3</title>
		<link>https://xlinesoft.com/blog/2025/12/24/version-11-3/</link>
					<comments>https://xlinesoft.com/blog/2025/12/24/version-11-3/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 24 Dec 2025 16:20:46 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3330</guid>

					<description><![CDATA[Version 11.3 release is here! Find trial version download links below. If you are a customer with a valid maintenance, you can download registered version 11.3 via your control panel. You can watch this video on YouTube that explains how to use new features. Trial version download links 1. AI features. Help with writing the code ( PHP, C#, Javascript, CSS, SQL ). More info in the manual Video on AI features from Corrie 2. Intellisense. Code completion in all areas where code can be...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/12/24/version-11-3/" class="more-link">Continue Reading <span class="screen-reader-text">"Version 11.3"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p><strong>Version 11.3 release is here!</strong></p>
<p>Find trial version download links below. If you are a customer with a valid maintenance, you can download registered version 11.3 via your <span style="width: 300px; display: inline-block;"><a style="color: white;" class="btn_purple" href="https://xlinesoft.com/dss/support.php">control panel</a></span>. </p>
<p>You can watch <a href="https://youtu.be/5fX1vV7Z_nw">this video on YouTube</a> that explains how to use new features. </p>
<p><span style="width: 300px; display: inline-block;"><a style="color: white;" class="btn_purple" href="https://xlinesoft.com/download">Trial version download links</a></span></p>
<p>1. AI features. Help with writing the code ( PHP, C#, Javascript, CSS, SQL ).<br />
<a href="https://xlinesoft.com/phprunner/docs/cody_ai_assistant.htm">More info in the manual</a><br />
<a href="https://www.youtube.com/watch?v=bKS9y_7d8uM">Video on AI features from Corrie</a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/event_editor_ai_assistant.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/event_editor_ai_assistant-600x333.png" alt="" width="600" height="333" class="alignnone size-medium wp-image-3365" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/event_editor_ai_assistant-600x333.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/event_editor_ai_assistant-1024x568.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/event_editor_ai_assistant-768x426.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/event_editor_ai_assistant-1536x853.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/event_editor_ai_assistant.png 1598w" sizes="(max-width: 600px) 100vw, 600px" /></a><br />
<span id="more-3330"></span></p>
<p>2. Intellisense. Code completion in all areas where code can be edited.<br />
<a href="https://xlinesoft.com/phprunner/docs/events.htm#intellisense">More info</a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/intellisense_dal.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/intellisense_dal-600x389.png" alt="" width="600" height="389" class="alignnone size-medium wp-image-3364" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/intellisense_dal-600x389.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/intellisense_dal-768x498.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/intellisense_dal.png 896w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>3. Save project as template<br />
<a href="https://xlinesoft.com/phprunner/docs/working_with_projects.htm#save_as_template">More info</a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template-600x332.png" alt="" width="600" height="332" class="alignnone size-medium wp-image-3362" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template-600x332.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template-768x425.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template.png 901w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>4. Export/import of individual objects between projects. We you export a table in includes everything that is relevant: field settings, page designs, events etc.<br />
<a href="https://xlinesoft.com/phprunner/docs/datasource_tables_screen.htm#exportimport">More info</a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/import_table_create.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/import_table_create-600x356.png" alt="" width="600" height="356" class="alignnone size-medium wp-image-3363" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/import_table_create-600x356.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/import_table_create-768x455.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/import_table_create.png 899w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>5. Small things. Adding search to all dialogs where more than a dozen objects is listed: list of projects, list of buttons, or code snippets etc. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template-600x332.png" alt="" width="600" height="332" class="alignnone size-medium wp-image-3362" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template-600x332.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template-768x425.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/save_as_template.png 901w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>6. New cloud providers: Google Cloud and Azure Blob Storage<br />
<a href="https://xlinesoft.com/phprunner/docs/google-cloud-storage.htm">Google Cloud Storage info</a><br />
<a href="https://xlinesoft.com/phprunner/docs/azure-blob-storage.htm">Azure Blob Storage info</a></p>
<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/12/24/version-11-3/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title>Version 11.2</title>
		<link>https://xlinesoft.com/blog/2025/12/10/version-11-2/</link>
					<comments>https://xlinesoft.com/blog/2025/12/10/version-11-2/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 10 Dec 2025 15:50:13 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3236</guid>

					<description><![CDATA[The final version of PHPRunner/ASPRunner.NET 11.2 is here! To download a registered version logon to your control panel account and find download links under &#8216;My purchases&#8217;. All others can find trial version links below. Trial version download links PHPRunner 11.2 for Windows trial &#160;&#160; ASPRunner.NET 11.2 for Windows trial PHPRunner 11.2 for Mac trial PHPRunner 11.2 for Linux (Debian) trial PHPRunner 11.2 for Linux (RedHat) trial You can install and run it side by side with versions 10.x and 11.1. Existing software functionality will not...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/12/10/version-11-2/" class="more-link">Continue Reading <span class="screen-reader-text">"Version 11.2"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>The final version of PHPRunner/ASPRunner.NET 11.2 is here! To download a registered version logon to your <a href='https://xlinesoft.com/dss/support.php'>control panel</a> account and find download links under &#8216;My purchases&#8217;. </p>
<p>All others can find trial version links below. </p>
<h3>Trial version download links</h3>
<p><a href="https://asprunner.com/files/phprunner-trial.exe" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.2 for Windows trial</a><br />
&nbsp;&nbsp;<br />
<a href="https://asprunner.com/files/asprunnernet-trial.exe" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">ASPRunner.NET 11.2 for Windows trial</a></p>
<p><a href="https://asprunner.com/files/phprunner-trial.dmg" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.2 for Mac trial</a></p>
<p><a href="https://asprunner.com/files/phprunner-trial.deb" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.2 for Linux (Debian) trial</a></p>
<p><a href="https://asprunner.com/files/phprunner-trial.rpm" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.2 for Linux (RedHat) trial</a></p>
<p>You can install and run it side by side with versions 10.x and 11.1. Existing software functionality will not be affected. Just make sure you do not launch previous version and v11.2 at the same time if you use a built-in database to store projects. </p>
<p>This new version comes with ten improvements. </p>
<h3>1. Cards layout on the List page</h3>
<p>Similar to vertical or columns layout but lightweight. No field names, just the data. Fixed width and height. View page of the record can be open by click on the whole card on on one of the fields. Customization: list of fields, layout, fonts, colors, paddings, borders, rounded corners. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-600x362.png" alt="" width="600" height="362" class="alignnone size-medium wp-image-3284" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-600x362.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-1024x618.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-768x463.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-1536x927.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web.png 1667w" sizes="(max-width: 600px) 100vw, 600px" /></a><br />
<span id="more-3236"></span></p>
<h3>2. Two new visual themes</h3>
<p>New visual themes are named Futuro (light) and Obsidian (dark). You can choose them on the Style Editor screen. They are modern, sleek and lightweight. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/light.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/light-600x325.png" alt="" width="600" height="325" class="alignnone size-medium wp-image-3311" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/light-600x325.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/light-1024x555.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/light-768x416.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/light-1536x832.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/light.png 1920w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/dark.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/dark-600x323.png" alt="" width="600" height="323" class="alignnone size-medium wp-image-3309" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/dark-600x323.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/dark-1024x550.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/dark-768x413.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/dark-1536x826.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/dark.png 1920w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>3. Three new welcome page layouts</h3>
<p>New Welcome page layouts are called Nebula, Optima and Sonic. You can switch between them while on the Page Designer screen and menu page is open. Here is an example of how Nebula layout looks. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/welcome.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/welcome-600x376.png" alt="" width="600" height="376" class="alignnone size-medium wp-image-3310" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/welcome-600x376.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/welcome-1024x641.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/welcome-768x481.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/welcome.png 1462w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>4. Dashboards tabs</h3>
<p>You can combine several dashboard elements into a single one and use tabs to switch between them. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/dashboard_tabs.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/dashboard_tabs-600x402.png" alt="" width="600" height="402" class="alignnone size-medium wp-image-3308" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/dashboard_tabs-600x402.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/dashboard_tabs-768x515.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/dashboard_tabs.png 786w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>5. Dashboard widgets</h3>
<p>Similar to dashboard code snippets but easier to customize. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/widgets.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/widgets-600x128.png" alt="" width="600" height="128" class="alignnone size-medium wp-image-3316" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/widgets-600x128.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/widgets-768x164.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/widgets.png 853w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>6. SMTP with OAuth support</h3>
<p>At this moment we support Gmail Workplace and Microsoft Exchange accounts.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/oauth.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/oauth-600x456.png" alt="" width="600" height="456" class="alignnone size-medium wp-image-3326" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/oauth-600x456.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/oauth-768x583.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/oauth.png 828w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>7. &#8220;Javascript Create TinyMCE&#8221; event for TinyMCE customization</h3>
<p>This new event allows you to customize TinyMCE editor behavior across the whole project. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/tinymce.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/tinymce-600x309.png" alt="" width="600" height="309" class="alignnone size-medium wp-image-3324" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/tinymce-600x309.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/tinymce-768x396.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/tinymce.png 896w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>8. ‘View as’ option Copy value to clipboard</h3>
<p>This option can be enabled for any text field under &#8216;View as&#8217; settings.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/clipboard.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/clipboard-600x88.png" alt="" width="600" height="88" class="alignnone size-medium wp-image-3320" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/clipboard-600x88.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/clipboard.png 603w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>9. Foldable elements in dashboards</h3>
<p>All dashboard elements can be folded by clicking on their header. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/foldable.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/foldable-600x376.png" alt="" width="600" height="376" class="alignnone size-medium wp-image-3318" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/foldable-600x376.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/foldable-1024x641.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/foldable-768x481.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/foldable.png 1462w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>10. Show details key column as a hyperlink</h3>
<p>Hyperlink points to the View page of the master and can be open in the same window, in a new window or in a popup. The same functionality for the Lookup Wizard field in view mode. On screenshot below OrderID is a foreign column in &#8216;Order Details&#8217; table. It is set to open master table data in a popup.  </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/12/foreign.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/12/foreign-600x480.png" alt="" width="600" height="480" class="alignnone size-medium wp-image-3322" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/12/foreign-600x480.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/foreign-768x614.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/12/foreign.png 1002w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/12/10/version-11-2/feed/</wfw:commentRss>
			<slash:comments>14</slash:comments>
		
		
			</item>
		<item>
		<title>Version 11.2 &#8211; work in progress</title>
		<link>https://xlinesoft.com/blog/2025/10/30/version-11-2-work-in-progress/</link>
					<comments>https://xlinesoft.com/blog/2025/10/30/version-11-2-work-in-progress/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Thu, 30 Oct 2025 15:41:17 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3276</guid>

					<description><![CDATA[Just wanted to share a preview of a couple of new features coming in version 11.2. This update is mostly about UI improvements in the generated web applications and we&#8217;ll show you new cards layout and dashboard improvements. More details on what is new in version 11.2. 1. Cards layout Cards layout is a compact way to present the information on the List page. Consistent design, all cards look the same, automatically responsive, adjusts to the screen size. No extra information like field names. Previously...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/10/30/version-11-2-work-in-progress/" class="more-link">Continue Reading <span class="screen-reader-text">"Version 11.2 &#8211; work in progress"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Just wanted to share a preview of a couple of new features coming in version 11.2. This update is mostly about UI improvements in the generated web applications and we&#8217;ll show you new cards layout and dashboard improvements.</p>
<p><a target="_new" href="https://xlinesoft.com/blog/2025/09/23/version-11-2/" rel="noopener">More details on what is new in version 11.2. </a></p>
<h3>1. Cards layout</h3>
<p>Cards layout is a compact way to present the information on the List page. Consistent design, all cards look the same, automatically responsive, adjusts to the screen size. No extra information like field names. Previously this required some trickery and now this is a built-in feature. Take a look at this example. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-600x362.png" alt="" width="600" height="362" class="alignnone size-medium wp-image-3284" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-600x362.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-1024x618.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-768x463.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web-1536x927.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_web.png 1667w" sizes="(max-width: 600px) 100vw, 600px" /></a><br />
<span id="more-3276"></span></p>
<p>Cards layout can be customized in the Page Designer. You can choose which fields will be displayed and what are their types: image, title, subtitle, price etc. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_designer.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_designer-600x348.png" alt="" width="600" height="348" class="alignnone size-medium wp-image-3283" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_designer-600x348.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_designer-1024x595.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_designer-768x446.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_designer-1536x892.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/cards_designer.png 1624w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>2. Dashboard improvements</h3>
<p>Two main new features are tabbed elements and foldable elements. You can now fit even more info into a single dashboard using tabbed elements. This is how it looks in the generated application. You can see &#8216;Customers&#8217; tabbed section including multiple elements. You can also see &#8216;Alphabetical list pf products&#8217; element being collapsed. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-5.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-5-600x420.png" alt="" width="600" height="420" class="alignnone size-medium wp-image-3280" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-5-600x420.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-5-1024x716.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-5-768x537.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-5.png 1352w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>And here is how it looks in the Page Designer. You can see tabbed section on the left side and properties like &#8216;foldable&#8217; and &#8216;closed initially&#8217; on the right. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-6.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-6-600x323.png" alt="" width="600" height="323" class="alignnone size-medium wp-image-3279" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-6-600x323.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-6-1024x550.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-6-768x413.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-6-1536x826.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/image-6.png 1920w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/10/30/version-11-2-work-in-progress/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Using Azure Key Vault to store connection strings</title>
		<link>https://xlinesoft.com/blog/2025/10/15/using-azure-key-vault-to-store-connection-strings/</link>
					<comments>https://xlinesoft.com/blog/2025/10/15/using-azure-key-vault-to-store-connection-strings/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 15 Oct 2025 16:13:44 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3260</guid>

					<description><![CDATA[Azure Key Vault offers secure storage for sensitive information like passwords, secrets etc. If you host your PHPRunner or ASPRunner.NET application on Azure, it will makes sense to store your connection strings in Key Vault. In this article we&#8217;ll show you how this can be done. Creating Key Vault in Azure 1. Create an application in Azure. Write down tenantId, clientId and clientSecret values. 2. Logon to Azure and create a subscription. You need to specify subscription name and select a billing account. More info:...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/10/15/using-azure-key-vault-to-store-connection-strings/" class="more-link">Continue Reading <span class="screen-reader-text">"Using Azure Key Vault to store connection strings"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Azure Key Vault offers secure storage for sensitive information like passwords, secrets etc. If you host your PHPRunner or ASPRunner.NET application on Azure, it will makes sense to store your connection strings in Key Vault. In this article we&#8217;ll show you how this can be done.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/10/azure_key_vault2.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/10/azure_key_vault2-600x315.png" alt="" width="600" height="315" class="alignnone size-medium wp-image-3272" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/10/azure_key_vault2-600x315.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/azure_key_vault2-768x403.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/10/azure_key_vault2.png 772w" sizes="(max-width: 600px) 100vw, 600px" /></a><br />
<span id="more-3260"></span></p>
<h3>Creating Key Vault in Azure</h3>
<p>1. Create an application in Azure. Write down tenantId, clientId and clientSecret values.</p>
<p>2. Logon to <a href="https://portal.azure.com/#home">Azure</a> and create a subscription. You need to specify subscription name and select a billing account. </p>
<p>More info:<br />
<a href="https://learn.microsoft.com/ru-ru/azure/cost-management-billing/manage/create-subscription">https://learn.microsoft.com/ru-ru/azure/cost-management-billing/manage/create-subscription</a></p>
<p>3. Go back to portal home and proceed to Key Vaults. Click &#8216;Create&#8217;. Select a subscription, select a Resource group ( or create a new one ). Enter Key Vault name. </p>
<p>4. Now it is the time to assign access permissions. Under the Key Vault you just created proceed to &#8216;Access control (IAM)&#8217;. Add -> Add Role assigment, select &#8216;Key Vault Certificates Officer&#8217; using search option, click &#8216;Next&#8217;. Select &#8216;User, group, or service principal&#8217; </p>
<p>Click &#8216;Select members&#8217; search for your application name and select it. . в поисковой строке справа найти свое приложение (не пользолвателя). Next, Review + assign. </p>
<p>Permissions may take a bit of time to be applied but in our situation it worked right away. </p>
<p>5. Create a secret. The secret is the actual object that stores the sensitive information, database password in our case. Select your Key Vault and under Objects -> Secrets -> Generate/Import enter the name of the secret ( &#8220;pass&#8221; in our case ) and its value. </p>
<p>Now we are ready to use this in our code. </p>
<h3>Using Key Vault in PHPRunner</h3>
<p>1. Under Style Editor -> Custom Files add a new file named keyvault.php. Paste the code below and use your own values of tenantId, clientId and clientSecret. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
<?php
function getSecret($secretName){
	$tenantId = '...';
	$clientId = '...'; 
	$clientSecret = '...';
	$vaultUrl = "https://xkvault.vault.azure.net/";

	$url = "https://login.microsoftonline.com/".$tenantId."/oauth2/token";
	$parameters = array(
		'grant_type' => 'client_credentials',
		'client_id' => $clientId,
		'client_secret' => $clientSecret,
		'resource' => 'https://vault.azure.net'
	);
	$headers = array("Content-Type"=>"application/x-www-form-urlencoded");
	$response = runner_post_request($url, $parameters, $headers);

	$json = runner_json_decode($response["content"]);
	if (!isset($json['access_token'])) {
		throw new Exception("Failed to get access token: " . $response);
	}
	$accessToken = $json['access_token'];

	$url = $vaultUrl . "secrets/".$secretName."?api-version=7.3";

	$headers = array(
		"Authorization"=>"Bearer ".$accessToken,
		"Content-Type"=>"application/json"
	);
	$response = runner_http_request($url, "", "GET", $headers);
	$result = runner_json_decode($response["content"]);
	return $result['value'];
}
?></textarea></pre>
</div>
<p>2. An example of using Key Vault in your application. We will create a new Server Database Connection and will retrieve database password from Key Vault. In this example we use MySQL database.</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
include("keyvault.php");
$host="localhost";
$user="root";
$pwd=getSecret("pass");
$port="";
$sys_dbname="cars";</textarea></pre>
</div>
<h3>Using Key Vault in ASPRunner.NET</h3>
<p>1.  Under Style Editor -> Custom Files add a new file named keyvault.cs. Paste the code below and use your own values of tenantId, clientId and clientSecret. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Reflection;
using runnerDotNet;
namespace runnerDotNet
{
	public partial class CommonFunctions
	{
		public static XVar getSecret(dynamic secretName)
		{
			dynamic accessToken = null, clientId = null, clientSecret = null, headers = null, json = XVar.Array(), parameters = null, result = XVar.Array(), tenantId = null, url = null, var_response = XVar.Array(), vaultUrl = null;
			tenantId = new XVar("...");
			clientId = new XVar("...");
			clientSecret = new XVar("...");
			vaultUrl = new XVar("https://xkvault.vault.azure.net/");
			url = XVar.Clone(MVCFunctions.Concat("https://login.microsoftonline.com/", tenantId, "/oauth2/token"));
			parameters = XVar.Clone(new XVar("grant_type", "client_credentials", "client_id", clientId, "client_secret", clientSecret, "resource", "https://vault.azure.net"));
			headers = XVar.Clone(new XVar("Content-Type", "application/x-www-form-urlencoded"));
			var_response = XVar.Clone(MVCFunctions.runner_post_request((XVar)(url), (XVar)(parameters), (XVar)(headers)));
			json = XVar.Clone(CommonFunctions.runner_json_decode((XVar)(var_response["content"])));
			if(XVar.Pack(!(XVar)(json.KeyExists("access_token"))))
			{
				new Exception((XVar)(MVCFunctions.Concat("Failed to get access token: ", var_response)));
			}
			accessToken = XVar.Clone(json["access_token"]);
			url = XVar.Clone(MVCFunctions.Concat(vaultUrl, "secrets/", secretName, "?api-version=7.3"));
			headers = XVar.Clone(new XVar("Authorization", MVCFunctions.Concat("Bearer ", accessToken), "Content-Type", "application/json"));
			var_response = XVar.Clone(MVCFunctions.runner_http_request((XVar)(url), new XVar(""), new XVar("GET"), (XVar)(headers)));
			result = XVar.Clone(CommonFunctions.runner_json_decode((XVar)(var_response["content"])));
			return result["value"];
		}
	}
}</textarea></pre>
</div>
<p>2. In BeforeConnect event use the following code:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
dynamic pass = CommonFunctions.getSecret("pass");
GlobalVars.ConnectionStrings["conn"] = MVCFunctions.Concat("Server=localhost;Database=cars;User Id=root;Password=",pass);</textarea></pre>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/10/15/using-azure-key-vault-to-store-connection-strings/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Moving logic from Javascript to the database</title>
		<link>https://xlinesoft.com/blog/2025/09/04/moving-logic-from-javascript-to-the-database/</link>
					<comments>https://xlinesoft.com/blog/2025/09/04/moving-logic-from-javascript-to-the-database/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Thu, 04 Sep 2025 22:07:33 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3222</guid>

					<description><![CDATA[In this article we will discuss how to minimize the amount of code that handles Javascript on Add/Edit pages. Making your code data-driven will help you easily manage forms with a huge numbers of fields. We will be taking care of functionality like showing/hiding fields, making fields readonly, required, disabled etc. This approach will involve the following steps 1. Creating and populating database tables to store triggers ( when to apply the logic ) and actions ( what happens when trigger goes off ) 2....<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/09/04/moving-logic-from-javascript-to-the-database/" class="more-link">Continue Reading <span class="screen-reader-text">"Moving logic from Javascript to the database"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>In this article we will discuss how to minimize the amount of code that handles Javascript on Add/Edit pages. Making your code data-driven will help you easily manage forms with a huge numbers of fields. We will be taking care of functionality like showing/hiding fields, making fields readonly, required, disabled etc. </p>
<p>This approach will involve the following steps<br />
1. Creating and populating database tables to store triggers ( when to apply the logic ) and actions ( what happens when trigger goes off )<br />
2. Server-side PHP and C# code that passes this data to Javascript<br />
3. Javascript code itself that listens to &#8220;change&#8221; event and implements the logic defined in the database.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/09/f4e4f71c-f4d8-465c-8a1c-67dbe4d205b9.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/09/f4e4f71c-f4d8-465c-8a1c-67dbe4d205b9-600x400.png" alt="" width="600" height="400" class="alignnone size-medium wp-image-3233" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/09/f4e4f71c-f4d8-465c-8a1c-67dbe4d205b9-600x400.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/09/f4e4f71c-f4d8-465c-8a1c-67dbe4d205b9-1024x683.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/09/f4e4f71c-f4d8-465c-8a1c-67dbe4d205b9-768x512.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/09/f4e4f71c-f4d8-465c-8a1c-67dbe4d205b9.png 1536w" sizes="(max-width: 600px) 100vw, 600px" /></a><br />
<span id="more-3222"></span></p>
<h3>Database tables</h3>
<p>Triggers table. The structure is fairly simple. You can see that we store table name, page type, field name and what event we are listening to. In this article we will only show how to implement the most common &#8220;change&#8221; event as it covers 95% of required functionality. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/09/triggers.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/09/triggers-600x104.png" alt="" width="600" height="104" class="alignnone size-medium wp-image-3226" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/09/triggers-600x104.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/09/triggers.png 632w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>Actions table is a details one while triggers is a master. They are linked by trigger_id field. This table knows what condition to check and what action to perform. Lets take a look at the first row and try to decipher it.</p>
<p>The action is tied to trigger #1 ( change event of Country field on the Edit page of customers table). The condition is equal and the condition_value is &#8216;USA&#8217;. Which means that when Country field equals &#8216;USA&#8217; we should proceed with our action. And the action itself is showing of the Region field. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/09/actions.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/09/actions-600x269.png" alt="" width="600" height="269" class="alignnone size-medium wp-image-3225" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/09/actions-600x269.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/09/actions-768x345.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/09/actions.png 789w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>And here is the SQL script that will create both tables for you with sample data.</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="sql" name="mshighlighter" >
CREATE TABLE IF NOT EXISTS `actions` (
  `id` int NOT NULL AUTO_INCREMENT,
  `condition` varchar(250) DEFAULT NULL,
  `target` varchar(250) DEFAULT NULL,
  `action` varchar(250) DEFAULT NULL,
  `condition_value` varchar(250) DEFAULT NULL,
  `trigger_id` int DEFAULT NULL,
  KEY `Index 1` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `actions` (`id`, `condition`, `target`, `action`, `condition_value`, `trigger_id`) VALUES
	(1, 'equals', 'Region', 'show', 'USA', 1),
	(2, 'empty', 'ContactTitle', 'hide', NULL, 2),
	(3, 'notepmty', 'ContactTitle', 'readonly', NULL, 2),
	(4, 'notempty', 'ContactTitle', 'show', NULL, 2),
	(5, 'notequals', 'Region', 'hide', 'USA', 1),
	(6, 'notempty', 'Fax', 'disable', NULL, 2),
	(7, 'empty', 'Fax', 'enable', NULL, 2),
	(8, 'equals', 'Region', 'clear', 'USA', 1),
	(9, 'equals', 'Region', 'focus', 'USA', 1),
	(10, 'equals', 'Region', 'require', 'USA', 1);

CREATE TABLE IF NOT EXISTS `triggers` (
  `id` int NOT NULL AUTO_INCREMENT,
  `table` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `page` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `event` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `field` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `triggers` (`id`, `table`, `page`, `event`, `field`) VALUES
	(1, 'customers', 'edit', 'change', 'Country'),
	(2, 'customers', 'edit', 'change', 'ContactName');</textarea></pre>
</div>
<h3>Server-side code</h3>
<p>Server-side code goes to <strong>BeforeDisplay event</strong> of each page where this functionality needs to be implemented. In our situation this code goes to BeforeDisplay event of Customers table Edit page. The code itself is fairly straightforward. It just dumps all the data from actions and triggers tables and makes this data available in Javascript via proxy object. </p>
<p>Note that for better code structure you need to create an external PHP or C# file, define a function there and place this code into that function. Then in all BeforeDisplay events </p>
<p><strong>PHP code</strong></p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$table = $pageObject->tName;
$page = $pageObject->pageName;

// get all the triggers for the current page and send it to Javascript
$sql = DB::PrepareSQL("select * from triggers WHERE `table`=':1' and page=':2'",
    $table, $page);

$triggers=array();

$rs = DB::Query($sql);
while( $data = $rs->fetchAssoc() )
{
    $triggers[] = $data;
}

// do the same for actions table

$sql = DB::PrepareSQL("SELECT * FROM actions WHERE trigger_id IN (select id from triggers WHERE `table`=':1' and page=':2'",
    $table, $page);

$actions=array();

$rs = DB::Query($sql);
while( $data = $rs->fetchAssoc() )
{
    $actions[] = $data;
}

$pageObject->setProxyValue("triggers", $triggers);
$pageObject->setProxyValue("actions", $actions);</textarea></pre>
</div>
<p><strong>C# code</strong></p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
dynamic actions = XVar.Array(), page = null, sql = null, triggers = XVar.Array();
table = XVar.Clone(pageObject.tName);
page = XVar.Clone(pageObject.pageName);
sql = XVar.Clone(DB.PrepareSQL(new XVar("select * from triggers WHERE `table`=':1' and page=':2'"), (XVar)(table), (XVar)(page)));
triggers = XVar.Clone(XVar.Array());
rs = XVar.Clone(DB.Query((XVar)(sql)));
while(XVar.Pack(data = XVar.Clone(rs.fetchAssoc())))
	{
	triggers.InitAndSetArrayItem(data, null);
	}
sql = XVar.Clone(DB.PrepareSQL(new XVar("SELECT * FROM actions WHERE trigger_id IN (select id from triggers WHERE `table`=':1' and page=':2'"), (XVar)(table), (XVar)(page)));
actions = XVar.Clone(XVar.Array());
rs = XVar.Clone(DB.Query((XVar)(sql)));
while(XVar.Pack(data = XVar.Clone(rs.fetchAssoc())))
	{
	actions.InitAndSetArrayItem(data, null);
	}
pageObject.setProxyValue(new XVar("triggers"), (XVar)(triggers));
pageObject.setProxyValue(new XVar("actions"), (XVar)(actions));
return null;</textarea></pre>
</div>
<h3>Javascript code</h3>
<p>Javascript code goes to Javascript OnLoad event of the page where the action should happen, in our case this is Javascript OnLoad event of Customers table Edit page. The same idea with creating a Javascript function and calling from the external file is also valid here. </p>
<p>You can extend this code by adding more conditions types and more actions. Check inline comments for more info. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="javascript" name="mshighlighter" >
$('input, textarea, select, radio').on('change', function() {
        // 'this' refers to the element that triggered the change event
        const str = $(this).attr('id');
        const match = str.match(/^value_(.*?)_/);
        var name;
        if (match) {
            console.log('Value changed for:', match[1] , 'New value:', $(this).val());
            name = match[1];
            value = $(this).val();
        }
        
        // lets see if we have a trigger associated with the current field
        let triggers = proxy['triggers'];
        let actions = proxy['actions'];
        for (const trigger of triggers) {
            if (trigger["field"]==name ) {
                // loop through actions array to see what kind of actions we need to perform
                for (const action of actions) {
                    if ( trigger["id"] == action["trigger_id"]) {
                        
                        var proceed = false;
                        
                        // check conditions
                        // equals
                        if (action["condition"] == "equals") {
                            if ( action["condition_value"] == value ) {
                                proceed = true;
                            }
                        }
                        // not equals
                        if (action["condition"] == "notequals") {
                            if ( action["condition_value"] != value ) {
                                proceed = true;
                            }
                        }
                        // empty
                        if (action["condition"] == "empty") {
                            if ( value.length==0 ) {
                                proceed = true;
                            }
                        }
                        // not empty
                        if (action["condition"] == "notempty") {
                            if ( value.length!=0 ) {
                                proceed = true;
                            }
                        }
                        
                        // do we have mathcing conditions 
                        
                        if (proceed) {
                            // show action
                            if (action["action"] == "show") {
                                pageObj.showField(action["target"]);
                            }

                            // hide action
                            if (action["action"] == "hide") {
                                pageObj.hideField(action["target"]);
                            }
                            // clear action
                            if (action["action"] == "clear") {
                                Runner.getControl(pageid, action["target"]).setValue();
                            }
                            // focus action
                            if (action["action"] == "focus") {
                                Runner.getControl(pageid, action["target"]).setFocus();
                            }
                            // require action
                            if (action["action"] == "require") {
                                Runner.getControl(pageid, action["target"]).addValidation("IsRequired");
                            }
                            
                        }

                    }
                }
            }
        }    
    });</textarea></pre>
</div>
<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/09/04/moving-logic-from-javascript-to-the-database/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Version 11.1 release</title>
		<link>https://xlinesoft.com/blog/2025/06/17/version-11-1-release/</link>
					<comments>https://xlinesoft.com/blog/2025/06/17/version-11-1-release/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Tue, 17 Jun 2025 16:04:51 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3189</guid>

					<description><![CDATA[Version 11.1 release is here! Is is available for both PHPRunner (Windows, Mac, Linux) and ASPRunner.NET. Version 11 most frequently asked questions answered. To download a registered version logon to your control panel account and find download links under &#8216;My purchases&#8217;. Trial version download links PHPRunner 11.1 for Windows trial &#160;&#160; ASPRunner.NET 11.1 for Windows trial PHPRunner 11.1 for Mac trial PHPRunner 11.1 for Linux (Debian) trial PHPRunner 11.1 for Linux (RedHat) trial You can install and run it side by side with versions 10.x...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/06/17/version-11-1-release/" class="more-link">Continue Reading <span class="screen-reader-text">"Version 11.1 release"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Version 11.1 release is here! Is is available for both PHPRunner (Windows, Mac, Linux) and ASPRunner.NET. </p>
<p><a href="https://asprunner.com/forums/topic/30499-Version-11-Frequently-Asked-Questions">Version 11 most frequently asked questions answered</a>.</p>
<p>To download a registered version logon to your <a href='https://xlinesoft.com/dss/support.php'>control panel</a> account and find download links under &#8216;My purchases&#8217;.</p>
<h3>Trial version download links</h3>
<p><a href="https://asprunner.com/files/phprunner-trial.exe" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.1 for Windows trial</a><br />
&nbsp;&nbsp;<br />
<a href="https://asprunner.com/files/asprunnernet-trial.exe" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">ASPRunner.NET 11.1 for Windows trial</a></p>
<p><a href="https://asprunner.com/files/phprunner-trial.dmg" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.1 for Mac trial</a></p>
<p><a href="https://asprunner.com/files/phprunner-trial.deb" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.1 for Linux (Debian) trial</a></p>
<p><a href="https://asprunner.com/files/phprunner-trial.rpm" class="btn_purple" style="color: white;" contenteditable="false" style="cursor: pointer;">PHPRunner 11.1 for Linux (RedHat) trial</a></p>
<p>You can install and run it side by side with versions 10.x and 11.0. Existing software functionality will not be affected. Just make sure you do not launch v11 and v11.1 at the same time if you use a built-in database to store projects. </p>
<h3>New features in v11.1</h3>
<ul>
<li>Calendar View</li>
<li>GANTT View</li>
<li>Source control systems support ( SVN, git )</li>
<li>Lookup wizard enhancements</li>
</ul>
<p><span id="more-3189"></span></p>
<h3>Calendar View</h3>
<p>When you launch version 11.1 you notice two new buttons on the toolbar, &#8216;Create Calendar&#8217; and &#8216;Create GANTT chart&#8217;. Click &#8216;Create Calendar&#8217;. Choose to create a new table to get familiar with this feature. You can switch to using your own database table later. The software will create a new table for you and will even add some test data. You can now build the project and enjoy a fully-working Calendar page. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/06/calendar.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/06/calendar-600x305.png" alt="" width="600" height="305" class="alignnone size-medium wp-image-3194" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/06/calendar-600x305.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/calendar-1024x521.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/calendar-768x391.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/calendar-1536x781.png 1536w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/calendar.png 1827w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>If you choose to use your own table make sure to proceed to &#8216;Calendar settings&#8217; screen ( between Pages and Fields ) and configure your Calendar. Date field and subject field are mandatory and rest are optional. </p>
<p>PS. Calendar view is based on FullCalendar open source project. </p>
<h3>GANTT View</h3>
<p>GANTT Chart is similar in many ways to the Calendar. We also recommend creating a new table and getting familiar with this feature first. GANTT View specific settings can be found on &#8216;Gantt&#8217; screen. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/06/gantt.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/06/gantt-600x321.png" alt="" width="600" height="321" class="alignnone size-medium wp-image-3198" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/06/gantt-600x321.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/gantt-1024x547.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/gantt-768x410.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/gantt.png 1319w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>You can add, edit, delete tasks, add dependent tasks, use drag-n-drop to change task/subtask dates or update task progress. </p>
<p>PS. We use Frappe Gantt component as a core of this new feature. </p>
<h3>SVN/git support</h3>
<p>Source control system support provides two main scenarios.<br />
1. Using SVN/git for backups only.<br />
2. Single developer working on the same project from different machines. </p>
<p><strong>SVN, backup only</strong></p>
<p>1. Install SVN command-line client software. On Windows we recommend <a href="https://sliksvn.com/download/">SilkSVN</a>. </p>
<p>2. Create a new empty folder in your SVN repository. Checkout it to any folder on your local computer. </p>
<p>3. In PHPRunner/ASPRunner.NET proceed to Project -> Version control settings and select &#8216;Export directory&#8217;, the same one where you performed the checkout. Other settings will be populated automatically.</p>
<p>4. &#8216;Commit after each project saving&#8217; &#8211; this is up to you to decide.</p>
<p>5. &#8216;Update on each project opening&#8217; &#8211; if you use SVN for backups only then turn this option off.  </p>
<p>Once you performed these steps, you will see a new &#8216;Save and commit&#8217; button at the bottom of the software. Use it to save your project and commit changes to repository. </p>
<p>6. To restore a project from backup proceed to the start screen of the software, &#8216;Import&#8217; tab. Click Browse and point the software to project.json file in the checkout folder.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/06/svn.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/06/svn-600x497.png" alt="" width="600" height="497" class="alignnone size-medium wp-image-3204" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/06/svn-600x497.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/svn.png 726w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p><strong>SVN, working from different computers</strong></p>
<p>1. On the first computer repeat all steps for &#8216;Backup only&#8217;. Enable option &#8216;Update on each project opening&#8217;. Now, in order to open this project you will need to use &#8216;Open from version control&#8217; tab on the start screen. </p>
<p>2. On the second computer perform project checkout from SVN. On start screen proceed to &#8216;Open from version control&#8217; tab, click Browse and point it to the folder where you performed the checkout. You will see a dialog with SVN settings, just leave everything as is and click &#8216;OK&#8217;. </p>
<p><strong>Note:</strong> it is important not to use this feature for simultaneous development by multiple developers. Each commit will override changes made on another computer. </p>
<p><strong>GIT</strong></p>
<p>The ideology is the same but there a few extra commands you need to execute manually. </p>
<p>1. Create a new repository at <a href="https://github.com/">github.com</a>, for example JonDoe/Cars</p>
<p>2. Create a directory on your computer, open the terminal and change to that directory.</p>
<p>3. Run these commands, replace <strong>JonDoe/Cars</strong> with the actual repository name:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
git init
git remote add origin https://github.com/JonDoe/Cars.git
git branch -M main</textarea></pre>
</div>
<p>4. Repeat steps 2 and 3 to access your github repository on the second computer.</p>
<p>5. During the first Save and Commit you may encounter this error:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
Author identity unknown
*** Please tell me who you are.</textarea></pre>
</div>
<p>If this happens, run the following commands in the terminal/command line and and try saving and committing again.</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="" name="mshighlighter" >
git config --global user.email "you@example.com"
git config --global user.name "Your Name"</textarea></pre>
</div>
<p>6. Now you can do the same, in PHPRunner/ASPRunner.NET proceed to &#8216;Open from version control&#8217; tab, click Browse and point it to the folder where you performed the checkout.</p>
<h3>Lookup Wizard enhancements</h3>
<p>New feature here is &#8216;Edit selected&#8217; which allows you to edit selected entry in a lookup wizard. Can be handy when you noticed a typo in Lookup Wizard data and want to fix it without leaving the page. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/06/lookupwizard.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/06/lookupwizard-600x437.png" alt="" width="600" height="437" class="alignnone size-medium wp-image-3201" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/06/lookupwizard-600x437.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/lookupwizard-1024x746.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/lookupwizard-768x559.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/06/lookupwizard.png 1056w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/06/17/version-11-1-release/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title>Running PHPRunner application as a Docker container</title>
		<link>https://xlinesoft.com/blog/2025/05/07/running-phprunner-application-as-a-docker-container/</link>
					<comments>https://xlinesoft.com/blog/2025/05/07/running-phprunner-application-as-a-docker-container/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 07 May 2025 15:35:28 +0000</pubDate>
				<category><![CDATA[News]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP Code Generator]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3145</guid>

					<description><![CDATA[What is Docker? Docker is a software platform that allows you to package, distribute, and run applications within self-contained units called containers. These containers include everything the application needs, like code, runtime, and libraries, ensuring consistent execution across different environments. How this can be useful? There are many possible uses of Docker containers. You can use them for testing for instance. If you have a website that runs on PHP 8.1 and want to make sure that switching to PHP 8.3 doesn&#8217;t break anything, you...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/05/07/running-phprunner-application-as-a-docker-container/" class="more-link">Continue Reading <span class="screen-reader-text">"Running PHPRunner application as a Docker container"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>What is Docker? Docker is a software platform that allows you to package, distribute, and run applications within self-contained units called containers. These containers include everything the application needs, like code, runtime, and libraries, ensuring consistent execution across different environments. </p>
<p>How this can be useful? There are many possible uses of Docker containers. You can use them for testing for instance. If you have a website that runs on PHP 8.1 and want to make sure that switching to PHP 8.3 doesn&#8217;t break anything, you can create a container based on PHP 8.3 and fully test your app there before making a switch on the main website. Other options include distributing your application to your customers as a container or using containers for web hosting if your web hosting provider supports this option.  </p>
<p>In this article we will explain how you can package your PHPRunner application as a Docker container. </p>
<h3>Docker Desktop installation</h3>
<p>As a first step you need to install Docker Desktop on your local computer. Just download it from the <a href="https://www.docker.com/products/docker-desktop/">official website</a> and install keeping all default settings. </p>
<p>Once Docker Desktop is installed, launch it. Two main sections we are going to work with are <strong>Images</strong> and <strong>Containers</strong>. </p>
<p>A Docker image is a read-only template that contains everything needed to run an application, including the application&#8217;s code, system tools, and libraries. A Docker container is a running instance of a Docker image Think of an image as a blueprint and a container as the building constructed from that blueprint. </p>
<h3>Project structure</h3>
<p>Create a new folder for our Docker project i.e. C:\Project\Docker. Here is how the default Docker project looks like. <strong>src</strong> folder needs to be created manually and this is where all our PHP files will be stored. Luckily, we do not need to create the rest of file manually, Docker desktop can help us creating those. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/04/project_structure.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/04/project_structure-600x358.png" alt="" width="600" height="358" class="alignnone size-medium wp-image-3152" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/04/project_structure-600x358.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/project_structure-768x458.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/project_structure.png 965w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>Docker init</h3>
<p>Start by opening a terminal ( click Terminal icon in the bottom right corner ). First, switch to your project folder and then run <strong>docker init</strong> command. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
cd C:\Projects\Docker

docker init</textarea></pre>
</div>
<p>Docker will ask you a few questions about what you want to create. Select Apache+PHP web application. You can use up and down keyboard keys to change your selection. Press Enter to make a choice.  </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_init.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_init-600x405.png" alt="" width="600" height="405" class="alignnone size-medium wp-image-3171" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_init-600x405.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_init-1024x691.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_init-768x518.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_init.png 1475w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>For all other questions leave default options. Docker will create an image for you, based on your selection. On <strong>Images</strong> tab you are now going to see a new image. Docker will also tell you that now you need to run <strong>docker compose up &#8211;build</strong> command in order to build and launch your container. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/04/docker_after_init.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/04/docker_after_init-600x392.png" alt="" width="600" height="392" class="alignnone size-medium wp-image-3150" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/04/docker_after_init-600x392.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/docker_after_init-1024x669.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/docker_after_init-768x502.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/docker_after_init.png 1415w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<h3>Dockerfile</h3>
<p>This is how the initial <strong>dockerfile</strong> looks. I just removed some extra comments for brevity. Lets try to create a container and run it. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
# syntax=docker/dockerfile:1

# https://docs.docker.com/go/dockerfile-reference/

FROM php:8.2-apache

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

# Copy app files from the app directory.
COPY ./src /var/www/html

# Switch to a non-privileged user (defined in the base image) that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
USER www-data</textarea></pre>
</div>
<h3>Build and first run</h3>
<p>Without making any changes to <strong>dockerfile</strong> we go back to the terminal and run the suggested build command:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
docker compose up –build</textarea></pre>
</div>
<p>First build will take about a minute and consecutive will be a bit faster. Dcker performs the build and starts our container. You can see container&#8217;s status on Containers tab. You can see that our new container is up and running. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/04/container_running.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/04/container_running-600x402.png" alt="" width="600" height="402" class="alignnone size-medium wp-image-3149" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/04/container_running-600x402.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/container_running-1024x686.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/container_running-768x515.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/container_running.png 1343w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>Which means we can can now open a web browser and type in the following URL: <strong>http://localhost:9000</strong>. This is what you are supposed to see:</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/04/mysqli_missing.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/04/mysqli_missing-600x371.png" alt="" width="600" height="371" class="alignnone size-medium wp-image-3151" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/04/mysqli_missing-600x371.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/mysqli_missing-768x474.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/04/mysqli_missing.png 955w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>Good news: our container is in fact running.<br />
Bad news: mysqli extension is missing and we cannot connect to MySQL.</p>
<h3>Installing mysqli extension</h3>
<p>Quick Google search for &#8220;docker php install mysqli&#8221; points us to the solution and we modify our <strong>dockerfile</strong> in any text editor adding lines 7 and 8. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
# syntax=docker/dockerfile:1

# https://docs.docker.com/go/dockerfile-reference/

FROM php:8.2-apache

# this is the line we have added manually
RUN docker-php-ext-install mysqli

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

# Copy app files from the app directory.
COPY ./src /var/www/html

# Switch to a non-privileged user (defined in the base image) that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
USER www-data</textarea></pre>
</div>
<p>Now we stop the container, run the build command again and once finished we refresh our browser window. This takes us one step further but we are not quite there yet. </p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_mysql_connection_error.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_mysql_connection_error-600x342.png" alt="" width="600" height="342" class="alignnone size-medium wp-image-3181" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_mysql_connection_error-600x342.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_mysql_connection_error-1024x584.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_mysql_connection_error-768x438.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_mysql_connection_error-900x515.png 900w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_mysql_connection_error.png 1135w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>While this error is not very descriptive we can guess that our application is not able to connect to MySQL. We use &#8220;localhost&#8221; as MySQL server address which in case of the container refers to container itself. We need to find a way to connect to MySQL on the host which is our local computer running Docker desktop. Another quick Google search for &#8220;docker connect to host from container&#8221; points us to <strong>host.docker.internal</strong> which we need to use instead of <strong>localhost</strong> while connecting to MySQL. </p>
<p>We proceed to PHPRunner, create a new Server Database Connection on the &#8216;Output Directory&#8217; screen and change the address of MySQL server as follows:</p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
$host = 'host.docker.internal';
$user = 'root';
$pwd = '';
$port = 3306;
$sys_dbname = 'cars';</textarea></pre>
</div>
<h3>And one more try</h3>
<p>1. Stop the container<br />
2. Build PHPRunner project ( make sure that new connection is selected )<br />
3. Copy all files from PHPRunner&#8217;s output folder to C:\Projects\Docker\src<br />
4. Execute <strong>docker compose up &#8211;build</strong> command one more time.<br />
5. Refresh browser&#8217;s window</p>
<p>It runs!</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_runs.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_runs-600x314.png" alt="" width="600" height="314" class="alignnone size-medium wp-image-3184" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_runs-600x314.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_runs-1024x536.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_runs-768x402.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/05/docker_runs.png 1394w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>Additional notes. You can make your container connect to any database. It can be a database that is a part of the same container ( though it is not recommended ), or a database running in another container or any external database server. All you need to do is to point the database connection to your database of choice. </p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/05/07/running-phprunner-application-as-a-docker-container/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>PHPDesktop update</title>
		<link>https://xlinesoft.com/blog/2025/01/09/phpdesktop-update/</link>
					<comments>https://xlinesoft.com/blog/2025/01/09/phpdesktop-update/#comments</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 10 Jan 2025 02:32:57 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3138</guid>

					<description><![CDATA[As you might know, we teamed up with PHPDesktop developer to bring it to the latest version of Chromium and PHP. Now we have PHPDesktop 130.1 at our disposal that runs PHP 8.3. It will be a part of PHPRunner 11 soon and meanwhile I will show you how to upgrade PHPDesktop in your PHPRunner 10.91 installation. 1. Download updated PHPDesktop. Unzip it to C:\Program Files\PHPRunner 10.91\DesktopApp keeping directory structure and overwriting existing files. 2. Download updated phprunnerapp-setup.iss file and replace the existing one in...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2025/01/09/phpdesktop-update/" class="more-link">Continue Reading <span class="screen-reader-text">"PHPDesktop update"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>As you might know, we teamed up with PHPDesktop developer to bring it to the latest version of Chromium and PHP. Now we have PHPDesktop 130.1 at our disposal that runs PHP 8.3. It will be a part of PHPRunner 11 soon and meanwhile I will show you how to upgrade PHPDesktop in your PHPRunner 10.91 installation.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2025/01/scr.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2025/01/scr-600x449.png" alt="" width="600" height="449" class="alignnone size-medium wp-image-3140" srcset="https://xlinesoft.com/blog/wp-content/uploads/2025/01/scr-600x449.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2025/01/scr-768x575.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2025/01/scr.png 785w" sizes="(max-width: 600px) 100vw, 600px" /></a></p>
<p>1. Download updated <a href="https://drive.google.com/file/d/1DomS70RuUU-DxpZSRL2orNQmmvRgUlTl/view?usp=sharing">PHPDesktop</a>. Unzip it to C:\Program Files\PHPRunner 10.91\DesktopApp keeping directory structure and overwriting existing files.</p>
<p>2. Download updated <a href='https://xlinesoft.com/blog/wp-content/uploads/2025/01/phprunnerapp-setup.iss'>phprunnerapp-setup.iss</a> file and replace the existing one in C:\Program Files\PHPRunner 10.91\DesktopApp folder. </p>
<p>This is it! Now you can use PHPRunner 10.91 to build desktop apps that support PHP 8.3.  </p>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2025/01/09/phpdesktop-update/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Displaying website visitors on the map</title>
		<link>https://xlinesoft.com/blog/2024/01/03/displaying-website-visitors-on-the-map/</link>
					<comments>https://xlinesoft.com/blog/2024/01/03/displaying-website-visitors-on-the-map/#respond</comments>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Thu, 04 Jan 2024 01:43:29 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://xlinesoft.com/blog/?p=3074</guid>

					<description><![CDATA[Our goal is to display website visitors on the map, similar to the screenshot below. We will convert their IP address to lat/lng coordinates and display those markers on OpenStreetMap map. To perform the conversion of IP addresses to lat/lng pairs we are going to use the geolocation data from ip2location.com. We will display users that were active in the last ten minutes. If the user had some activity in the last 60 seconds, their dot will be pulsing. There is also a YouTube video...<span class="clearfix clearfix-post"></span><a href="https://xlinesoft.com/blog/2024/01/03/displaying-website-visitors-on-the-map/" class="more-link">Continue Reading <span class="screen-reader-text">"Displaying website visitors on the map"</span> <span class="meta-nav">&#8594;</span></a>]]></description>
										<content:encoded><![CDATA[<p>Our goal is to display website visitors on the map, similar to the screenshot below.</p>
<p>We will convert their IP address to lat/lng coordinates and display those markers on OpenStreetMap map. To perform the conversion of IP addresses to lat/lng pairs we are going to use the geolocation data from <a href="https://ip2location.com">ip2location.com</a>.</p>
<p>We will display users that were active in the last ten minutes. If the user had some activity in the last 60 seconds, their dot will be pulsing.</p>
<p><a href="https://xlinesoft.com/blog/wp-content/uploads/2024/01/interactive_map.png"><img loading="lazy" decoding="async" src="https://xlinesoft.com/blog/wp-content/uploads/2024/01/interactive_map-600x288.png" alt="" width="600" height="288" class="alignnone size-medium wp-image-3075" srcset="https://xlinesoft.com/blog/wp-content/uploads/2024/01/interactive_map-600x288.png 600w, https://xlinesoft.com/blog/wp-content/uploads/2024/01/interactive_map-1024x491.png 1024w, https://xlinesoft.com/blog/wp-content/uploads/2024/01/interactive_map-768x368.png 768w, https://xlinesoft.com/blog/wp-content/uploads/2024/01/interactive_map.png 1471w" sizes="(max-width: 600px) 100vw, 600px" /></a><br />
<span id="more-3074"></span></p>
<p>There is also a <a href="https://youtu.be/Xb5vmbVPe_g">YouTube video</a> that provides more details of this project.</p>
<p>1. Database structure. </p>
<p>We are going to need two tables, &#8216;users&#8217; and &#8216;ip2location&#8217;. The following is the script for MySQL database. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="sql" name="mshighlighter" >
CREATE TABLE `ip2location`(`id` int NOT NULL AUTO_INCREMENT, `ip_start` decimal(20,6) NULL, `ip_end` decimal(20,6) NULL, `STATE` varchar(50) NULL, `COUNTRY` varchar(50) NULL, `REGION` varchar(50) NULL, `CITY` varchar(100) NULL, `LATITUDE` double NULL, `LONGITUDE` double NULL, PRIMARY KEY (`id`))CHARACTER SET utf8;
CREATE TABLE `users`(`id` int NOT NULL AUTO_INCREMENT, `ip` varchar(50) NOT NULL DEFAULT '0', `lat` double NOT NULL DEFAULT 0, `lng` double NOT NULL DEFAULT 0, `last_activity` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`))CHARACTER SET utf8;</textarea></pre>
</div>
<p>Please note that this SQL script only creates &#8216;ip2location&#8217; but doesn&#8217;t come with the data. The data set itself is about 300Mb and you can download it for free at https://lite.ip2location.com/database/ip-country.</p>
<p>2. Insert a code snippet into a page where you want to display the map. In our case it would be the menu page. The code itself is very simple, it merely outputs the div with &#8216;map&#8217; ID. </p>
<p><strong>PHP code:</strong></p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >
echo "<div id='map' style=''></div>";</textarea></pre>
</div>
<p>3. AfterApplicationInitialized event</p>
<p><strong>PHP code:</strong></p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="php" name="mshighlighter" >

// convert IP address to a decimal number in order to perform a database search
function ip_to_decimal($ip_address) {
    $parts = explode('.', $ip_address);
    $decimal_ip = 0;
    foreach ($parts as $part) {
        $decimal_ip = $decimal_ip * 256 + (int) $part;
    }
    return $decimal_ip;
}

function saveCurrentUserData(){
		$ip = $_SERVER["REMOTE_ADDR"];
		if( empty($ip) )
			return false;
		$userRs = DB::Select("users",array("ip" => $ip));
		$user = $userRs->fetchAssoc();
		if( $user ){
			DB::Update("users", array("last_activity" => date("Y-m-d H:i:s")) ,array("ip" => $ip));
		}
		else{
			$decimalip = ip_to_decimal($ip);
			$coordsRs = DB::Query("select * from ip2location where ".$decimalip." BETWEEN ip_start and ip_end");

			$coords = $coordsRs->fetchAssoc();
			if( $coords ){
				$userData = array("ip" => $ip,
                                            "lat" => $coords["LATITUDE"], 
                                            "lng" =>  $coords["LONGITUDE"],
                                             "last_activity" => date("Y-m-d H:i:s"));
				DB::Insert("users",$userData);
			}
		}
}
if( postvalue("getActiveUsers") ){
	$interval = 10;  
        // we only display on the map users that accessed any page in the last ten minutes

	saveCurrentUserData();
	$dateCondition = date("Y-m-d H:i:s",time() - ($interval*60));
	$userRs = DB::Query("select * from users where last_activity > '".$dateCondition."'");
	$latLng = array();

        $userData = $userRs->fetchAssoc();
	while( $userData ){
		$coordsInfo = array("id" => $userData['id'], 
                              "lat" => $userData['lat'],
                              "lng" => $userData['lng'],
                               "active" => false);

		if( ( time() - strtotime($userData["last_activity"]) ) <=60 ) {
		    $coordsInfo['active'] = true;
                }
		$latLng[] = $coordsInfo;
                $userData = $userRs->fetchAssoc();
	}
	echo my_json_encode($latLng);
	exit();
}</textarea></pre>
</div>
<p>4. custom_function.js </p>
<p>The following Javascript code goes to Event Editor -> custom_function.js section. </p>
<pre>
$(document).ready(function() {

    $("#map").width($(".r-fluid").width());
    var height = $(window).height() - $("#map").offset().top - 30;
    $("#map").height(height);

    window.mapObj = new OpenLayers.Map("map", {
        controls: [
            new OpenLayers.Control.PanZoomBar(),

            new OpenLayers.Control.Navigation()
        ],
    });

    var layer = new OpenLayers.Layer.OSM();

    mapObj.addLayer(layer);

    window.markersList = new OpenLayers.Layer.Markers("Markers");
    mapObj.addLayer(markersList);
    mapObj.zoomToMaxExtent();

    updateMarkers();
    setInterval(updateMarkers, 5000);

    function updateMarkers() {

        $.post("", {
            getActiveUsers: true
        }, function(response) {

            var coordsArr = JSON.parse(response),
                activeIds = coordsArr.map(function(coords) {
                    return coords.id;
                }),
                allIds = markersList.markers.map(function(marker) {
                    return marker.id;
                });


            $.each(coordsArr, function(i, latLon) {

                if (!allIds.includes(latLon.id)) {
                    addMarker(latLon.id, latLon.lat, latLon.lng, latLon.active);
                } else {
                    var curMarker = markersList.markers.find(function(marker) {
                        return marker.id == latLon.id
                    });
                    if (curMarker.active != latLon.active) {
                        $(curMarker.icon.imageDiv).toggleClass("active", latLon.active);
                    }
                }
            });
            allIds = markersList.markers.map(function(marker) {
                return marker.id;
            });

            for (var i = 0; i < allIds.length; i++) {

                if (allIds[i] != undefined &#038;&#038; !activeIds.includes(allIds[i])) {

                    var markerToRemove = markersList.markers.find(function(marker) {
                        return marker.id == allIds[i]
                    });
                    markersList.removeMarker(markerToRemove);
                }

            }

            function addMarker(id, lat, lng, active) {

                var lonLat = new OpenLayers.LonLat(lng, lat)
                    .transform(
                        new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984
                        mapObj.getProjectionObject() // to Spherical Mercator Projection
                    );
                var icon = new OpenLayers.Icon("", new OpenLayers.Size(15, 15));
                var marker = new OpenLayers.Marker(lonLat, icon);
                if (active) {
                    $(marker.icon.imageDiv).addClass("active");
                }

                marker.id = id;
                marker.active = active;
                markersList.addMarker(marker);

                return marker;
            }
        });


    }
    updateMarkers();


});
</pre>
<p>5. CSS code ( Style Editor -> Modify CSS )</p>
<p>We use this CSS code to customize and prettify the default look of OSM map. </p>
<div class="my-syntax-highlighter">
<pre><textarea id="mshighlighter" class="mshighlighter" language="css" name="mshighlighter" >
.olTileImage {
    filter: brightness(48%) contrast(256%);
}

[id^="OL_Icon"] .olAlphaImg {
    background: white;
    cursor: pointer;
    border-radius: 100%;
  }
  [id^="OL_Icon"].active .olAlphaImg {
    animation: pulse 2s infinite;
    box-shadow: 10px 10px 10px rgba(255,255,255, 0.7);
    width:20px !important;
    height: 20px !important;
  }

  @-webkit-keyframes pulse {
    0% {
      -webkit-box-shadow: 10px 10px 10px rgba(255,255,255, 0.7);
    }
    70% {
        -webkit-box-shadow: 0 0 0 10px rgba(255,255,255, 0);
    }
    100% {
        -webkit-box-shadow: 0 0 0 0 rgba(255,255,255, 0);
    }
  }
  @keyframes pulse {
    0% {
      -moz-box-shadow: 0 0 0 0 rgba(255,255,255, 0.7);
      box-shadow: 0 0 0 0 rgba(255,255,255, 0.7);
    }
    70% {
        -moz-box-shadow: 0 0 0 10px rgba(255,255,255, 0);
        box-shadow: 0 0 0 10px rgba(255,255,255, 0);
    }
    100% {
        -moz-box-shadow: 0 0 0 0 rgba(255,255,255, 0);
        box-shadow: 0 0 0 0 rgba(255,255,255, 0);
    }
  }</textarea></pre>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://xlinesoft.com/blog/2024/01/03/displaying-website-visitors-on-the-map/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
