Best practices in translating your platform

Posted: October 27th, 2021 | Author: | Filed under: Developers | No Comments »

Translating a platform becomes more important as the market shifts toward globalization and the internet became available even for poor countries. But as a developer it is a challenge to organize the translations in such a way that serves all parties involved in the process.

Let's begin with stories:

  1. as a developer I want to be able to use the same translations for common buttons such as edit, delete, add, etc
  2. as a developer I want to have the translations grouped by a model so I can find them easily when working on a particular feature.
  3. as a developer I want to split the translation by modules/packages/bundles so I can stay focused on a single part of the application when working on it
  4. as a project manager I want to have the translations grouped by business logic so I can give them to a translator then to a reviewer

Possible implementations related to the location of the translation files

Note that <somepackage> is the name of a package/module/bundle and <language> is the language code.

  1. All files under the same translation folder:
    /translations/somepackage.language.yaml
    /translations/anotherpakage.language.yaml
  2. Translations inside the same folder of different packages
    somepackage/translations/language.yaml
    anotherpackage/translations/language.yaml
  3. All files under language-related folders:
    language/somepackage.yaml
    language/anotherpackage.yaml
    – this one opens the possibility of having not only 'yaml translation files' but also entire documents translated and stored here, like emails in both text and html formats.
  4. Nested paths, e.g. the list of translations is made of a sequence of files where the keys in the later files overwrites the first ones.
    translations/messages.language.yaml
    somepackage/translations/messages.language.yaml
    – while is neat for the developer, it may be difficult for the project manager to cover all implications

Possible approaches related to the position of the translation keys inside the files

  1. Use a <global> key for global translations and <model> keys for specifics
    global:
    edit: Edit
    User:
    create_user: Create user
    – the problem here may be that sometimes seems awkward not to place an "invitation" keyword under an "organization" model because the "invitation" is also a model, but in the application it makes sense to relate the invitation to the organization only
  2. Use business logic based on the features implemented (by menus, for example):
    account_management:
    create_user: Create user
    purchased_plan: Purchased plan

Every approach seems to have some advantages and disadvantages, but choosing the proper approach has to be done very early in the project's lifetime and once chosen one stay stick with it in order to avoid frustrations. Which one do you prefer?


How to switch PATH in fish shell – with example script

Posted: October 22nd, 2021 | Author: | Filed under: Server side | No Comments »

fish is a Unix shell with a focus on interactivity and usability. Fish is designed to give the user features by default, rather than by configuration.[4] Fish is considered an exotic shell since it does not rigorously adhere to POSIX shell standards, at the discretion of the maintainers. en.wikipedia.org/wiki/Fish_(Unix_shell)

Although being "an exotic shell" once installed you'd never want to remove it since it proves so useful in the daily tasks. However, one of the annoying side effects is that it is not easy to remove paths from the PATH variable, which is required when wanting to change the environment. Here is an example:  changing from PHP74 to PHP72 on a MacOS, for both the CLI and FPM configurations.

Scripts to switch PHP versions from PHP72 to PHP74

> cat ~/fish_php74.sh

#!/usr/bin/env fish

#remove PHP72
for argv in /usr/local/opt/[email protected]/bin /usr/local/opt/[email protected]/sbin;
    while contains $argv $fish_user_paths
        set -l index (contains -i $argv $fish_user_paths)
        set –erase –universal fish_user_paths[$index]
    end
end

#add PHP74
for argv in /usr/local/opt/[email protected]/bin /usr/local/opt/[email protected]/sbin;
    if contains $argv $fish_user_paths
        echo "Path already contains $argv"
    else
fish_add_path $argv
    end
end

echo $fish_user_paths | tr " " "\n" | nl

brew services stop [email protected]
brew services start [email protected]

Run it:

> ~/fish_php74.sh

     1 /usr/local/opt/[email protected]/sbin

     2 /usr/local/opt/[email protected]/bin

Stopping `[email protected]`… (might take a while)

==> Successfully stopped `[email protected]` (label: [email protected])

==> Successfully started `[email protected]` (label: [email protected])

Scripts to switch PHP versions from PHP74 to PHP72

> cat ~/fish_php72.sh

#!/usr/bin/env fish

#remove PHP74
for argv in /usr/local/opt/[email protected]/bin /usr/local/opt/[email protected]/sbin;
    while contains $argv $fish_user_paths
        set -l index (contains -i $argv $fish_user_paths)
        set –erase –universal fish_user_paths[$index]
    end
end

#add PHP72
for argv in /usr/local/opt/[email protected]/bin /usr/local/opt/[email protected]/sbin;
    if contains $argv $fish_user_paths
        echo "Path already contains $argv"
    else
        fish_add_path $argv
    end
end

echo $fish_user_paths | tr " " "\n" | nl

brew services stop [email protected]
brew services start [email protected]

 

Run it:

~/fish_php72.sh

     1 /usr/local/opt/[email protected]/sbin

     2 /usr/local/opt/[email protected]/bin

Stopping `[email protected]`… (might take a while)

==> Successfully stopped `[email protected]` (label: [email protected])

==> Successfully started `[email protected]` (label: [email protected])


PHP + Apache + mod_fcgid + suexec on Ubuntu and Mac

Posted: August 14th, 2014 | Author: | Filed under: General | No Comments »

Background

On linux machines (Mac too) the default installation of Apache+PHP is sometime not suitable for modern script due to at least two reasons:

      1. speed
      2. user rights

While the first is obvious, the second one needs some explanations and the best way is by giving a real-world example. In the Symfony framework the content is prepared under a "cache" folder for achieving a very good speed in processing requests. The framework also uses some command prompt statements for achieving various tasks such as database structure update, warming up the cache, etc. As one knows, all requests processed by Apache runs by default under its user, be it "apache", "www" or "nobody". On the other hand the command line statements run under the current logged-in user, let's call it "myuser". Both actions above uses the same cache folder and here is where the conflict appears, since one cannot update the other's files. Hence the need to run the script under the same user, both withing the Apache and from the command prompt. Plus that is faster (developers would like this).

Goal & solution

One recommended configuration for achieving the speed is to replace the standard mod_php with mod_fcgid, meaning that the process won't run anymore inside Apache but as an external process called by Apache when needed.

As for running the process as another user than Apache's, there is suexec which consists of a module running under apache and a binary executable acting as a wrapper.

The configuration presented here uses virtual hosts, a know feature of Apache letting one run multiple websites on the same computer, each with its own configuration, folder and user account. The process of configuring the server is split in two parts, first for configuring the mod_fcgid module then adding suexec mechanism.

Configuring mod_fcgid

The configuration starts by disabling the standard php module and installing the mod_fcgid module. The steps differs based on the linux distribution therefore the steps below will be given for Ubuntu and Mac so the reader will have to figure out how it works for other ditributions.

Ubuntu:

sudo apt-get install libapache2-mod-fcgid php5 php5-cgi

Note that this article assume that apache is already installed. If php5 is also installed is even better, just be sure php5-cgi is also.

Mac

sudo port install php5 +fastcgi

sudo port install mod_fcgid

Then disable mod_php end enable mod_fcgid:

Ubuntu:

sudo a2dismod php5
sudo a2enmod fcgid

Mac:

sudo vi /private/etc/apache2/httpd.conf

Comment this line: #LoadModule php5_module libexec/apache2/libphp5.so

Add/uncomment this line: LoadModule fcgid_module libexec/apache2/mod_fcgid.so

Add this line to your php.ini (Ubuntu: /etc/php5/cgi/php.ini, Mac: /etc/php.ini)

cgi.fix_pathinfo=1

 

Tip: the location of php.ini differs and not only that, there may be two different files, one for PHP as module and the other for PHP as CGI. Locate them in the system with:

locate php.ini

A better approach can be to use php itself to tell where is its ini file:

php -i |grep php.ini

Result:

Loaded Configuration File => /etc/php.ini

Additional useful information, in case one needs to know what PHP version runs from command line, especially where there are two PHP versions installed:

#which php
/usr/bin/php

#php -v

PHP 5.4.24 (cli) (built: Jan 19 2014 21:32:15)

 

Now is the time to configure mod_fcgid and this is done in two places: one global place inside apache's own confguration files and the other in each site. The global configuration file looks like this:

Ubuntu (/etc/apache2/mods-enabled/fcgid.conf):

<IfModule mod_fcgid.c>
AddHandler    fcgid-script .fcgi
FcgidConnectTimeout 20
FcgidFixPathinfo 1
FcgidMaxRequestLen 2097152
</IfModule>

Mac (/etc/apache2/other/fcgid.conf):

<IfModule mod_fcgid.c>
FcgidIPCDir /usr/lib/apache2/fcgid/sock
FcgidProcessTableFile /usr/lib/apache2/fcgid/fcgid_shm
FcgidConnectTimeout 20
FcgidFixPathinfo 1

# fix for:   mod_fcgid: read data timeout in 40 seconds
FcgidIdleTimeout 3000
FcgidMinProcessesPerClass 100
FcgidConnectTimeout 120
FcgidIOTimeout 400
FcgidMaxProcesses 150
FcgidBusyTimeout 600

# to get around upload errors when uploading images increase the MaxRequestLen size to 15MB
FcgidMaxRequestLen 15728640

FcgidProcessLifeTime 7200
</IfModule>

Although the Mac configuration seems more complex, it is only because more configuration directives have been used. Normally the two may look the same, except the paths of FcgidIPCDir and FcgidProcessTableFile

Tip: Be aware that the names of the directives were changed since mod_fcgid 2.3.2  (it is currently at version 2.3.9). The reason you're warned here is because many answers online uses PHP_Fix_Pathinfo_Enable instead of FcgidFixPathinfo. (all changes in the table here). The most important directives to use are: FcgidFixPathinfo, FcgidMaxRequestLen and FcgidBusyTimeout.

 

The site configuration file should include the followings somewhere inside <VirtualHost> tags:

Ubuntu (/etc/apache2/sites-available/yoursite.com.conf) & Mac (/etc/apache2/extra/httpd-vhosts.conf):

<IfModule mod_fcgid.c>
#SuexecUserGroup myuser mygroup
<Directory /var/www/yoursite.com/ >
Options +ExecCGI
AllowOverride All
AddHandler fcgid-script .php
FcgidWrapper /var/www/php-fcgi-scripts/php-fcgi-starter .php
Order allow,deny
Allow from all
</Directory>
</IfModule>

 

Tip: Note that in the above configuration the line with SuexecUserGroup had been commented. This is to avoid seeing errors caused by Suexec while setting up fcgid. Later the line will be un-commented. Or, if you feel lucky, un-comment it now.

Once the above are set, restart apache:

Ubuntu: sudo service apache2 restart

Mac: sudo apachectl restart

 

Now is is time to talk about the log files because they tell valuable information about the problems which may occur.

Apache's log file to watch is named "error_log" and there you may find issues related to why the server does not start, when some SSL configuration is broken or when suexec has issues.

Suexec has its own log in the same folder as Apache's and there you will find issues related to user rights or problems when running the executable.

Each site is recommended to have its own access and error log files. There you will find errors related to the script or accessing the script, acting as a starting point in solving the issues. In case of errors related to suexec you will find here only some indications, while the details have to be found in the suexec file above

Configuring suexec

This executable binary usually comes with Apache but on a Mac installation this binary was missing. With or without it, sometimes is necessary to adjust its default settings, e.g. those it has been compiled with. Below all these situations will be covered so one can skip some steps if the default installation fits the needs.

Begin with checking the location end existence of suexec:

# which suexec

If the binary is not in the path you won't get any result. Try this:

# locate suexec

The output should be a path like /usr/lib/apache2/suexec on Ubuntu or /usr/bin/suexec on Mac, but the location may vary.

Once you located it, run it to see its current settings. Since it is supposed to be run by root only, you need to use "sudo":

Ubuntu:

# sudo /usr/lib/apache2/suexec -V
-D AP_DOC_ROOT="/var/www"
-D AP_GID_MIN=100
-D AP_HTTPD_USER="www-data"
-D AP_LOG_EXEC="/var/log/apache2/suexec.log"
-D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
-D AP_UID_MIN=100
-D AP_USERDIR_SUFFIX="public_html"

Mac:

#sudo /usr/bin/suexec -V
Password:
-D AP_DOC_ROOT="/Users"
-D AP_GID_MIN=10
-D AP_HTTPD_USER="_www"
-D AP_LOG_EXEC="/var/log/apache2/suexec_log"
-D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
-D AP_UID_MIN=500
-D AP_USERDIR_SUFFIX="Sites"

Pay attention to DOC_ROOT : it is the only path allowed to run executable (wrapper) from. The HTTPD_USER is useful in verifying that the wrapper is accessible by this user Finally, UID and GID are constraints for minimum user and group IDs, as they are defined in the /etc/passwd file.

The wrapper file is being launched by Apache for any request whose extension ends in PHP, as defined in the site's configuration file above and suexec must have the rights to access it. Also, the file must be inside DOC_ROOT. The file looks like this:

#!/bin/sh
PHPRC=/etc/php5/cgi/
export PHPRC
export PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_CHILDREN=8
exec /usr/lib/cgi-bin/php

Tip: PHPRC is the path up to php.ini. The executable on the last line must be the cgi version of php binary. Sometimes (on Mac) it is called "php-cgi" and is under /opt/local/bin/. Just check yours using "which" or "locate" and run it as "php-cgi -v" to verify that it is ok and has the expected version.

So, create the file under DOC_ROOT, let's say "/var/www/php-fcgi-scripts/php-fcgi-starter" with the above content and make it executable (chmod a+x /var/www/php-fcgi-scripts/php-fcgi-starter). Take care yourself of creating the folder php-fcgi-scripts and assigning it the proper user and group (myuser and mygroup).

Tip: an easy way to verify that a user can access that file is to add "-v" as parameter for php on the last line then call it with:

su myuser -c "/var/www/php-fcgi-scripts/php-fcgi-starter"

In other words: "run this command as user myuser" and it will display the PHP version. Remove the parameter afterward. Repeat: remove the parameter afterward (so it will run whatever PHP file will be send by Apache as parameter)

 

Back on track now, if everything goes well you should remove the comment to SuexecUserGroup and restart Apache. Prepare a simple PHP file in the site's root folder with the following content:

<?php phpinfo();

If everything was setup successfully then you'll get the nice long listing with PHP's current configuration. Among those lines you will find references to mod_fcgid and all other features configured.

 

Additional info

Sometime installing on MacOS using brew or port is not enough due to missing packages. In this case compiling from sources is a good alternative and it is not that difficult. Here is a sample of configuring PHP but also making use of "port" for installing supporting libraries

Download PHP from sources first then run this from the folder where the files were unpacked:

$ sudo port install libpng freetype mcrypt

$ sudo './configure' '–prefix=/opt/local' '–mandir=/opt/local/share/man' '–infodir=/opt/local/share/info' '–program-suffix=55' '–includedir=/opt/local/include/php55' '–libdir=/opt/local/lib/php55' '–with-config-file-path=/opt/local/etc/php55' '–with-config-file-scan-dir=/opt/local/var/db/php55' '–disable-all' '–enable-bcmath' '–enable-ctype' '–enable-dom' '–enable-filter' '–enable-hash' '–enable-json' '–enable-libxml' '–enable-pdo' '–enable-session' '–enable-simplexml' '–enable-tokenizer' '–enable-xml' '–enable-xmlreader' '–enable-xmlwriter' '–with-bz2=/opt/local' '–with-mhash=/opt/local' '–with-pcre-regex=/opt/local' '–with-libxml-dir=/opt/local' '–with-zlib=/opt/local' '–without-pear'  '–enable-fileinfo' '–enable-phar' '–disable-fpm' '–enable-cgi'  –with-freetype-dir –enable-mbstring –with-curl –with-mcrypt=/opt/local –with-zlib –with-gd –with-pdo-mysql –with-mysqli –with-openssl –with-gettext=/opt/local –with-xmlrpc –with-xsl –with-png-dir=/opt/local –with-jpeg-dir=/opt/local –with-freetype-dir=/opt/local

 

Downloading Apache from sources requires this configuration:

./configure –with-layout=Darwin –enable-suexec –with-suexec-caller=_www –with-suexec-uidmin=500 –with-suexec-gidmin=10 –with-suexec-docroot=/Users/ –with-suexec-userdir=Sites \
–prefix=/usr/local/apache2 –enable-so \
–enable-rewrite –enable-ssl –enable-mods-shared='rewrite ssl'

 


RegEx email parser

Posted: April 16th, 2008 | Author: | Filed under: Developers | No Comments »

Split email formats in pieces through preg_match:

$str=' (|(([^a-zA-Z0-9_\-.]|)(.*?)(|[^a-zA-Z0-9_\-.]+?)([a-zA-Z0-9_\-.]+?)(|[^a-zA-Z0-9_\-.]+?)))(|[^a-zA-Z0-9_\-.])([a-zA-Z0-9_\-.]+?)(|[^a-zA-Z0-9_\-.])@(.+?)\.([^>]+)';


Email validity check

Posted: April 16th, 2008 | Author: | Filed under: Developers | No Comments »

PHP code:


<pre lang="php">
<?php 
function is_email($email) {
  $x = '\d\w!\#\$%&\'*+\-/=?\^_`{|}~';    //just for clarity
  return   count($email = explode('@', $email, 3)) == 2 && 
      strlen($email[0]) < 65 && strlen($email[1]) < 256 && 
      preg_match("#^[$x]+(\.?([$x]+\.)*[$x]+)?$#", $email[0]) && 
      preg_match('#^(([a-z0-9]+-*)?[a-z0-9]+\.)+[a-z]{2,6}.?$#', $email[1]);
}
?>
</pre>


Contact us

Posted: March 22nd, 2008 | Author: | Filed under: General | No Comments »

%%wpcontactform%%