<?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>Donato Glodenio puslapiai</title>
	<atom:link href="https://dg.lapas.info/feed/" rel="self" type="application/rss+xml" />
	<link>https://dg.lapas.info</link>
	<description>„deja, Lietuva yra įvairi“</description>
	<lastBuildDate>Thu, 26 Feb 2026 17:27:19 +0000</lastBuildDate>
	<language>lt-LT</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://dg.lapas.info/wp-content/uploads/2023/10/dg-lapas-icon-favicon.png</url>
	<title>Donato Glodenio puslapiai</title>
	<link>https://dg.lapas.info</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Nuorodų trumpintuvė</title>
		<link>https://dg.lapas.info/irasas/nuorodu-trumpintuve/</link>
					<comments>https://dg.lapas.info/irasas/nuorodu-trumpintuve/#respond</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Thu, 26 Feb 2026 17:17:51 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[programavimas]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4896</guid>

					<description><![CDATA[Be reklamų. Be bandymo įpiršti mokamas paslaugas. Nežinia, kiek gyvens, bet – greičiausiai bent keletą metų 😉 Naudokite į sveikatą. https://s.lapas.info Sukurta naudojant elementarų PHP, JS, na ir Bootstrap5, kad bjauriai neatrodytų (nors pernelyg daug į stilių neinvestavau).]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Be reklamų. Be bandymo įpiršti mokamas paslaugas. Nežinia, kiek gyvens, bet – greičiausiai bent keletą metų <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Naudokite į sveikatą.</p>



<p class="wp-block-paragraph"><a href="https://s.lapas.info">https://s.lapas.info</a> </p>



<p class="wp-block-paragraph">Sukurta naudojant elementarų PHP, JS, na ir Bootstrap5, kad bjauriai neatrodytų (nors pernelyg daug į stilių neinvestavau). </p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="577" height="717" src="https://dg.lapas.info/wp-content/uploads/2026/02/nuorodu-trumpintuve.png" alt="" class="wp-image-4897" srcset="https://dg.lapas.info/wp-content/uploads/2026/02/nuorodu-trumpintuve.png 577w, https://dg.lapas.info/wp-content/uploads/2026/02/nuorodu-trumpintuve-241x300.png 241w" sizes="(max-width: 577px) 100vw, 577px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/nuorodu-trumpintuve/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Daugiskaitos formos Codeigniter 4 lokalizavime</title>
		<link>https://dg.lapas.info/irasas/daugiskaitos-formos-codeigniter-4-lokalizavime/</link>
					<comments>https://dg.lapas.info/irasas/daugiskaitos-formos-codeigniter-4-lokalizavime/#respond</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Thu, 26 Feb 2026 17:14:27 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[vertimas]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4892</guid>

					<description><![CDATA[Čia tiesiog pasidedu ateičiai. Kartais prireikia rašyti eilutes, kuriose gali būti įterpiamas bet koks skaičius. Tad reikia, kad programa atsižvelgtų į tai ir pakeistų žodžių daugiskaitos formą priklausomai nuo skaičiaus: Aš neturiu obuolių; Aš turiu vieną obuolį; Aš turiu 3 obuolius; Aš turiu 11 obuolių; Aš turiu 21 obuolį; Aš turiu 20 obuolių; Tokios problemos [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Čia tiesiog pasidedu ateičiai. Kartais prireikia rašyti eilutes, kuriose gali būti įterpiamas bet koks skaičius. Tad reikia, kad programa atsižvelgtų į tai ir pakeistų žodžių daugiskaitos formą priklausomai nuo skaičiaus: </p>



<p class="wp-block-paragraph">Aš neturiu obuolių; <br>Aš turiu vieną obuolį; <br>Aš turiu 3 obuolius; <br>Aš turiu 11 obuolių; <br>Aš turiu 21 obuolį; <br>Aš turiu 20 obuolių; </p>


<a class="wp-block-read-more" href="https://dg.lapas.info/irasas/daugiskaitos-formos-codeigniter-4-lokalizavime/" target="_self">Skaityti daugiau<span class="screen-reader-text">: Daugiskaitos formos Codeigniter 4 lokalizavime</span></a>


<p class="wp-block-paragraph">Tokios problemos seniai išspręstos daugelyje programavimo aplinkų, tačiau tai, kaip tai padaryta Codeigniter&#8217;yje, nebuvo aiškiai dokumentuota.</p>



<p class="wp-block-paragraph">Taigi, tokiu atveju, LT kalbos faile vietoje įprastos eilutės įterpiama: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">    // 'apples' => 'Aš turiu {1} obuolių/-us/-į', // čia nekažkas
    'apples' => 'Aš {apples, plural, // čia gerai!
        =0 {neturiu obuolių}
        =1 {turiu vieną obuolį}
        one {turiu # obuolį}
        few {turiu # obuolius}
        other {turiu # obuolių}
    }',</pre>



<p class="wp-block-paragraph">O View faile taip: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;?= lang('Fruit.apples', ['apples' => $appleNo]) ?></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/daugiskaitos-formos-codeigniter-4-lokalizavime/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Codeigniter4, VSCode, Xdebug</title>
		<link>https://dg.lapas.info/irasas/codeigniter4-vscode-xdebug/</link>
					<comments>https://dg.lapas.info/irasas/codeigniter4-vscode-xdebug/#respond</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Mon, 04 Aug 2025 13:00:52 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[Codeigniter4]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[vscode]]></category>
		<category><![CDATA[xdebug]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4880</guid>

					<description><![CDATA[Below is a code block for myself the next time I need a launch.json file to enable debugging within VSCode using a Codeigniter4 project. Hope someone else also it useful. It is extracted from this Youtube video:]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Below is a code block for myself the next time I need a launch.json file to enable debugging within VSCode using a Codeigniter4 project. Hope someone else also it useful.</p>



<p class="wp-block-paragraph">It is extracted from this Youtube video: </p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Run and debug a CodeIgniter project in Visual Studio Code" width="500" height="281" src="https://www.youtube.com/embed/-m6QLfYKJ5k?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<span id="more-4880"></span>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch built-in web server",
            "type": "php",
            "request": "launch",
            "runtimeArgs": [
                "spark",
                "serve",
                "-dxdebug.mode=debug",
                "-dxdebug.start_with_request=yes",
                "-S",
                "localhost:8080"
            ],
            "env": {
                "XDEBUG_MODE": "debug",
                "XDEBUG_SESSION": "permsExample",
            },
            //"externalConsole": true,
            "program": "",
            "cwd": "${workspaceRoot}",
            "port": 9003,
            "serverReadyAction": {
                "pattern": "Development server \\(http://localhost:[0-9]+)\\) started",
                "uriFormat": "http://localhost:%s",
                "action": "openExternally"
            }
        }
    ]
}</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/codeigniter4-vscode-xdebug/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Easy integration of Queue management into an existing Codeigniter 4 project</title>
		<link>https://dg.lapas.info/irasas/easy-integration-of-queue-management-into-an-existing-codeigniter-4-project/</link>
					<comments>https://dg.lapas.info/irasas/easy-integration-of-queue-management-into-an-existing-codeigniter-4-project/#respond</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Wed, 23 Apr 2025 20:22:01 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[Codeigniter4]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4871</guid>

					<description><![CDATA[While working on a university information system we were encountering a phenomenon when some emails sent to users would fail to send. The university used Microsoft infrastructure for sending emails via SMTP, and well, for some esoteric reason one in 100 or so were failing to reach the recepient. Of course the failed emails needed [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">While working on a university information system we were encountering a phenomenon when some emails sent to users would fail to send. The university used Microsoft infrastructure for sending emails via SMTP, and well, for some esoteric reason one in 100 or so were failing to reach the recepient. Of course the failed emails needed to be resent, and Queue package seemed like the obvious choice to handle that.</p>



<p class="wp-block-paragraph">Here is how we achieved the desired change basically without refactoring of the codebase.</p>



<span id="more-4871"></span>



<p class="wp-block-paragraph">First of all, we simply followed the Queue documentation to the letter in installing the package in our Codeigniter app and creating the Email queue files. You can read about that step <a href="https://queue.codeigniter.com/basic-usage/">here</a>. </p>



<p class="wp-block-paragraph">Now we had to decide where to tweak the code to send the failed emails to queue for sending retry later. There were a lot of code blocks in our code that looked like this: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$email = service('email');
$email->setFrom('noreply@lsu.lt', 'LSU informacinė sistema');
$email->setTo($user->email);
$email->setSubject($messageSubject);
$email->setMessage($messageBody);
$success = $email->send(false);</pre>



<p class="wp-block-paragraph">So the obvious choice at first seemed to add a code block that would check if email was successfully sent and if not &#8211; send it&#8217;s data to the queue: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if (! $success) {
    $emailData = [
        'recipients' => $user->email,
        'subject'    => $messageSubject,
        'body'       => $messageBody,
        'fromEmail'  => 'noreply@lsu.lt',
        'fromName'   => 'LSU informacinė sistema',
    ];
    service('queue')->push('emails', 'email', $emailData);
}</pre>



<p class="wp-block-paragraph">It would work fine, but this code had to be added in many different places.</p>



<p class="wp-block-paragraph">So we decided to extend the Codeigniter 4 Email library instead. We added file <em>app/Libraries/Email.php</em>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;?php

namespace App\Libraries;

use CodeIgniter\Email\Email as BaseEmail;
use ErrorException;

class Email extends BaseEmail {
    /**
     * Store the raw subject.
     *
     * @var string
     */
    protected $rawSubject = '';

    /**
     * Raw debug messages
     *
     * @var list&lt;string>
     */
    private array $debugMessageRaw = [];

    /**
     * Indicates whether the email is being executed by the queue.
     *
     * @var bool
     */
    protected $isQueueExecution = false;

    /**
     * Indicates whether the email is being moved to the queue instead of sending.
     *
     * @var bool
     */
    protected $toQueue = false;

    /**
     * Spool mail to the mail server
     *
     * @return bool
     */
    protected function spoolEmail() {
        $this->unwrapSpecials();
        $protocol = $this->getProtocol();
        $method = 'sendWith' . ucfirst($protocol);

        if($this->toQueue){
            $this->addToQueue();
            return true;
        }

        try {
            $success = $this->{$method}();
        } catch (ErrorException $e) {
            $success = false;
            log_message('error', 'Email: ' . $method . ' throwed ' . $e);
        }

        if (! $success) {
            $message = lang('Email.sendFailure' . ($protocol === 'mail' ? 'PHPMail' : ucfirst($protocol)));

            log_message('error', 'Email: ' . $message);
            log_message('error', $this->printDebuggerRaw());

            $this->setErrorMessage($message);

            // Avoid recursive queueing
            if (! $this->isQueueExecution) {
                $this->addToQueue();
            }

            return false;
        }

        $this->setErrorMessage(lang('Email.sent', [$protocol]));

        return true;
    }

    /**
     * Returns raw debug messages
     * copy of the original method, which is set to private unfortunately
     */
    private function printDebuggerRaw(): string {
        return implode("\n", $this->debugMessageRaw);
    }

    /**
     * Set the queue execution flag.
     */
    public function setQueueExecution(bool $isQueue): void {
        $this->isQueueExecution = $isQueue;
    }

    /**
     * Set toQueue flag.
     */
    public function setToQueue(bool $toQueue): void {
        $this->toQueue = $toQueue;
    }

    /**
     * Add the failed email to the queue.
     */
    protected function addToQueue(): void {
        $queueData = [
            'recipients' => $this->recipients,
            'cc' => $this->CCArray,
            'bcc' => $this->BCCArray,
            // $this->subject results in empty string, had to make an owerride
            'subject' => $this->getRawSubject(),
            'body' => $this->body,
            'headers' => $this->headers,
            'fromEmail' => $this->fromEmail,
            'fromName' => $this->fromName,
            // 'attachments' => $this->attachments, // needs more work
        ];

        // Use queue library to add the data
        service('queue')->push('emails', 'email', $queueData);
    }

    /**
     * Override setSubject to store the raw subject.
     *
     * @param string $subject
     *
     * @return $this
     */
    public function setSubject($subject) {
        $this->rawSubject = $subject; // Store the raw subject

        return parent::setSubject($subject); // Call the parent method
    }

    /**
     * Get the raw subject.
     */
    public function getRawSubject(): string {
        return $this->rawSubject;
    }
}
</pre>



<p class="wp-block-paragraph">The main change here is this: the extended class contains an overridden method spoolEmail(), which is responsible for sending emails; here, when an email fails to send, it automatically executes a new method addToQueue(),which sends the data of email to the queue for retrying later. (Some other changes needed to be added as well, they are explained in the comments of the code).</p>



<p class="wp-block-paragraph">After this, we also added this code: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public static function email($config = null, bool $getShared = true)
{
    if ($getShared) {
        return static::getSharedInstance('email', $config);
    }

    if (empty($config) || (! is_array($config) &amp;&amp; ! $config instanceof EmailConfig)) {
        $config = config(EmailConfig::class);
    }

    return new Email($config);
}</pre>



<p class="wp-block-paragraph">to <em>app/Config/Services.php</em> file, so that the Codeigniter&#8217;s Email service would use our extended class in place of the default Codeigniter&#8217;s Email class.</p>



<p class="wp-block-paragraph">And that was it. Now, without any other changes to the old code, once an email fails to send, it is transferred to the emails queue and retries are made to send it later. </p>



<p class="wp-block-paragraph">As you can see, if email is sent from the queue and fails, it is NOT readded to the queue, the code block</p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if (! $this->isQueueExecution) {
    $this->addToQueue();
}</pre>



<p class="wp-block-paragraph">takes care of that. </p>



<p class="wp-block-paragraph">And if we have a cycle where a lot of emails need to be sent and we do not want the user to wait for those emails to send, we can call the method setToQueue() with parameter &#8216;true&#8217; before the $email-&gt;send(): </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$email->setToQueue(true);
$email->send(false);</pre>



<p class="wp-block-paragraph">And, instead of sending the email immediately, it will be transferred to the queue to be dealt with later. And the user will be returned the page faster (as database operations are much faster than sending emails). </p>



<p class="wp-block-paragraph">Here is how, with very little change in the code, we solved the problem of failed emails; and added a lot of scalability to our app. </p>



<p class="has-text-align-right wp-block-paragraph"><em>Image credit goes to ChatGPT</em></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/easy-integration-of-queue-management-into-an-existing-codeigniter-4-project/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Started using Packagist.org</title>
		<link>https://dg.lapas.info/irasas/started-using-packagist-org/</link>
					<comments>https://dg.lapas.info/irasas/started-using-packagist-org/#comments</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Fri, 10 Jan 2025 11:46:55 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[Codeigniter4]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4857</guid>

					<description><![CDATA[Last week I finally braced for a reform – imported two of my PHP packages into Packagist.org – a public repository for PHP packages. And I was surprised at how easy it was. All I had to do was to register at the packagist.org, paste the github.com link to the package, and press a few [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Last week I finally braced for a reform – imported two of my PHP packages into Packagist.org – a public repository for PHP packages. And I was surprised at how easy it was. All I had to do was to register at the packagist.org, paste the github.com link to the package, and press a few buttons.</p>



<p class="wp-block-paragraph">So here they are, my two packages: <a href="https://packagist.org/users/dgvirtual/">https://packagist.org/users/dgvirtual/</a></p>



<span id="more-4857"></span>



<p class="wp-block-paragraph">I have maintained a few packages installable via PHP&#8217;s Composer for a while, but only as private packages, so I had to add additional instructions on how to install them via composer (setting up a private repository for the package and stuff). </p>



<p class="wp-block-paragraph">But now my packages can be installed with a one-liner. </p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/started-using-packagist-org/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Republishing „Legislation on Religion“ paper</title>
		<link>https://dg.lapas.info/irasas/republishing-legislation-on-religion-paper/</link>
					<comments>https://dg.lapas.info/irasas/republishing-legislation-on-religion-paper/#comments</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Sat, 21 Dec 2024 14:05:47 +0000</pubDate>
				<category><![CDATA[religijų pasaulis]]></category>
		<category><![CDATA[religija]]></category>
		<category><![CDATA[religijos laisvė]]></category>
		<category><![CDATA[sektos]]></category>
		<category><![CDATA[teisė]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4823</guid>

					<description><![CDATA[I have noticed my most quoted work on law and religion in Lithuania – Legislation on Religion and the Challenge of Pluralism in Lithuania &#8211; has gone missing from all online sources where it was previously published (most notably Cesnur.org and Religija.lt). So I am republishing it here. It is by far the best (though [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I have noticed my most quoted work on law and religion in Lithuania – Legislation on Religion and the Challenge of Pluralism in Lithuania &#8211; has gone missing from all online sources where it was previously published (most notably Cesnur.org and Religija.lt). So I am <a href="https://dg.lapas.info/religija/legislation-on-religion-and-the-challenge-of-pluralism-in-lithuania/">republishing it here</a>. It is by far the best (though not the most scholarly) piece I have ever written, it should be online. </p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/republishing-legislation-on-religion-paper/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Beware: QRFY.com, QR code snare</title>
		<link>https://dg.lapas.info/irasas/beware-qrfy-com-qr-code-snare/</link>
					<comments>https://dg.lapas.info/irasas/beware-qrfy-com-qr-code-snare/#respond</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Wed, 06 Nov 2024 09:37:04 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4770</guid>

					<description><![CDATA[Spanish company QR Code Generator PRO S.L (hereinafter, the &#8222;QRFY&#8221;) practice on getting paid clients on QR code generation services is manipulative and violate informed consumer consent principle. In this post I will describe how you can get in trouble by generating the QR codes on their website. First let&#8217;s define the terms: QR code [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Spanish company <strong>QR Code Generator PRO S.L (hereinafter, the &#8222;QRFY&#8221;)</strong> practice on getting paid clients on QR code generation services is manipulative and violate informed consumer consent principle.</p>



<p class="wp-block-paragraph">In this post I will describe how you can get in trouble by generating the QR codes on their website.</p>



<p class="wp-block-paragraph">First let&#8217;s define the terms: <strong>QR code</strong> and<strong> &#8222;dynamic&#8221; link</strong>. </p>



<span id="more-4770"></span>



<p class="wp-block-paragraph"><em>A <strong>QR code </strong>(Quick Response code) is a type of two-dimensional barcode that stores information as a pattern of black and white squares, which can be quickly scanned by a device to access data, such as URLs, text, or contact details.</em> It is easy to generate QR codes using free tools, even within browsers (Edge and Chrome have it built in, just right-click on the page).</p>



<p class="wp-block-paragraph">Often QR codes are used with <em>shortened URL</em>&#8216;s, that is, short links that, on trying to access it, redirect the browser to another (longer) link. Shorter links allow QR codes to be smaller and quicker to read for our devices, and in addition, some services allow you to make such <strong>links <em>dynamic</em></strong>, that is, you can change the page URL the short link targets without changing the short URL itself. </p>



<p class="wp-block-paragraph">Cool, ha?</p>



<p class="wp-block-paragraph">Those two services (URL shortening and QR generation) are often integrated and, if you only need a few such links/QR codes, they are available for free. E.g., <a href="http://bitly.com" data-type="link" data-id="bitly.com">bitly.com</a> allows you to create two 10 short links and 2 QR codes for such links per month for free. That is enough for most casual users. </p>



<p class="wp-block-paragraph">But you have to be careful with some providers. If the provider allows you to generate a dynamic link and later demands money for using it, you may be in trouble. One such manipulative provider is QRFY.com.</p>



<p class="wp-block-paragraph">Suppose you need a single QR code for an event, like wedding or conference. You intend to put it on the participants cards and conference/event materials, so people can easily find updates and news. You google for QR code generator online and find one, looking very professional, at <a href="https://qrfy.com" rel="nofollow">https://qrfy.com</a></p>



<p class="wp-block-paragraph">So, you enter the link, website generates the QR code:</p>



<ol class="wp-block-list">
<li></li>
</ol>



<figure class="wp-block-image size-full"><img decoding="async" width="759" height="480" src="https://dg.lapas.info/wp-content/uploads/2024/11/image001.png" alt="" class="wp-image-4771" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image001.png 759w, https://dg.lapas.info/wp-content/uploads/2024/11/image001-300x190.png 300w" sizes="(max-width: 759px) 100vw, 759px" /></figure>



<p class="wp-block-paragraph">If you scan the code, <strong>you will see</strong> <strong>it resolves to Website URL you have entered, so, great!</strong> So, you press download button&#8230;</p>



<p class="wp-block-paragraph">After pushing „Download QR“  you find that there is no direct download, registration is required:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="761" height="481" src="https://dg.lapas.info/wp-content/uploads/2024/11/image003.png" alt="" class="wp-image-4773" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image003.png 761w, https://dg.lapas.info/wp-content/uploads/2024/11/image003-300x190.png 300w" sizes="auto, (max-width: 761px) 100vw, 761px" /></figure>



<p class="wp-block-paragraph">And if you go back and try to download it via the browser right-click action „Save image“, you find it is also disabled. You can actually do a screenshot and be done with it. But that is a workaround that may not come to your mind at once.  OK, so lets register, that could not hurt, right?</p>



<p class="wp-block-paragraph">So you register successfully:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="762" height="482" src="https://dg.lapas.info/wp-content/uploads/2024/11/image005.png" alt="" class="wp-image-4774" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image005.png 762w, https://dg.lapas.info/wp-content/uploads/2024/11/image005-300x190.png 300w" sizes="auto, (max-width: 762px) 100vw, 762px" /></figure>



<p class="wp-block-paragraph">On pushing <em>Accept </em>(what are you accepting, exactly?), they suggest downloading the QR code yoy previously generated, some nice format choices there!</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="761" height="481" src="https://dg.lapas.info/wp-content/uploads/2024/11/image007.png" alt="" class="wp-image-4775" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image007.png 761w, https://dg.lapas.info/wp-content/uploads/2024/11/image007-300x190.png 300w" sizes="auto, (max-width: 761px) 100vw, 761px" /></figure>



<p class="wp-block-paragraph">So you push „Download“ and here is the JPEG image that you get:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="236" height="236" src="https://dg.lapas.info/wp-content/uploads/2024/11/image009.jpg" alt="" class="wp-image-4776" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image009.jpg 236w, https://dg.lapas.info/wp-content/uploads/2024/11/image009-150x150.jpg 150w" sizes="auto, (max-width: 236px) 100vw, 236px" /></figure>



<p class="wp-block-paragraph">It<strong><em> might look</em> </strong>about the same, but <strong>it is not </strong>the same as the initial QR code. Here is a comparison, and what it resolves to. Inestead of a QR code that resolves directly to your website/page you get a code that resolves to THEIR website&#8217;s redirect link to your website, which is in <strong>their </strong>control, not <strong>yours</strong>:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Initial QR code: <br><img decoding="async" style="width: 200px;" src="https://dg.lapas.info/wp-content/uploads/2024/11/image011.png" alt=""><br>Resolves to:<br><strong>https://lapas.info</strong></td><td>„Same“ QR code after registration: <br><img decoding="async" style="width: 200px;" src="https://dg.lapas.info/wp-content/uploads/2024/11/image009.jpg" alt=""><br>Resolves to: <br><strong>https://qrfy.io/8JxBhAMXnS</strong></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Notice that <strong>NOWHERE</strong> on the way to download of the QR code you were:</p>



<ul class="wp-block-list">
<li>a) <em>introduced/required to opt-in </em>to the site service conditions,</li>



<li>b) <em>warned that your QR code is changed</em> into a redirect link controlled by QRFY company, and of course I was not;</li>



<li>c) <em>warned that (surprise surprise!) the link encoded into the QR code will expire in a week</em></li>
</ul>



<p class="wp-block-paragraph">Not only were you not warned explicitly, the website implies that the QR codes will work all the time in the title page, in the <em>Basic Information</em> section:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="797" height="168" src="https://dg.lapas.info/wp-content/uploads/2024/11/image015.png" alt="" class="wp-image-4778" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image015.png 797w, https://dg.lapas.info/wp-content/uploads/2024/11/image015-300x63.png 300w, https://dg.lapas.info/wp-content/uploads/2024/11/image015-768x162.png 768w" sizes="auto, (max-width: 797px) 100vw, 797px" /></figure>



<p class="wp-block-paragraph">So, the idea is that the QR codes generated over the trial period are &#8222;yours forever&#8221; and therefore should work as expected (on scan should lead to your website). But after the trial period you can only create codes on their website if you choose a plan and pay. The reality is that, though the pictures of QR codes cannot be taken away after you download them, they stop doing what they are supposed to do when the trial period expires.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="799" height="111" src="https://dg.lapas.info/wp-content/uploads/2024/11/image017.png" alt="" class="wp-image-4779" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image017.png 799w, https://dg.lapas.info/wp-content/uploads/2024/11/image017-300x42.png 300w, https://dg.lapas.info/wp-content/uploads/2024/11/image017-768x107.png 768w" sizes="auto, (max-width: 799px) 100vw, 799px" /></figure>



<p class="wp-block-paragraph">Again, impression is given that the codes you generate during the trial period will work indefinitely, and only later you will have to pay to generate new ones.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="802" height="134" src="https://dg.lapas.info/wp-content/uploads/2024/11/image019.png" alt="" class="wp-image-4780" srcset="https://dg.lapas.info/wp-content/uploads/2024/11/image019.png 802w, https://dg.lapas.info/wp-content/uploads/2024/11/image019-300x50.png 300w, https://dg.lapas.info/wp-content/uploads/2024/11/image019-768x128.png 768w" sizes="auto, (max-width: 802px) 100vw, 802px" /></figure>



<p class="wp-block-paragraph">So here they say, again, the the QR codes <strong>do not expire</strong>, <strong>UNLESS you create</strong> a &#8222;dynamic code&#8221;. You did not opt for that, but <strong>they replaced covertly</strong> the static code with dynamic. Again, you were not warned that the website intentionally changes the QR codes to dynamic ones, and the passage above says explicitly they do not do that.</p>



<p class="wp-block-paragraph">So, you have created a QR code, there is a promise that it will not expire, and you continue with production of the materials (sending wedding invitations, printing conference materials with the QR code, etc).</p>



<p class="wp-block-paragraph">And then in 7 days the QR code stops working: on scan they open the QRFY website instead of yours, which says the link is disabled &#8222;for some reason&#8221;, and suggests you log in to reenable it.</p>



<p class="wp-block-paragraph">You login to their website and find that to reenable the redirection link, that is encoded in your QR code, you need to choose one of the paid subscription plans, the cheapest of which is 145.16€ for three months, paid at once.</p>



<p class="wp-block-paragraph">The materials, invitations etc that have already been printed and distributed, and if they contain a QR code that does not work, they promise you a public relations disaster. You are time-pressed, so you weight the options and choose the lesser evil, choose that &#8222;cheapest&#8221; plan and pay those 145.16€. Ah, and when choosing that subscription you have to agree to all the long list of conditions, one of which is &#8222;no refund&#8221;.</p>



<p class="wp-block-paragraph">What a fine business model in a EU country Spain! Consumer protection institutions should take not of this one.</p>



<p class="wp-block-paragraph">If you try to find out who they are, the most you can get on their website is their legal address. No people are willing to associate their name with this schema.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/beware-qrfy-com-qr-code-snare/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Patogesnė juridinių asmenų paieška</title>
		<link>https://dg.lapas.info/irasas/patogesne-juridiniu-asmenu-paieska/</link>
					<comments>https://dg.lapas.info/irasas/patogesne-juridiniu-asmenu-paieska/#comments</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Wed, 11 Sep 2024 06:38:04 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[Alpine.js]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[sqlite3]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4764</guid>

					<description><![CDATA[Darbe man tenka naudotis Registrų centro juridinių asmenų duomenų paieška. Kuri yra ganėtinai ribota (galima ieškoti tik pagal pavadinimą ir juridinio asmens kodą), ir dar su kiekviena paieška prašo įvesti Captcha paveikslėlio duomenis. Sucks, vienžo (nors suprantu, kodėl taip padaryta). Tad vasarą pasidariau tokį projektėlį &#8211; patogesnę juridinių asmenų duomenų paiešką. Panaudojau viešai prieinamus juridinių [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Darbe man tenka naudotis <a href="https://www.registrucentras.lt/jar/p/index.php">Registrų centro juridinių asmenų duomenų paieška</a>. Kuri yra ganėtinai ribota (galima ieškoti tik pagal pavadinimą ir juridinio asmens kodą), ir dar su kiekviena paieška prašo įvesti Captcha paveikslėlio duomenis. Sucks, vienžo (nors suprantu, kodėl taip padaryta). </p>



<p class="wp-block-paragraph">Tad vasarą pasidariau tokį projektėlį &#8211; <a href="https://pr.lapas.info/jar/" data-type="link" data-id="https://pr.lapas.info/jar/">patogesnę juridinių asmenų duomenų paiešką</a>. Panaudojau viešai prieinamus juridinių asmenų duomenis (jie atnaujinami kas mėnesį) ir taip pat sukūriau scenarijus, kurie tuos duomenis papildo panaudojant tą pačią Registrų centro duomenų paiešką (mat viešai pateikiamuose duomenyse nėra individualių įmonių ir komanditinių bendrovių pavadinimų). </p>



<p class="wp-block-paragraph">Technologinis sprendimas buvo paprastas: </p>



<ul class="wp-block-list">
<li>SQlite3 duomenų bazė</li>



<li>su PHP kurtas API backend&#8217;as </li>



<li>Alpine.js frontend&#8217;as</li>
</ul>



<p class="wp-block-paragraph">Paiešką galima atlikti pagal įregistravimo/išregistravimo datas, teisinį statusą, teisinę formą, pavadinimą, adresą &#8211; tad galite ir savo poreikius, ir smalsumą patenkinti:</p>



<ul class="wp-block-list">
<li><em>Kiek įmonių įregistruota jūsų name</em>? </li>



<li>Ar name, gatvėje, miestelyje <em>yra religinių bendruomenių</em>? </li>



<li>Ar <em>Jūsų planuojamame įsigyti būste nėra įregistruotų įmonių</em>? </li>



<li>Kiek mažųjų bendrijų yra jūsų miestelyje? O kiek &#8211; Lietuvoje?</li>
</ul>



<p class="wp-block-paragraph">Įprastai sistemas kuriu su Codeigniter 4 PHP karkasu, bet šį kartą nusprendžiau pamėginti apsieiti be karkaso &#8211; tiesiog „vanilla PHP“, ir be jokių išorinių bibliotekų. Nedidelis projektėlis, turėtų būti nesudėtinga, ar ne? Gan greitai paaiškėjo, kad nieko panašaus. Būčiau tikrai daug laiko sutaupęs, jei nebūtų reikėję išradinėti visų tų dviračių pakeliui <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Na bet buvo įdomu šį tą naujo išmokti.</p>



<p class="wp-block-paragraph">Kodėl SQLite3, o ne MySQL? Nes norėjosi išmėginti šios duomenų bazės galimybes su dideliais duomenų kiekiais (juridinių asmenų Lietuvoje, įskaitant išregistruotus, – per pusę milijono). Buvo iššūkių su duomenų rikiavimu (SQLite3 „out of the box“ palaiko tik ASCII rikiavimą, taigi visos „lietuviškos“ raidės atsidurdavo rikiavimo gale). Taip pat ir greičio klausimas yra: su MySQL būtų tikrai greičiau, bent jau kol SQLite3 tinkamai neoptimizuota lietuviškiems simboliams (ko dar iki galo nepavyko padaryti). </p>



<p class="wp-block-paragraph">Taigi, štai projekto svetainė:  <a href="https://pr.lapas.info/jar/">https://pr.lapas.info/jar/</a></p>



<p class="wp-block-paragraph">Be to, sistemos kodą galite rasti Github&#8217;e (<a href="https://github.com/dgvirtual/jar-search-api">https://github.com/dgvirtual/jar-search-api</a>); sistema turi diegimo vediklį, tad galėsite nesunkiai ir ant savo serverio šį projektėlį pasileisti. </p>



<p class="has-text-align-right wp-block-paragraph"><em>Už paveikslėlį ačiū ChatGPT</em> (visgi smagu, kad DI dar nesugeba kurti kažko, kas prilygtų dizainerių darbui)</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/patogesne-juridiniu-asmenu-paieska/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Site Deployment Via ftp(s)</title>
		<link>https://dg.lapas.info/irasas/site-deployment-via-sftps/</link>
					<comments>https://dg.lapas.info/irasas/site-deployment-via-sftps/#respond</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Wed, 14 Aug 2024 15:12:00 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[ftp]]></category>
		<category><![CDATA[lftp]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4746</guid>

					<description><![CDATA[A few of my projects are on shared hosting, where I do not have shell access and cannot deploy using git. Rsync is also not an option. So I used to resort to using Krusader file manager (part of KDE ecosystem), which has a nice option to sync files over SFTP, FTP and other protocols. [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">A few of my projects are on shared hosting, where I do not have shell access and cannot deploy using git. Rsync is also not an option. So I used to resort to using Krusader file manager (part of KDE ecosystem), which has a nice option to sync files over SFTP, FTP and other protocols.</p>



<p class="wp-block-paragraph">And then one of the hosting providers dropped SFTP for FTP over TLS – which KDE ecosystem does not support&#8230; I had to switch to Filezilla (which does not have sync in their free ftp client) or look for other alternatives. </p>



<p class="wp-block-paragraph">So I found <a href="https://github.com/lavv17/lftp">lftp</a>, Unix command-line ftp client, which supports multiple protocols, including FTP over TLS, and, with some help of Gemini and ChatGPT wrote a script to automate deployment. </p>



<span id="more-4746"></span>



<p class="wp-block-paragraph">Here is how it works: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ ./deploy.sh app
Syncing app folder...
Synchronizing directory: '/home/me/Programavimas/myproject/app' to '/myproject/app'
source: Aplankas
Removing old file `Config/Filters.php'               
Transferring file `Config/Filters.php'
Removing old file `Controllers/Auth.php'                               
Transferring file `Controllers/Auth.php'
Removing old file `Controllers/Texts.php'       
Transferring file `Controllers/Texts.php'
Transferring file `Filters/IsLoggedIn.php'                           
Successfully synchronized '/home/me/Programavimas/myapp/app' to '/myapp/app'</pre>



<p class="wp-block-paragraph">Actually, the AI was great with Bash part, but not very helpful with lftp itself, giving me only non-working code, so I had to Google and read in the man page about it.</p>



<p class="wp-block-paragraph">So here I share the working script, maybe it will benefit someone. [<strong>Update</strong>: t<em>he script has been turned into a package installable via composer, see <a href="https://github.com/dgvirtual/deploy-via-ftp">https://github.com/dgvirtual/deploy-via-ftp</a>/ There you will find an updated version of the script</em>].<br><br>First, you will want a config file for FTP credentials, which are better kept outside of your Git repository. Also, to make the script itself more universal, the other options particular to the project should be kept in it. I named it <em>.ftp_config</em> :</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># .ftp_config
USERNAME="myname"
PASSWORD="mySecretPass"
PROTOCOL="ftps"  # or "ftp"
PORT=21
SERVER="ftp.example.com"

# Remote directories
REMOTE_APP="/myproject/app"
REMOTE_VENDOR="/myproject/vendor"
REMOTE_PUBLIC_HTML="/public_html"

# options for excluding files/directories from sync; things that rarely
# change, should not be in production, or that should be updated manually
APP_EXCLUDE_GLOBS=""
VENDOR_EXCLUDE_GLOBS=""
PUBLIC_EXCLUDE_GLOBS="--exclude-glob='uploads/*' --exclude-glob='index.php'"

# Local directories
LOCAL_APP="app"
LOCAL_VENDOR="vendor"
LOCAL_PUBLIC_HTML="public"</pre>



<p class="wp-block-paragraph">Be sure to use SERVER address which which matches the server certificate (as many providers do not care to configure server to use your site sertificate). So, in my case, I could not use the real address of my website, instead using the generic address of the server.</p>



<p class="wp-block-paragraph">And here is my <em>deploy.sh</em> script, which can be run in command line by specifying the directory to be deployed (in my case: <em>public</em>, <em>app </em>or <em>vendor</em>, or any combination of those), like this:  </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./deploy.sh app vendor</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/bash

# retrieve all variables for server, protocol and directories
source ./.ftp_config

# Base directory (where the script is executed)
BASE_DIR="$(pwd)"

# Function to synchronize a directory using lftp
sync_dir() {
  local local_dir="$1"
  local remote_dir="$2"
  local excludes="$3"  # Additional exclude patterns

  # Construct the full local directory path
  local full_local_path="$BASE_DIR/$local_dir"

  # Check if directory exists locally
  if [[ ! -d "$full_local_path" ]]; then
    echo "Error: Local directory '$full_local_path' does not exist."
    return 1
  fi

  # Debugging output to verify directory paths
  echo "Synchronizing directory: '$full_local_path' to '$remote_dir'"

  # Construct lftp command based on protocol
  if [[ "$PROTOCOL" == "ftps" ]]; then
    lftp -f "
    set ftp:ssl-force true
    set ftp:ssl-protect-data true
    open $SERVER
    user $USERNAME $PASSWORD
    lcd $BASE_DIR
    mirror -c --continue --reverse --delete --verbose $excludes $local_dir $remote_dir
    bye
    "
  else
    lftp -f "
    open $SERVER
    user $USERNAME $PASSWORD
    lcd $BASE_DIR
    mirror -c --continue --reverse --delete --verbose $excludes $local_dir $remote_dir
    bye
    "
  fi

  # Check if lftp command succeeded
  if [[ $? -eq 0 ]]; then
    echo "Successfully synchronized '$full_local_path' to '$remote_dir'"
  else
    echo "Error: Synchronization of '$full_local_path' failed."
    exit 1
  fi
}

# Function to upload a single file using curl
upload_single_file() {
  local local_file="$1"
  local remote_file="$2"

  # Construct the full local file path
  local full_local_path="$BASE_DIR/$local_file"

  # Check if file exists locally
  if [[ ! -f "$full_local_path" ]]; then
    echo "Error: Local file '$full_local_path' does not exist."
    return 1
  fi

  # Determine the URL scheme and port based on protocol
  if [[ "$PROTOCOL" == "ftps" ]]; then
    CURL_OPTS="--ftp-ssl-reqd --ftp-create-dirs --ftp-pasv"
  else
    CURL_OPTS="--ftp-pasv"
  fi

  # Upload the file using curl
  curl $CURL_OPTS --ftp-create-dirs -T "$full_local_path" --user "$USERNAME:$PASSWORD" ftp://$SERVER:$PORT$remote_file

  # Check if curl command succeeded
  if [[ $? -eq 0 ]]; then
    echo "Successfully uploaded '$full_local_path' to '$remote_file'"
  else
    echo "Error: Upload of '$full_local_path' failed."
    exit 1
  fi
}

# Check for arguments and call the appropriate function
if [[ $# -eq 0 ]]; then
  echo "Usage: $0 (app|vendor|public|onefile) [relative_local_path] [/absolute_remote_path]"
  echo "  app: Synchronize app directory only."
  echo "  vendor: Synchronize vendor directory only."
  echo "  public: Synchronize public_html directory only."
  echo "  onefile: Upload a single file using curl."
  echo "  for example:"
  echo "  ./deploy.sh onefile app/Controllers/Test.php /project/app/Controllers/Test.php"
  exit 1
fi

# Check the first argument to decide which function to call
case $1 in
  app)
    echo 'Syncing app folder...'
    sync_dir "$LOCAL_APP" "$REMOTE_APP" "$APP_EXCLUDE_GLOBS"
    ;;
  vendor)
    echo 'Remove vendor dev dependencies...'
    composer install --no-dev
    echo 'Syncing vendor folder...'
    sync_dir "$LOCAL_VENDOR" "$REMOTE_VENDOR"  "$VENDOR_EXCLUDE_GLOBS"
    echo 'Restore vendor dev dependencies...'
    composer install
    ;;
  public)
    echo 'Syncing public folder...'
    sync_dir "$LOCAL_PUBLIC_HTML" "$REMOTE_PUBLIC_HTML" "$PUBLIC_EXCLUDE_GLOBS"
    ;;
  onefile)
    if [[ $# -ne 3 ]]; then
      echo "Usage: $0 onefile [local_path] [remote_path]"
      exit 1
    fi
    echo 'Uploading single file...'
    upload_single_file "$2" "$3"
    ;;
  *)
    echo "Invalid argument: '$1'"
    exit 1
    ;;
esac

exit 0
</pre>



<p class="wp-block-paragraph">As you see, there is an option &#8222;onefile&#8221; with additional argumens, that runs a <strong>curl</strong> command (instead of <strong>lftp</strong>). It is handy if you only need to update one file; or, as in my case, sometimes lftp gets stuck on a file for a long time failing to sync (in my case there are two such files in the project). It is some bug in lftp, which is no longer actively maintained. So, in case I change such file and it gets stuck, I apply the &#8222;onefile&#8221; command as a workaround: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./deploy.sh onefile app/Controllers/Test.php /project/app/Controllers/Test.php</pre>



<p class="wp-block-paragraph">If anyone finds this useful, or as some improvements to suggest – please tell me in the comments.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/site-deployment-via-sftps/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Enhancing SQlite3 with ICU extension</title>
		<link>https://dg.lapas.info/irasas/enhancing-sqlite3-with-icu-extension/</link>
					<comments>https://dg.lapas.info/irasas/enhancing-sqlite3-with-icu-extension/#respond</comments>
		
		<dc:creator><![CDATA[Donatas Glodenis]]></dc:creator>
		<pubDate>Sun, 14 Jul 2024 15:17:40 +0000</pubDate>
				<category><![CDATA[informacinės technologijos]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[sqlite3]]></category>
		<guid isPermaLink="false">https://dg.lapas.info/?p=4737</guid>

					<description><![CDATA[I have been using SQLite3 databases in a few of my projects lately, and liked a lot the simplicity of development using SQLite3. It is so easy when database is a single file, and there is no extra database server to connect to. I liked SQLite3 so much that I have even moved this blog [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I have been using SQLite3 databases in a few of my projects lately, and liked a lot the simplicity of development using SQLite3. It is so easy when database is a single file, and there is no extra database server to connect to. I liked SQLite3 so much that I have even moved this blog to use it as backend DB. </p>



<p class="wp-block-paragraph">One thing that troubled me, however, was lack of proper text ordering in Lithuanian language, as well as lack of case-insensitive search for UTF8 characters. </p>



<span id="more-4737"></span>



<p class="wp-block-paragraph">Here is how SQLite3 would order Lithuanian letters: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ sqlite3
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> CREATE TABLE test (name TEXT COLLATE NOCASE);
sqlite> INSERT INTO test (name) VALUES ('ž'), ('Ž'), ('e'), ('q'), ('į'), ('i'), ('y'), ('ė'), ('ę'), ('c'), ('z');
sqlite> SELECT name FROM test ORDER BY name;
c
e
i
q
y
z
ė
ę
į
Ž
ž
sqlite></pre>



<p class="wp-block-paragraph">All specific Lithuanian letters would be moved to the end by the available collations. </p>



<p class="wp-block-paragraph"><strong>At first I tried to solve the ordering issue by a workaround</strong> (retrieve database results into a PHP array and then reorder it; or order the results on the frontend using, for example, Datatables). That was not very scalable (it meant loading full tables into PHP&#8217;s memory for sorting!) and it meant extra programming. So, not good enough.</p>



<p class="wp-block-paragraph"><strong>Later I found out that PHP&#8217;s SQlite3 extension can pass the ordering or lowercasing of text to a PHP function</strong>, which removed the need for extra programming to order (or search) properly. <a href="https://forum.codeigniter.com/showthread.php?tid=91104&amp;pid=419618#pid419618">I described how to do that in this forum post</a> (for both Vanilla PHP and Codeigniter 4). Searches, however, became so much slower. Not a big problem on a small app, mostly unnoticeable, but once I tried it on a database with 0.5 mln records searches began to take up to 5 seconds.</p>



<p class="wp-block-paragraph"><strong>Finally today I managed to compile</strong> the <a href="https://www.sqlite.org/src/doc/trunk/ext/icu/README.txt" data-type="link" data-id="https://www.sqlite.org/src/doc/trunk/ext/icu/README.txt">ICU extension for SQLite3</a>, which reduced the database operation time by half in comparison to the operation with PHP functions, since SQLite3 no longer needs to employ external PHP functions for ordering, lowercasing and searching. That is perhaps the best that can be done in trying to optimize SQLite3 for web projects on my part.</p>



<p class="wp-block-paragraph">Let me share it. </p>



<p class="wp-block-paragraph">So, my development computer has a Kubuntu 22.04 OS.</p>



<p class="wp-block-paragraph">First I had to get compile dependencies as well as the development packages for SQlite3 and the ICU libraries: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ sudo apt build-dep sqlite3
$ sudo apt install libicu-dev libsqlite3-dev</pre>



<p class="wp-block-paragraph">Then I got the source code of SQlite3:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ mkdir /tmp/SQLITE
$ cd /tmp/SQLITE
$ apt source sqlite3</pre>



<p class="wp-block-paragraph">Then got into the source, the ICU extension folder: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ cd sqlite3-3.37.2/ext/icu</pre>



<p class="wp-block-paragraph">Within that directory there were three files: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ ls
icu.c README.txt  sqliteicu.h</pre>



<p class="wp-block-paragraph">So I read the `README.txt` file first, where I found the compilation instructions: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ gcc -fPIC -shared icu.c `pkg-config --libs \  
     --cflags icu-uc icu-io` -o libSqliteIcu.so</pre>



<p class="wp-block-paragraph">After running the command the `libSqliteIcu.so` extension was compiled. I tried to use it in SQLite3 cli client: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ sqlite3
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .load ./libSqliteIcu.so
Error: ./libSqliteIcu.so: undefined symbol: sqlite3_sqliteicu_init</pre>



<p class="wp-block-paragraph">So, something did not quite work, some undefined symbol `sqlite3_sqliteicu_init`, whatever it is&#8230;</p>



<p class="wp-block-paragraph">It sent me googling, and I <a href="https://gist.github.com/ap-Codkelden/c5c0b8d1ed775238102eef6e2f482962" data-type="link" data-id="https://gist.github.com/ap-Codkelden/c5c0b8d1ed775238102eef6e2f482962">found explanation</a> that suggested I needed to open the file `icu.c` and replace every instance of string `sqlite3_icu_init` with `sqlite3_sqliteicu_init`. </p>



<p class="wp-block-paragraph">So I replaced `sqlite3_icu_init` with `sqlite3_sqliteicu_init` (found one string only) and rerun the compile command. And voila, it worked! Here is the test:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ sqlite3
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .load ./libSqliteIcu.so
sqlite> SELECT icu_load_collation('lt', 'lithuanian');

sqlite> CREATE TABLE test (name TEXT COLLATE lithuanian)
   ...> ;
sqlite> INSERT INTO test (name) VALUES ('ž'), ('Ž'), ('e'), ('q'), ('į'), ('i'), ('y'), ('ė'), ('ę'), ('c'), ('z');
sqlite> SELECT name FROM test ORDER BY name;
c
e
ę
ė
i
į
y
q
z
ž
Ž</pre>



<p class="wp-block-paragraph">So, now the ordering was good. </p>



<p class="wp-block-paragraph">To use the extension with Apache&#8217;s php I needed to put the extension into some common folder, so I created it: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">$ mkdir -p /usr/lib/sqlite3/ext/
$ cp libSqliteIcu.so /usr/lib/sqlite3/ext/</pre>



<p class="wp-block-paragraph">Then I pointed php.ini file located at `/etc/php/php8.1/apache/php.ini`, found relevant SQLite3 section and pointed the variable `sqlite3.extension_dir` to the directory:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="ini" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[sqlite3]
# Directory pointing to SQLite3 extensions
sqlite3.extension_dir = /usr/lib/sqlite3/ext/</pre>



<p class="wp-block-paragraph">The extension has to be loaded in PHP scripts after the initiation of database. Below is a full example:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;?php

$db = new SQLite3(':memory:');
$db->loadExtension('libSqliteIcu.so');
$db->exec("SELECT icu_load_collation('lt', 'lithuanian')");

$db->exec('CREATE TABLE test (name TEXT COLLATE lithuanian)');

$db->exec("INSERT INTO test (name) VALUES ('ž'), ('Ž'), ('e'), ('q'), ('į'), ('i'), ('y'), ('ė'), ('ę'), ('c'), ('z')");

$result = $db->query('SELECT name FROM test ORDER BY name');

while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
    echo $row['name'] . "\n";
}</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://dg.lapas.info/irasas/enhancing-sqlite3-with-icu-extension/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
