How To Run Multiple PHP Versions

The PHP programming language has gone long way since its beginnings. It made another big step by making it easy and convenient to run Apps in different versions of PHP at the same time. In this post, I go over the compiling of any PHP Version you wish, installation of the PHPFarm technology and the configuration of the Apache web server, all of which part of the task to run different PHP versions simultaneously on one machine with different apps

System: Ubuntu 14, Apache 2.4, LAMP

Installation of PHPFarm

Before compiling different versions of PHP, we install the PHPFarm. To do so

Step 1 – Install supporting necessary libraries:
udo apt-get install libxml2 libxml2-dev libssl-dev
sudo apt-get install libcurl4-openssl-dev pkg-config
sudo apt-get install libcurl4-gnutls-dev libjpeg-dev libpng12-dev libmysqlclient-dev
Step 2 – Install the PHPFarm

We grab the PHPFarm from PHPFarm Git repo

cd /home/tools
sudo git clone https://github.com/cweiske/phpfarm

This will clone PHPFarm at /home/tools/phpfarm directory

Compile PHP Versions

Once, we have the PHPFarm, lets compile some version of PHP.

Step 3 – Custom Configurations

First, we specify some custom configurations for new php version. Go to the ‘src’ directory of PHPFarm and create file of certain naming

cd /home/tools/phpfarm/src
sudo vim custom-options-5.3.29.sh

For some example of custom options, please see AmacGregor Git Repo
In our case, we use the following custom options by inserting them in the /home/tools/phpfarm/srccustom-options-5.3.29.sh:

#gcov='--enable-gcov'
configoptions="\
--enable-bcmath \
--enable-calendar \
--enable-exif \
--enable-ftp \
--enable-mbstring \
--enable-pcntl \
--enable-soap \
--enable-sockets \
--enable-sqlite-utf8 \
--enable-wddx \
--enable-zip \
--disable-debug \
--with-mysql \
--with-zlib \
--with-gettext \
--with-pdo-mysql \
--with-curl \
--with-openssl \
$gcov"
Step 4 – Compile PHP

Next, we compile new PHP version with our custom options:

cd /home/tools/phpfarm/src
sudo ./compile.sh 5.3.29

This will generate new version of PHP 5.3.29. Afterwards, test that it is working

cd /home/tools/phpfarm/inst/bin
./php-5.3.29 --version

Configure Virtual Host of Apache For Certain Version

Step 5 – Install supporting modules of PHPFarm for Apache

The FastCGI is technology to incorporate PHPFarm into Apache, so we are installing supporting libraries

sudo apt-get install libapache2-mod-fastcgi apache2-mpm-worker apache2-suexec
sudo a2enmod actions fastcgi suexec
sudo service apache2 restart

After restarting Apache service, the newly installed modules are included

Step 6 – Mapping CGI to PHPFarm

Next, we create web directory with configurations to map each PHP Version to the CGI service of Apache

sudo mkdir /var/www/cgi-bin
cd /var/www/cgi-bin
sudo vip php-cgi-5.3.29

Afterwards, we are adding mapping in form of Bash script named php-cgi-5.3.29 as follows

#!/bin/sh
PHPRC="/etc/php5/cgi/5.3.29/"
export PHPRC
PHP_FCGI_CHILDREN=3
export PHP_FCGI_CHILDREN
PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_MAX_REQUESTS
exec /home/tools/phpfarm/inst/bin/php-cgi-5.3.29

As you see the last line it maps to our PHP version. The variable “PHPRC” also specifies the location of php.ini file to use. By default, it uses the php.ini in the dir of /home/tools/phpfarm/inst/php-cgi-5.3.29/lib.php

Make our file of mapping is executable and chown rights to apache user („www-data“ by default):

sudo chmod +x /var/www/cgi-bin/php-cgi-5.3.29
sudo chown -R www-data:www-data /var/www/cgi-bin
Step 7 – Configure Virtual Host of Apache

At last, we configure particular application via Virtual Host Configurations at Apache to use the PHPFarm and our certain version of PHP by editing /etc/apache2/sites-enabled/some-domain.conf

<VirtualHost *:80>
	DocumentRoot /home/websites/min-view-drupal
	ServerName minnehaha.com
	ServerAlias www.minnehaha.com
	#LogLevel debug
	#FastCgiServer /home/websites/cgi-bin/php-cgi-5.3.29
    	FastCgiServer /home/websites/cgi-bin/php-cgi-5.4.27
	ScriptAlias /cgi-bin-php/ /home/websites/cgi-bin/
	<Directory "/home/websites/min-view-drupal">
		Options Indexes MultiViews FollowSymLinks
		AllowOverride All
		Require all granted
		AddHandler php-cgi .php
		#Action php-cgi /cgi-bin-php/php-cgi-5.3.29
		Action php-cgi /cgi-bin-php/php-cgi-5.4.27
		<FilesMatch "\.php$">
                	SetHandler php-cgi
            	</FilesMatch>
	</Directory>
	<Directory "/home/websites/cgi-bin">
   		Order allow,deny
   		Allow from all 
  		Require all granted
	</Directory>
 	Errorlog "${APACHE_LOG_DIR}/virtual-hosts/minnehaha/error.txt"
	CustomLog "${APACHE_LOG_DIR}/virtual-hosts/minnehaha/customLog.txt" common
</VirtualHost>

Here, all the highlighted lines involve configuring the PHPFArm. The line 7,8 includes the PHPFarm service. The lines 13-18, enables the certain version for our application. The lines 20-24 enables apache server to read our mapping configuration file which should be moved to general Apache configuration script since this directory may be shared between other Virtual hosts. In short if you have multiple Virtual Hosts then the following should be moved into general to declare only once:

...
FastCgiServer /home/websites/cgi-bin/php-cgi-5.4.27
ScriptAlias /cgi-bin-php/ /home/websites/cgi-bin/
...
<Directory "/home/websites/cgi-bin">
   		Order allow,deny
   		Allow from all 
  		Require all granted
	</Directory>
...

At last, restart Apache to load our newest changes:

sudoe services apache2 restart
Step 8 – Test it

To test it, include the phpinfo command into the index.php file of the root dir(i.e./home/websites/min-view-drupal) of the application

<?php phpinfo();?>

This should display php version along other setting once pointing the browser to the application of virtual host configured above(i.e. minnehaha.com)

Switching PHP Versions

After compiling another version of PHP as we did in Step 4, the switching between PHP Versions is as easy as configuring apache virtual host to read another mapping as done in Step 8(see commented out php version 5.4.27) to point to another php version done by creating new mapping as done in Step 7.

Troubleshooting

1. client denied by server configuration:

This error we got when the location(i.e. /home/websites/cgi-bin) of our mapping script of step 7 wasn’t configured to be loaded by Apache in virtual host missing the following:

<Directory "/home/websites/cgi-bin">
   		Order allow,deny
   		Allow from all 
  		Require all granted
	</Directory>

By including the above snippet and restarting Apache, the problem was solved

2. FastCgiServer: redefinition of a previously defined FastCGI server phpFarm

This error appeared when I declared the following in multiple Virtual host configurations:

FastCgiServer /home/websites/cgi-bin/php-cgi-5.4.27
ScriptAlias /cgi-bin-php/ /home/websites/cgi-bin/

Once, I moved it out into the general apache configuration while commenting out all other instances, it solved the problem

Sources

  • https://github.com/cweiske/phpfarm
  • http://www.jabommi.de/wiki/downgrade-php-5-5-to-5-3-ubuntu-14-with-multiple-php-versions/
  • https://wiki.apache.org/httpd/PHP-FPM
  • https://www.howtoforge.com/how-to-use-multiple-php-versions-php-fpm-and-fastcgi-with-ispconfig-3-ubuntu-12.04-lts
  • http://geekpad.ca/blog/post/compiling-php-and-php-fpm-part-2
  • http://www.sitepoint.com/run-multiple-versions-php-one-server/
  • https://gist.github.com/amacgregor/11041627#file-custom-options-sh

Caching in Drupal

The $variable[‘messages’] is not always available and, thus, it fails in situations where you need to have messages be available anytime and everywhere in the code during single request. As solution to my problem, I utilized Drupal Caching

In search for solution, I started by creating the following function:

function get_messages(){
    $status_messages = array();
    //loop through each message type
        foreach (drupal_get_messages(null, false) as $type => $messages) {
               foreach($messages as $key => $message){
                   $status_messages[$type]['enabled'] = true;
                   $status_messages[$type]['messages'] = array(
                       'message' => $message
                   );
               }
        }
return $status_messages;
}

This works well, however. The messages don’t get to be reset. As a result, messages build up from previous requests something that is unacceptable. We could try to reset messages by calling Drupal API function drupal_get_messages with parameter ‘true’, but then the messages got to be reset on the first time execution of our function – get_messages() and all other times it returns no messages. My challange

I had two ideas on how to approach this problem:

  1. Keep & Reset at the End of Request

    One option is to clear messages at the last point of the request

  2. Cache messages and Reset at the Start of Request

    At first time calling drupal_get_messages(), we would cache the messages and then reset at the same time by calling the function with parameter “TRUE”

For the first solution, it wasn’t clear where exactly is the last point of request good for reset messages. I was also concerned having to touch code in two places to solve single problem
For the second solution, I liked it because it would kill two birds with one stone. We would solve our problem and improve the performance by caching messages and, thus, minimizing execution.

To solve this problem, I turn to Drupal caching mechanism.

Cache During One Request

Drupal provides reference to static storage that is permanent during single execution. To utilize it as cache, here is common pattern of the function as follows:

function some_function(){
 $my_data = &drupal_static(__FUNCTION__);
  if (!isset($my_data)) {
    // Do your expensive calculations here, and populate $my_data
  }
  return $my_data;
}

Here, in the second line we got the static reference with name of our function(i.e. __FUNCTION__). For the first time, this reference will not be set, so we populate it with the data that we like to cache. For all other executions, the static reference is holding our data that we return

Here, is how our message function looks with the Drupal cache mechanism:

function get_messages(){
    $status_messages = &drupal_static(__FUNCTION__);

    if(!isset($status_messages)){
        //loop through each message type
        foreach (drupal_get_messages(null, true) as $type => $messages) {
               foreach($messages as $key => $message){
                   $status_messages[$type]['enabled'] = true;
                   $status_messages[$type]['messages'] = array(
                       'message' => $message
                   );
               }
        }
     }
     return $status_messages;
}

Here, we get the static reference first and then check if its already set. if not, we retrieve the messages and reset at the same time by calling Drupal API function drupal_get_messages with “true” parameter. As result, we reset the messages that solves our problem and, in addition, our performance is improved due to caching

Cache Over Multiple Requests

Perhaps, there is going to be time when you will need to cache over multiple requests instead only one. To do so, Drupal provides cache functions(i.e. cache_get, cache_set, cache_clear_all) that store cache into Database. There is an excellent article on caching in including Drupal cache functions at Beginners Guide Caching Data Drupal 7

References

  • https://www.lullabot.com/blog/article/beginners-guide-caching-data-drupal-7
  • https://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/drupal_static/7

Sharing via Samba Instead VirtualBox Shares

The performance was so largely effected by using the VirtualBox shares on Apache server that I had to find a better solution to share directories between my VirtualBox Dev environment with Web server,database,etc and my machine Mac with the editor software. I turn to Samba Service as solution since it is one of the well known file sharing services available

Step 1 – Make Your Mac Sharing

By default, the file sharing is not enabled in the Mac, so the first step is to do just that

  1. On Mac, go to “System Preferences”->”Sharing” and check “File Sharing”
  2. And add the users and set permissions to “read & write” in the “Sharing” section
  3. On VBox Guest, add the ‘shares’ – directories that going to be shared in the “Sharing” section under Settings of your VirtualBox guest machine

Step 2 – Install Samba

While Mac comes with the Samba support, our guest Os is xUbuntu and it didn’t have Samba installed/enanbled which I did as following:

sudo apt-get install samba
sudo start smbd
sudo start nmbd

This installs and starts the Samba server on my guest Ubuntu

Mount Shares

Once the Samba installed and started, then we mount the share directory. The mounting was done from the Samba server( in our case guest OS) into the host – Mac OS as following:

To mount a share from guest to host:

sudo mount -t cifs //HOST-IP/SHARE-As-Declared-Step-1 /path/At/Guest/machine -o uid=1000,iocharset=utf8,rw,username="USER-HOST",password=USER-HOST-PASSWORD,nounix --verbose

See “man mount.cifs” for more info on options(i.e. “-o”)

To check what’s mounted so far on Ubuntu:

mount

This should display all mounted points including the shares

To mount automatically on reboot:

  1. Create Permanent IP for Host Machine

    Before configuring to auto mount the shares, it is important to have the permanent IP for the host. Otherwise, you will have to manually change the IP every time your Host WiFi IP changes. To have the permanent IP for the Host machine, you have to create “Host Only Network” as described in section 6.1 of post Virtualbox Gotchas

  2. Configure Auto Mount Shares

    To mount the shares at the start up, we edit /etc/rc.local at our Guest Ubuntu OS and added the following:

    mount.cifs //HOST-IP/SHARE-As-Declared-Step-1 /path/to/shared/dir -o uid=1000,iocharset=utf8,rw,username=HOST-USER,password=HOST-USER-PASSWORD,nounix
    

    So by adding the line in the rc.local file, it is executes the mount at the boot process. Do the same for each share you like to mount.

Sharing via AFP

Disclaimer: The AFP is very buggy and should be avoided.
Its possible to use AFP instead of SAMBA. I had to install afp modules on the guest Ubuntu:

sudo apt-get install afpfs-ng

Afterwards, we mounted the the directory as following:

sudo mount_afp afp://host_user:host_user_password@hostIp/share /srv/afp/share

Troubleshooting

Do Manual Mount

To better see the issue, mount the shared dir manually via “mount” command. It seems to display better error messages. Here is sample command taken out from rc.local with extra flags

sudo mount -t cifs //10.88.12.0/workspace /home/margots/workspace -o uid=1000,iocharset=utf8,rw,username=margots,password=PASSWORD,nounix
Check What is mounted?

First, look at what is mounted by issuing command:

mount

It will display a list of all the mounted directories

1. Check Logs

Log files location is declared smb.conf file in “/etc/samba/” dir. The default location “/var/log/samba”

2. See Samba Settings

There is nice way to see the Samba settings:

smbclient -L smb -N 
3. Check if Samba Running

Check if Samba is running:

ps -ef | grep smbd
4. Follow Kernel Logs Fix Mount Problem

Look at the logs to fix any mount problem:

tail -f /var/log/kern.log 

This will be output logs as they come in

5. mount_smbfs: mount error: /srv/samba/share: Unknown error: -1073741275

I haven’t found solution to this. It is happening when trying to mount share from host to the guest instead way around as described above. I guess my solution was just to mount the share from guest to host that accomplishes the task at hand.

6. Package smbfs is not available

According to this page https://wiki.ubuntu.com/MountWindowsSharesPermanently, it seems smbfs is deprecated and you should use cifs instead. try:

sudo apt-get install cifs-utils
7. mount error(22): Invalid argument.Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)

This happened when trying to mount share into host mac

sudo mount -t cifs //192.168.1.4/srv /srv/samba/share -o uid=1000,iocharset=utf8,username="margots",password=38394815

To solve problem, we needed to disable the CIFS Unix Extensions for this mount that was done by adding option “nounix”

sudo mount -t cifs //192.168.1.4/srv /srv/samba/share -o uid=1000,iocharset=utf8,username=USERNAME,password=PASSWORD,nounix
8. Permission Denied For Apache When Writing On Samba Share

There was permission denied when Apache server tried to write in Samba Share. The solutions was to update apache user and group to one with Samba write permissions. To do so, I had to update Apache Environment variables – APACHE_RUN_USER, APACHE_RUN_GROUP in the /etc/apache2/envvars file as following:

export APACHE_RUN_USER=SAMBA_USER=samba_user
export APACHE_RUN_GROUP=share_dir_user_group

The Samba User with group of the share allowed Apache to make writes on Samba shares

9. mount error(13): Permission denied

I got this error when incorrectly enabled file sharing at host machine of Mac. You may need to have the 3 settings present in “System Preferences”->”Sharing”:

    Check “File Sharing”

  • Under “File Sharing”, click “Options” and add the user that will be used for remote sharing
  • Check “Remote Login”
  • Under “Remote Login”, add the user that will be used for remote sharing

This solved the error for me

10. /etc/rc.local Doesn’t Run at Startup

The /etc/rc.local contains our mount functions to run at startup, however. If it doesn’t run, try troubleshooting by running the following commands and looking for error messages for answers

/etc/rc2.d/S99rc.local start
/etc/init.d/rc.local start
/etc/rc.local

This is tracing through the sequence for scripts called before rc.local to find out where it fails

11. No Match Found in /etc/fstab

As I was tracing through the scripts to find out why the rc.local doesn’t seem to be executed at startup to mount my shared dir, i got the error “No Match xxx found in /etc/fstab” prusuant to 10 above. The solution was allow members of CDROM group to mount without prompting for root password by inserting the following line in /etc/sudoers:

%cdrom ALL = NOPASSWD:NOEXEC: /bin/mount, /bin/umount, /sbin/mount.cifs, /sbin/mount.nfs

Afterwards, i add “sudo mount” to mount command in rc.local script:

sudo mount.cifs ...
12. CIFS VFS: cifs_mount failed w/return code = -5 or mount error(5): Input/output error

These errors came up when the host, in my case Mac, didn’t have the shared directory enabled sharing according to Step 1 above

References

  • http://stackoverflow.com/questions/87442/virtual-network-interface-in-mac-os-x
  • http://www.dejadejoder.com/blog/posts/2010/07/21/mac-osx-10-6-snow-leopard-mount_smbfs-authentication-error-problem/
  • http://www.trickyways.com/2011/07/file-sharing-mac-os-x-lion-and-pc-windows-7/
  • http://www.samba.org/samba/docs/Samba24Hc13.pdf
  • http://www.clock.co.uk/blog/virtualbox-40-shared-folders-still-slow
  • https://forums.virtualbox.org/viewtopic.php?f=7&t=18044&start=0
  • https://forums.virtualbox.org/viewtopic.php?p=33519
  • https://help.ubuntu.com/10.04/serverguide/samba-fileserver.html
  • http://adrianmejia.com/blog/2011/07/12/how-to-set-up-samba-in-ubuntu-linux-and-access-it-in-mac-os-and-windows/
  • http://www.samba.org/samba/docs/man/manpages-3/mount.cifs.8.html

Looking at Drupal Performance with XProf and XHGui

Perhaps, your Drupal site is so slow that you cannot ignore it any longer and you have decided to look into the affects of the poor performance. In general, the affects of performance may be categorized into three categories each addressing different aspects of application such as Drupal:

  • Code execution looks at the time code executes.
  • Querying database looks at how much time it takes to query database.
  • Load balancing looks at how your site performs based on number of visitors on your site at the same time.

In this post, we cover how to use XProf and XHGUI to look into code execution aspect of the performance. It also helps determine if code execution related to DB querying is significant enough contributor to slow performance that you have to address it as well

Before starting troubleshooting performance, restart the mysql because mysql keeps cache and some bad queries in the cache may have an unwanted effects on your performance analyses

Profile PHP Executions

To to profile php executions, we will use XHprof. There is XHprof part of Drupal Devel module,however. It’s outdated and it was difficult to make it work with Ubuntu 14/Apache 2.4. Instead we will grab the newest XHprof that comes with nice GUI for reports and run it as stand alone solution

But before installing XHprof, lets install MongoDB because the newest of XHprof requires MongoDB to run.

sudo pecl install --nocompress mongo
echo 'extension=mongo.so' > /etc/php5/mods-available/mongo.ini
sudo php5enmod mongo
sudo service apache2 restart
mongo

This installs MongoDB, configures the php driver to be loaded and starts the mongo DB

Installing XHprof and XHGui

Next, lets install the XHprof.:

sudo pecl install --nocompress xhprof-beta
echo 'extension=xhprof.so' > /etc/php5/mods-available/xhprof.ini
service apache2 restart
php -m | grep xhprof

Here, we first install xhprof module. Next we enable it and then restart it to load the module. At last, we verify if the module is loaded by retrieving all php modules loaded and filtering for xhprof module only. If you see the ‘xhprof’ listed, then you have installed XHprof correctly

Lets install the Gui for xhprof(XHGui) as following:

sudo git clone https://github.com/perftools/xhgui.git xhgui
sudo chmod -R 777 xhgui
cd xhgui
sudo composer install
sudo php install.php

Access XHGui Reports

To see the reports, we will configure virtual host that services dir located at /path/to/xhgui/webroot as following:

<VirtualHost *:80>
	DocumentRoot /home/margots/tools/xhgui/webroot
	ServerName xhgui
	<Directory "/home/margots/tools/xhgui/webroot">
		Options Indexes MultiViews FollowSymLinks
		AllowOverride All
	#	Order allow,deny
	#	Allow from all
		Require all granted
	</Directory>
</VirtualHost>

After putting the above virtual host at /path/to/apache2/sites-available/xhgui.conf, we enable and load:

sudo a2ensite xhgui
services apache2 restart

At last, lets point the url – xhgui to our local machine in the local DNS table /etc/hosts:

127.0.0.1     xhgui

Now, if you go to ‘http://xhgui’, you should see the the reports of xhprof. You should see something like “Looks like you haven’t done any profiling” if for the first time. This is because, we haven’t enabled profiling to any o our sites.

Enabling XHProf Profiling for Drupal Site

One way to enable profiling is to do it in php.ini that is going to profile all sites. If you like to profile individual site, then another way to enable profiling is via virtual host for the particular site. To do so, update the virtual host by adding following line:

<VirtualHost *:80>
...
   php_admin_value auto_prepend_file /path/to/xhgui/external/header.php
...
<Directory path/to/root>
....
</Directory>
<VirtualHost>

After restarting apache, your site will be profiled. Make view requests and open reports at http://xhgui

Configure XProf Profiler

  1. Profiler Every Request

    By default, XHprof will only profile 1% of all requests because it is intended light weight to be used in production. Since we are at development and like to improve performance, we enable to profile all requests by updating /path/to/xhprof/config/config.default.php:

    ...
    'profiler.enable' => function() {
    #        return rand(0, 100) === 42;
    	return true;
        },
    

    Here, we uncommented the ‘rand(0,100)===42’ line and added ‘return true;’ to profile all requests

  2. Clear HXProf or XHGUI

    To clear XHProf with XHGui, log into mongo db and clear the collection – results as following:

    mongo
    use results
    db.drop()
    

    The first line log into mongo db console. Next we switch the the database. At last command, it drops database – results that is is going to be recreated by the XHGui on the next request that is profiled

    Some other useful commands:

    show dbs //shows all databases
    use results //switch to this database
    db.results.help() //to find out all commands available for collection results
    
  3. Delete After 5 Days

    To limit disk usage, perhaps, you like to set certain time after which the logs are deleted

    $ mongo
      > use xhprof
      > db.results.ensureIndex( { "meta.request_ts" : 1 }, { expireAfterSeconds : 432000 } )
    

    This will set logs to be deleted after 5 days.

Enable Profiling for Command Line Executions

Drush is common tool for handling Drupal and you may want to profile it as well.
To profile scripts run from command line, all you have to do is import the header.php as following:

<?php
require '/path/to/xhgui/external/header.php';
// Rest of script.

Or use the -d flag:

php -d auto_prepend_file=/path/to/xhgui/external/header.php do_work.php

This will start profiling the particular script run from command line

Troubleshooting

1. could not extract the package.xml file from “/build/buildd/php5-5.5.9+dfsg/pear-build-download/mongo-1.5.5.tgz”

This happened when installing php mongo driver. The solution was installing uncompressed as following:

pecl install --nocompress mongo
2. Fatal error: Class ‘Xhgui_Config’ not found in [path]/xhgui/src/bootstrap.php

This error appears,when you forget to run ‘composer install’ before ‘php install.php’ in the xhgui directory. Basically, the autoloader.php is missing because composer hasn’t created it and you need autoloader to load Xhgui_Config class

3. xhgui – Failed to connect to: 127.0.0.1:27017: Connection refused

This error indicates that the mongodb cannot start. After looking into mongodb logs(/var/log/mongodb/), it was telling that we are out memory. By increasing memory, the mongodb was able to start and the error was gone

References

SendMail Email Server for Registration and Contact in Drupal

By default, Drupal uses php function mail() for sending email that instead uses the sendmail email server. So, in order for your email to work in Drupal you have to make sure the sendmail email server is installed and working on the server

Locate Sendmail

To see if sendmail is already installed and present:

which sendmail

This should return path to the sendmail executable. If it doesn’t then you have to install. If it does, then continue on the Verify Step

Install Sendmail

Here are steps to install and setup sendmail on linux machines:

  1. To install:
    apt-get install sendmail
    
  2. Ensure there is a route to localhost in the local route table
    vim /etc/hosts
    //And make sure the line looks like this:
    127.0.0.1 localhost localhost.localdomain yourhostnamehere
    
  3. To configure the sendmail:
    sendmailconfig
    
  4. Restart sendmail:
    services sendmail restart
    

Verify Sendmail

To verify, an email works with sendmail:

echo "my test email being send" | /usr/sbin/sendmail local@domain.com 

Here, the path to the sendmail executable is specified that you can get by running “which sendmail” command. This sends email to the local@domain.com by sendmail email server.

If you receive the email, then the sendmail email server is working. If you don’t receive the email then troubleshoot as described in next step

Troubleshoot Sendmail

To troubleshoot the sendmail, look at the logs located at /var/log/mail.log or /var/log/mail.err

Registration & Contact in Drupal

First, make sure the admin email is specified with valid email at Site Information(admin/config/system/site-information), otherwise, the email will not be send and you will receive error message such as ‘Connection timed out with example.com.” in mail.logs

To edit current settings on how the registration is handled, go to
admin/config/people/accounts

To configure contact form, go to admin/structure/contact

Troubleshooting

1. Unable to send e-mail drupal

This indicates that sendmail email server is not installed or configured correctly. You can install,configure as described in this post or turn it off for drupal sending email by adding the following line to the php.ini

sendmail_path = /bin/true
2. sendmail[6140]: My unqualified host name unknown; sleeping for retry …sm-msp-queue[6412]: unable to qualify my own domain name

Make sure that the domain name as specified in the error message is in the local route table(in file /etc/hosts) routing to 127.0.0.1.

Connection timed out with example.com.

Ensure the admin email address is configured with a valid email account in Site Information section admin/config/people/accounts

To enable sendmail to use STARTTLS
Resources
  • https://www.drupal.org/node/404190
  • http://holarails.wordpress.com/2013/11/17/configure-sendmail-in-ubuntu-12-04-and-make-it-fast/

Setup Dynamic Virtual Hosts To Automize Drupal With WorkFlow

A tool WorkFlow has a new functionality “construct-container” that sets up a new container, so you can spin(i.e. drush spin-it) new Drupal instances automatically. Besides creating database, directories in file system, the construct container task also sets up and configures virtual host for the new Drupal instance. In this post, we cover the approach that was taken to provide multiple virtual hosts solution for WorkFlow tool users.

The solution utilizes Apache module virtual_alias. To be able use the Wildcard domain names on the local machine, the DNS server – dnsmasq is used

Install virtual_alias Module

First, check to see if virtual_alias module is already enable and loaded with apache:

apachectl -M | grep "virtual_alias"

If this doesn’t return anything, then the virtual_alias module is not enabled. Lets do so:

sudo a2enmod virtual_alias
sude service apache2 restart

This should enable the virtual_alias module and load in apache. Go ahead and verify with “apachectl -M” to see if its loading

Set Up Universal Virtual Host Alias

In this example, we configure one universal virtual host for DEV Environment(for WorkFlow Tool there is additional for TEST environment) that is going to service all of the new Drupal instances. To do so, lets add new file – dev-virtual.conf in the /path/to/apache/sites-available(by default /etc/apache2/sites-available) as following:

<VirtualHost *:80>
  ServerAlias *.dev
  VirtualDocumentRoot /path/to/dev/%1
  ServerAlias localhost *.dev
  <Directory "/path/to/dev">
    Options +SymLinksIfOwnerMatch
    AllowOverride All
    Require all granted
  </Directory>
</VirtualHost>

Afterwards, lets enable the universal virtual host

sudo service a2ensite dev-virtual 
sudo service apache2 reload

Here, we specify root dir with ‘%1’ at the end to service each Drupal instance at its own directory. The ‘%1’ refers to the first part of domain name separated by dot. For example, metronic.dev will result the ‘%1’ refer to ‘metronic'(for more info see section ‘Directory Name Interpolation’).

As you see from the universal virtual host configurations above, the domain name we have set up to be *.dev, so next we configure all request *.dev point to the right machine.

Configure DNS or Set Up Own Local DNS

For public server, you will point all “*.dev” requests to the public server. In that case, all you need is to add the new DNS A entry in the DNS service as following:

*.dev   IN A    177.77.177.77

For local machines, we cannot use a wildcard in /etc/hosts, so we have to set up local DNS. Let’s use the light weight DNS solution – dnsmasq

sudo apt-get install dnsmasq

Once installed, we configure by editing /etc/dnsmasq.conf and adding line:

address=/dev/127.0.0.1

Here, we set *.dev requests point to 127.0.0.1 which is the local machine
Afterwards, restart the dnsmasq:

sudo service dnsmasq restart

Now, all request with the wild card (i.e. “*.dev”) will be pointed to local machine where the universal virtual host should service them from the directory specified.

Troubleshooting

1. client denied by server configuration: /path/to/dev/some

This error was caused by adding %1 at the end of path for the Directory configurations. Here is correct way:


VirtualDocumentRoot /path/to/dev/%1
...
//<Directory "/path/to/dev/%1">
<Directory "/path/to/dev">
...
                Order allow,deny
		Allow from all
...
</Directory>

By removing ‘%1’, the actual directory is found and no more errors. In short, when you have VirtualDocumentRoot, then the Directory root is the path to the virtual aliases instead the full path as it is with static virtual hosts configurations

References

  • http://brunodbo.ca/blog/2013/04/26/setting-up-wildcard-apache-virtual-host-wildcard-dns
  • http://float64.uk/blog/2014/08/22/dynamic-development-area-apache-php-fpm/
  • http://httpd.apache.org/docs/2.4/mod/mod_vhost_alias.html
  • https://httpd.apache.org/docs/2.4/rewrite/vhosts.html
  • https://httpd.apache.org/docs/2.4/vhosts/mass.html
  • https://code.google.com/p/mod-myvhost/wiki/Install
  • http://eosrei.net/articles/2012/08/create-dynamic-virtual-hosts-apache-http-vhostalias

CKeditor, Stop Altering Elements!!!

CKEditor is very opinionated on what needs to be altered/removed. It parses and filters html too strict for our needs, so this post will cover how to loosen up CKeditor to stop removing/altering html.

Before we move on, lets clarify that we can alter CKEditor behavior in 3 ways:

  • From HTML – by adding attributes provided by CKEditor such as data-cke-survive, data-cke-filter, etc
  • From Configurations – by changing CKEditor configurations in CKEDITOR.dtd
  • From Implementation – by implementing additional custom processor,filter,rules and attaching it to CKEditor mechanism

We will not cover the “From Implementation” in this post. Our perfect solution is to accomplish our task from configuration but we will also show how its done from HTML with CKEditor provided attributes.

Stop Removing Empty Anchor Tag in CKEditor

CKEditor is removing empty anchor tags such as the following:

<a data-original-title="youtube" class="youtube" href="#"> </a>
  • From HTML
    To stop removing anchors if the html is altered as following:

    <a data-original-title="youtube" class="youtube" href="#" data-cke-survive="true"> </a>
    //--OR--
    <a data-original-title="youtube" class="youtube"> </a>
    

    So, by adding new attribute of data-cke-survive or by removing attribute href, the empty anchor elements are not being removed

  • From Configurations
    We like to stop removing empty anchor tags from configurations as following:

    if(window.CKEDITOR){
                    CKEDITOR.on('instanceCreated', function (ev) {
                        CKEDITOR.dtd.$removeEmpty['a'] = 0;
                   }
    }
    

    This works if we alter the CKeditor library by changing function RemoveEmpty to

    function isRemoveEmpty( node ) {
    		// Keep marked element event if it is empty.
    		if ( node.attributes[ 'data-cke-survive' ] )
    			return false;
    
    		// Empty link is to be removed when empty but not anchor. (#7894)
    		//return node.name == 'a' && node.attributes.href || CKEDITOR.dtd.$removeEmpty[ node.name ];
    		return CKEDITOR.dtd.$removeEmpty[ node.name ];
    	}
    

    We realize this is not often times acceptable to update CKEditor library, however. This was only way we know to accomplish this from configurations. If you know how to do that without altering CKEditor library, please, let us know. We have also asked at the CKEditor forums:
    http://ckeditor.com/forums/CKEditor/How-to-stop-removing-empty-anchor-element

Stop Removing ANY Empty Tag in CKEditor

There is a defined list of tags that is going to be removed if empty(see dtd.js and $removeEmpty or run CKEDITOR.dtd.$removeEmpty from console).

  • From HTmL
    To ensure the certain empty tag are not being removed, add attribute ‘data-cke-survive’:

    <span data-cke-survive="true" ></span>
    
  • From Configurations
    Or you can configure the particular tag from not be removed:

    if(window.CKEDITOR){
                    CKEDITOR.on('instanceCreated', function (ev) {
                        CKEDITOR.dtd.$removeEmpty['span'] = 0;
                        CKEDITOR.dtd.$removeEmpty['TAG-NAME'] = 0;
                   }
    }
    

By setting an element to 0 in the CKEDITOR.dtd.$removeEmpty, it prevents the empty tags from being removed by CKEditor.

Stop Auto Wrap Tags in CKeditor

The default behavior is for CKEditor to wrap any inline elements into paragraph tags. To stop that globally:

if(window.CKEDITOR){
                CKEDITOR.on('instanceCreated', function (ev) {
                    CKEDITOR.config['autoParagraph'] = false
                    
                    //or for certain editor instance
                    if (ev.editor.name == textId) {
                        ev.editor.on('customConfigLoaded', function () {
                            ev.editor.config.autoParagraph = false;
                        });
                    }
               }
}
      

This will stop automatically wrapping every element into paragraph tag.
To stop wrap a certain element such as anchor tag into paragraph tag:

if(window.CKEDITOR){
                CKEDITOR.on('instanceCreated', function (ev) {
                      delete CKEDITOR.dtd.$inline['a'];
                }
}

This will change the declaration of the anchor tag to not be an inline element, thus, no any wrapping takes place

Perhaps, you like to wrap inline elements, but instead of paragraph tags, you like to wrap into ‘div’, ‘br’ or any tag of your choice, then:

CKEDITOR.config['entermode'] = CKEDITOR.ENTER_BR;//or config.entermode=2 in config.js
CKEDITOR.config['entermode'] = CKEDITOR.ENTER_DIV;//or config.entermode=23 in config.js

This will add BR or DIV tag to every inline element

Stop Wrap UL around LI in CKEditor

By default, CKeditor is wrapping UL element around LI if one is not present. To stop:

if(window.CKEDITOR){
                CKEDITOR.on('instanceCreated', function (ev) {
                  //make sure LI is not wrapped within UL
                    delete CKEDITOR.dtd.$listItem['li'];
                    delete CKEDITOR.dtd.$intermediate['li'];    
                }
}

By removing the LI form the list, you ensure that CKEditor is not wrapping LI elements with additional “UL”.

Stop Filtering in CKEditor

By default, CKEditor runs its processors and filters when outputing content.

  • From Html – To disable the filter or processing on a particular tag and children add the the attribute as following:
    • data-cke-filter=”off”
    • data-cke-processor=”off”
    • autocomplete=”off”

    The data-cke-filter stops the filering, the data-cke-processor stops the processor and the autocomplete for both. This will apply for the tag and all of its children

  • From Configuration – To disable processing/filtering from configurations, add the following:
    if(window.CKEDITOR){
                    CKEDITOR.on('instanceCreated', function (ev) {
                        //disable filters
                        ev.editor.on('customConfigLoaded', function () {
                            ev.editor.config.allowedContent = true;
                        });
                    }
    }
    

Here by setting configuration allowedContent to true, it disables the filtering/processing editor content output

VirtualBox Gotchas

This is ongoing post listing different challenges and how those can be overcome when utilizing VirtualBox for your development needs

1. VirtualBox host only supports 32bit guest OS

This was a problem on Windows 8 Intel processor machine that didn’t have vt-x enabled. The vt-x is feature of Intel processor that allows the virtualization of some kind. Once enabled, the VirtualBox host can load also 64bit Guest OS. To enable, boot Vindows 8 in BIOS and enable the vt-x feature

2. Freezes with “Building initial module for 3.x”

Make sure the guest OS you are installing is for the right processor.

3. Cannot Change Display Resolution

Install the VirtualBox additions at the Guest

sudo apt-get install virtualbox-guest-dkms

Afterwards, reboot and the display resolution works as you like

4. Cannot Copy & Paste from VirtualBox Host to Client and from Client to Host

Copy/paste is not enabled by default. You have to enable in host ‘Settings’ under ‘General’->’Advanced’ and check ‘Shared Clipboard’ and ‘Drag & Drop’. Note, that changes can only made when client OS is turned off.

5. Sharing folders Between VirtualBox Host and Guest

There are two ways to share directories between Host and Guest – via VirtualBox Shares or via independent Samba Server. As we learn later, the VirtualBox Shares solutions is very slow for Apache to service web applicaitons such as Drupal. So we strongly recommend Samba solution for sharing directories, however. Here are setup for both solutions:

  • Share Via Samba

    Please, see post “Sharing Vis Samba Instead VirtualBox Shares” for sharing directories via Samba

  • Share Via VirtualBox Shares

    There are two steps:

    1. In the host settings under ‘shared folder’ directory, add the shared directory and give a name ‘some-host-share-name’. Make sure you check “Permanent”, so its available on each reboot
    2. Next, physically mount the shared directory

      sudo mount -t vboxsf -o uid=$UID,gid=$(id -g) some-host-share-name ~/guest-dir
      

    Make sure the ‘~/guest-dir’ exists on the guest OS. Otherwise, you will get error “/sbin/mount.vboxsf: mounting failed with the error: No such file or directory” or no any error and it will just not mount

To mount it automatically on reboot, add the entry in the /etc/rc.local as following:

  • For Samba solution:
    mount.cifs //HOST-IP/SHARE-NAME /path/to/shared/dir -o uid=1000,iocharset=utf8,rw,username=HOST-USER,password=HOST-USER-PASSWORD,nounix
    
  • For VirtualBox shares solution:
    mount.vboxsf -w some-host-share-name /home/<username>/guest-dir
    

This will mount the the shared drive on each reboot automatically.

5A. Share Between Guest OS of Windows XP

First ensure, the Guest addition is installed for the Guest Windows XP OS by grabbing appropriate ISO of your VirtualBox solution at the http://download.virtualbox.org/virtualbox/ dir.
Next, add the ISO to the Optical CD/DVD drive of the Guest OS.
At last, run the executable/installer in the ISO rood directory. This will install Guest additions necessary, so that at the next boot it creates the shared dir in the Guest Windows OS automatically in the “Network Drives”

6. Easily access your NAT configured virtual machine

You probably need host access servers on the guest OS, while your guest OS, probably, need access internet at the same time. Here is NAT + Host ONLY solution to address both needs configurable as following.

  1. Create new Network Go to VirtualBox VM->Preferences->Network->Host Only Networks and add new one with the following settings:
    IP:10.88.12.0
    Mask:255.0.0.0
    

    Keep DHCP server disable, so you can configure static IP much more easier than looking up the new IP every time it changes

  2. Add Network Interfaces Add the network interfaces by going to Guest Settings->Network->Adapter
    • Adapter 1: NAT
    • Adapter 2: Host Only Adapter with “name” of network you configured in step 1 above
  3. Configure Two Guest OS Interfaces. In your guest OS, edit /etc/network/interfaces for Deb or Ubuntu as following:
    # interfaces(5) file used by ifup(8) and ifdown(8)
    auto lo
    iface lo inet loopback
    
    #the primary network interface
    auto eth0
    iface eth0 inet dhcp
    
    #The secondary network interface
    auto eth1
    iface eth1 inet static
    address 10.88.12.4
    netmask 255.0.0.0
    

    This will configure two network interfaces one for NAT and another for Host Only network

Afterwards, reboot guest OS and you should access guest OS at 10.88.12.4 from host, access host at 10.88.12.0 from guest all which while your guest OS have internet connection via the NAT network

7. Slow Performance for Guest xUbuntu

The following settings improved Guest performance:

  • Check “Storage” -> “Use Host I/O Cache”
  • Check “Display” -> “Enable 3D Acceleration”
  • Install guest addtions, utilities and drivers:
    sudo apt-get install virtualbox-guest-dkms virtualbox-guest-utils virtualbox-guest-x11
    
  • Add extra CPUs “System”->”Processor”
  • Check “Enable PAE/NX” under “System”->”Processor”
  • Add more RAM to Video Card “Display” -> “Video” -> “Video Ram”
  • Install VB extension pack on the host machine:
    1. Download the extension pack from VirtualBox Download page
    2. Run the following command:
      sudo VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-4.3.18-96516.vbox-extpack 
      sudo VBoxManage list extpacks
      
  • look into logs to see if it shows any issues that may affect performance

Note: After analyzing performance for my Drupal site running on VirtualBox(see more details at post looking-at-drupal-performance-with-xprof-and-xhgui), it shows that the top 6 slowest functions are “file_scan_directory”. It made me conclude that reading/writing on disk is the slowest. I upgraded HD to SSD HD, but while there was performance improvement it still was noticeable. This applies to the shared directories between Guest and Host. If not shared then ‘file_scan_directory’ is not any more the bottleneck and Drupal site runs really fast.

UPDATE: The best improvement by far was moving away from sharing directories via VirtualBox Shares solution to the sharing via Samba Server as described in post Sharing Via Samba Instead VirtualBox Shares

8. Increase Size for VB Guest Hard Drive

After about 3 weeks running VB, it run out of space on Hard Drive that was set of fixed size. Here are steps to increase Hard Drive space

  1. [ONLY IF FIXED SIZE] Re-clone HD .VDI to be able use ModifyHD
    Resizing HD involves runing command ‘modifyhd’, however. This only works for HD that were not fixed, so before we are able to use “modifyhd”, we re-clone our HD .vdi file:

    sudo VBoxManage clonehd "/full/path/to/disk1.vdi" ClonedFixedHD.vdi --format VDI --variant Standard
    

    This will prepare our Hard Drive .vdi file for modifyhd command to use in next step

  2. Resize the HD Drive .vdi
    sudo VBoxManage modifyhd "/full/path/to/ClonedDevelLamp.vdi" --resize 20000
    

    This will increase the size of the hard drive to 20GB

  3. Update Partition Size for VB Guest
    Once the size is changed for hard drive, we need to update also the VB Guest partition that mounts the new sized Hard drive. It is accomplish by booting from GParted

    1. Download CD of GParted
    2. Mount to CD drive of the VB Guest
    3. Boot into your VB Guest
    4. Resize partition.Here is good article
  4. Remove Old Hard Drive & Attache New Resized HD
    Go to Setting->Storage and remove the old HD .vdi. Then add your new resized HD. Afterwards, reboot the machine

Check Logs

In the following are directories for logs per OS:

  • On Windows, this is %HOMEDRIVE%%HOMEPATH%\\.VirtualBox\\Machines\\\\Logs; typically some- thing like C:\\Documents and Settings\\Username\\.VirtualBox\\Machines\\\\Logs\\vbox.log
  • On Mac OS X, this is $HOME/Library/VirtualBox/Machines//Logs
  • On Unix-like systems (Linux, Solaris), this is $HOME/.VirtualBox/Machines//Logs

Troubleshooting

1. No extension pack by the name ‘Oracle VM VirtualBox Extension Pack’ was found

Go to VirtualBox Download page and download extension pack. Afterwards, install it as following:

sudo VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-4.3.18-96516.vbox-extpack 

To verify:

sudo VBoxManage list extpacks

This should display descriptions of the extension pack or 0 for no extension pack present

2. VBoxManage: error: Cannot register the hard disk ‘/path/to/disk1.vdi’ {8e28be07-2543-4967-a83f-8a6a07d18c64} because a hard disk ‘path/to/disk1.vdi’ with UUID {..} already exists

We got this after cloning and then trying to resize hard drive. The solution was to reset UUID:

sudo VBoxManage internalcommands sethduuid "/full/path/to/disk1.vdi"

Make sure you provide full path to the HD file .vdi, otherwise, you will get another error at least on Mac OS.

3. Progress state: VBOX_E_NOT_SUPPORTED

This error came up when trying to resize the HD file .vdi, because the HD was configured fixed and command “modifyhd” doesn’t work with fixed HD .vdi. The solution was convert the HD .vdi into one that is not fixed size by re-cloning as following:

sudo VBoxManage clonehd "/full/path/to/disk1.vdi" ClonedNewVDIfile --format VDI --variant Standard

This will create new .vdi size ready to work with “modifyhd”

4. My .VDI is missing

I was looking at $HOME/VirtualBox VM/SOME_VM_BOX_DIR just to find there is no .vdi file, however, the particular Box was loading okey. I was unaware that the HD(i.e. .vdi file) can be outside the Box directory. See “Settings”->”Storage” to locate where is the Hard Drive .vdi file located

VBoxManage: error: Could not find file for the medium ‘/full/path/to/disk1.vdi’ (VERR_FILE_NOT_FOUND)/h6>
This error is because the path specified is not correct or it is not full path that is required for running VBoxManage commands

5. “the parameter is incorrect” Or “the network name cannot be found” OR “is not accessible”

This error happened when I was sharing files of mac host with Window XP as guest OS and I didn’t have the Guest Additions installed. To install, grab appropriate ISO of your VirtualBox solution at the http://download.virtualbox.org/virtualbox/ dir. Next, add the ISO to the Optical CD/DVD drive of the Guest OS. At last, run the executable/installer in the ISO rood directory. This will install Guest additions necessary, so that at the next boot it creates the shared dir in the Guest Windows OS automatically in the “Network Drives” dir

References

https://forums.virtualbox.org/viewtopic.php?f=24&t=50661
http://drwho.virtadpt.net/archive/2014/02/04/problems-cloning-virtualbox-disk-images

Resizing vmdk harddisks for VirtualBox

jQuery Snippets

Here are list of JQuery code snippets updated ongoing bases

0. Attach Custom Function to HTML Element
(function ($) {
    $('.image_button').click(function(e){
        e.preventDefault();
        var img_src = $(this).find('img').attr('src');
        $('#focus-image').attr('src',img_src);
    });
})(jQuery);

Here, we put everything in parentheses so it executes on page load while passing jQuery object and assign it to “$” symbol. Afterwards, search the html element with class ‘image_button’ and assign click event with our function. This function grabs src from one element and swaps with another

1. How to find events attached to particular element
$._data($('a[href="/node/56/edit"]')[0], "events");

the first element in the _date() function is the element. In our case, that is a link with an attribute of ‘href’ equal to specific path

or with chrome Developer tools select “inspect element’ on the element and then look at the righ

2. How to assign your custom methods to an DOM element
(function(window, document, $) {

var prototype = $.fn,
	    placeholder;
...
            placeholder = prototype.placeholder = function() {
			return this;
		};
            function customFunciton() {
                    //do something
            }
}(this, document, jQuery));

Here, the prototype refers the global prototype heap, so by assigning function placeholder() to the global prototype we make it available to call on any of the element we wish:

$('textarea').placeholder();

So this will assign the scope with all our custom function, so anyone can all the custom function ‘customFunction’ from now on

3. Ways to call your Custom function
$(window).on('load', function () {
    $('[data-ride="carousel"]').each(function () {
      var $carousel = $(this)
      $carousel.carousel($carousel.data())
    })
  })

Here, onload event we search for dom element and then once found execute our costum method ‘carousel’

4. Stop Event

Once you attach an event to an element(i.e.btn-nav-toggle-responsive) to the link tag, you may want it not to go anywhere. In that case, you prevent the default behaviour

$('.btn-nav-toggle-responsive').click(function(e){
		$('.left-sidebar').toggleClass('show-fullsidebar');
        e.preventDefault();
	});

Here, we passing in the event ‘e’, so that we can call ‘preventDefault() that prevents from going to a different link

5. Trigger Event From Console

To trigger events from console:

var my_e = jQuery.Event("some_event_id");
jQuery("body").trigger(my_e)

This will trigger event ‘some_event_id’ from console
To trigger event from code:

$.event.trigger({
            type: "reset_image_ds"
        });
6. Load JS Library from Console

There are at least two ways:

jQuery.getScript('http://dev-ckeditor/ckeditor/ckeditor.js');

In this approach we use jQuery libarary to load script ckeditor.js. An altertnative, is:

var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'script.js';
document.head.appendChild(script);

For stylesheets:

document.appendStyleSheet('/path/bootstrap.min.css');
7. Decode String in jQuery

Perhaps, you garbed html text just to learn it is in the encoded form. To decode it back into html tags:

var decodedHtml = jQuery('<textarea/>').html("SOME-STRING-WITH-HTML-CHARACTERS").val();

This will take all html character symbols and convert to appropriate tags names

8. jQuerify HTML

Perhaps, you have raw HTML and you would like to make into jQuery elements, so you can take advantage of jQuery Lib:

var b = jQuery(decodedOldHtml);
var output = b.find('.wrapper-end').html();

This will take each html element and wrap into jQuery objects. Afterwards, you can call jQuery Api as we did here to find certain element(i.e. wrapper-end)

Note: You will get error –

“Uncaught Error: Syntax error, unrecognized expression:”

if the content you are trying to jQuerify(i.e. decodeOldHtml) is not wrapped into any tags. The good practice always wrap into some tags before jQuerify as following:

jQuery('<div>' + decodedOldHtml + '</div>');

This way you will avoid the Syntax error all times for text only content. The more important it will not break on ‘find’ calls by putting it all in one jQuery object. For more, see jquerify-string-to-find-element-fails. At last, ensure the wrapping tags are not ‘body’ because jQuery ignores the ‘body’ tag wrapper

9. Make jQuery Sandbox

It may be too long of name – jQuery to type each time, so what often is done is create jQuery sandbox and pass the “$” as jQuery object:

(function ($) {
       console.log($('find-me').html());
})(jQuery);

Here,we first wrap our code into parentheses, so besides parsing it is also executed. In addition,for our code block, we pass in jQuery object by putting at the end and assigning it to “$” since its the parameter in the function.

Often times your code will do some manipulation on DOM, then you probably like to make sure DOM is loaded before the code in your sandbox is executed:

jQuery(document).ready(function($) {
       console.log($('find-me').html());
});

So, we attach event ‘ready’ before executing the sandbox

At last, it may be the case you will merge your code with other javascript files, then add ‘;’ in front to avoid breaking the execution:

;jQuery(document).ready(function($) {
       console.log($('find-me').html());
})(jQuery);
9. Remove and Replace DOM Elements

Some of the most common manipulations to the DOM is remove & replace. To remove element with its children:

$('.find-me').empty();

This will remove the element ‘.find-me’ and all of the children(the tree) below
To replace an element or often times remove the element but not the children:

var cnt = $('.find-me').contents();
 $('find-me').replaceWith(cnt);

This will take out the element ‘.find-me’ but keep all the children intact.

JS Widget Boilerplate

There are probably several ways to setup and organize code for your JS widget. Here is one I find common and works well for me:

if (!window.VIRTUAL_HOSTY) {
    /**
     * @singleton
     */
    window.VIRTUAL_HOSTY = (function ($) {

        //private variables come here...
        var base_url = window.BASE_URL || '/',
            log_on = false;

        //private function to initiate your widget
        var _init = function () {
            $(window.document).ready(function ($) {
               _parse_url();
            });
        }

        //public interface...the API for your widget
        return {
            set_base: function (some_val) {//function to set private variable
                base_url = some_val;
            },
            get_base: function () {//function to retrieve private variable
                return base_url;
            },
            init: function(){//public function called to initialize widget
              _init();  
            },
            turnOnDebug: function () {//public function to turn on logging
                log_on = true;
            }
        }
    })(jQuery);
}

Here we create global variable VIRTUAL_HOSTY that is the access point of our Widget. All the interaction happens via this variable. The VIRTUAL_HOSTY returns an object. All of the functions specified in this object is public. Everything else is private since we wrapped it into parentheses “()” making the scope execute at the time of load accessible only with the references in the returned object VIRTUAL_HOSTY

Once you have the widget lib file imported you can call on it:

<script>
VIRTUAL_HOSTY.turnOnDebug();
VIRTUAL_HOSTY.init();
</script>

Here,we turn on logging and then initiate our widget VIRTUAL_HOSTY

How to Pass Variables Horizontally and Vertically in Drupal

There are some variables($node, $page,etc) you depend more than on anothers in Drupal. Its important to have access them when you need. In the following, we describe two ways -“top to down” and “left to right” to pass and accesses any variable at any part of the execution in Drupal

Top to Down …or Vertical

A good example, would be passing some variable from page scope to the node scope

function THEME_preprocess_page(&$vars, $hook){
...
   $vars['node']->bazar_product = $prod_default;
}

Since page scope calls theme function ‘node’ to render the node, it takes the variable – vars as parameter(see theme function for node). You can utilize that by storing your custom variables into $vars making it accessible in the node scope – hook_preprocess_node().
For passing between any other scope, first find what theme function is called for rendering the children and look at this theme’s function declaration to see exact variables being passed down to children

Left To Right …or Horizontal

Often times we need to know what node type is set for the current request in the html scope – hook_preprocess_html(), however the node type is processed and set during page scope. Since there are no variables passed between hook_preporcess_page and hook_preporcess_html, the variables such as node type is not shared between, however. We need the node type in the html scope, so we can render particular template. To pass variables from one scope to another horizontally, we utilize cache as following:

//get the current content from cache
$content = drupal_set_page_content();
//indicate that you are not adding content but just reading it by setting static variable 'system...added' to False 
$system_main_content_added = &drupal_static('system_main_content_added');
$system_main_content_added = FALSE;

The variable content will have variables from hook_preproccess_page scope. Its important to note, that cache may not always work because you may be asking too early.